Extension of saveactions plugin that allows Geany to store/load data for new, unnamed tabs when Geany window is closed/reopened. This is a convenience feature inspired by Notepad++ and discussed here https://github.com/geany/geany/issues/905 Includes one small extension to API (new function in main.h) - if i understood correctly, this does not require incrementing any versions
https://github.com/geany/geany/assets/9273621/8bdd67a1-97a4-4cf3-b224-81f194...
You can view, comment on, or merge this pull request online at:
https://github.com/geany/geany/pull/3911
-- Commit Summary --
* persistent-temp-files-plugin: added feature to saveactions plugin
-- File Changes --
M plugins/saveactions.c (605) M src/libmain.c (12) M src/main.h (2)
-- Patch Links --
https://github.com/geany/geany/pull/3911.patch https://github.com/geany/geany/pull/3911.diff
@LiquidCake commented on this pull request.
return;
}
- close(fd); /* close the returned file descriptor as we only need the filename */ + old_file_name = g_path_get_basename(old_file_path); + + if (is_temp_saved_file(old_file_name) && ! g_str_equal(old_file_path, new_file_path)) + { + /* we have to store old/new filename pair in a global hashtable to be able to somehow + pass it to document-save callback that is called directly after this one */ + g_hash_table_insert(
we have to use this hack with hashtable to remember name of original 'temp' file, during performing 'save as' which assigns a new 'final' name to file.
Ideally we would like to do all these actions in a more proper manner but this would require core code modifications
@techee requested changes on this pull request.
Thanks for the patch - looks good to me in general except for some of the notes below. I personally think that having such a feature in Geany (i.e. multi-tab Scrilbble backed by Scintilla with all the Geany features) would be nice, but depends what others think.
I didn't go through all the things in detail and this is just a rough quick review.
Includes one small extension to API (new function in main.h) - if i understood correctly, this does not require incrementing any versions
Yes, it does, but it can be done one everything is ironed-out.
ui_set_statusbar(TRUE, "%s", message);
+ g_warning("%s", message); + g_free(message); + g_free(new_filename); + return; + } + + close(fd); /* close the returned file descriptor as we only need the filename */ + + doc->file_name = new_filename; + + if (ft != NULL && ft->id == GEANY_FILETYPES_NONE) + document_set_filetype(doc, filetypes_lookup_by_name(default_ft)); + + /* force saving the file to enable all the related actions(tab name, filetype, etc.) */ + document_save_file(doc, TRUE);
Couldn't document_save_file_as be used here instead? It should do the filetype detection for you and shouldn't require the kind of hacky `doc->file_name = new_filename`. (I know this was copied from the previous code but this might be better unless I overlook something.)
+ if (file_path == NULL) + { + return FALSE; + } + + filename = g_path_get_basename(file_path); + matched = is_temp_saved_file(filename); + + g_free(filename); + + return matched; +} + + +static void load_all_temp_files_into_editor(const gchar *path)
Is this whole function needed? Couldn't persistent temp files just use the standard Geany session management so they are stored either in the global session or a project based session and restored from it automatically by Geany?
}
+ else + { + g_remove(locale_file_path); + + ui_set_statusbar(TRUE, _("Empty temp file %s was deleted"), short_filename); + } + + g_free(short_filename); + g_free(locale_file_path); + } + } +} + + +static void persistent_temp_files_document_before_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data)
I guess you are trying to handle the "save as" case here, right? Then you could use the newly added `document-before-save-as`:
https://github.com/geany/geany/pull/3572
@@ -347,8 +706,13 @@ static gboolean on_document_focus_out(GObject *object, GeanyEditor *editor,
PluginCallback plugin_callbacks[] = { { "document-new", (GCallback) &instantsave_document_new_cb, FALSE, NULL }, + { "document-new", (GCallback) &persistent_temp_files_document_new_cb, FALSE, NULL },
I'd suggest having a single handler which then dispatches it to either the instantsave case or the persistent temp file case. Same for `document-save` below.
locale_file_path = g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL);
+ + if (is_temp_saved_file(utf8_filename)) + { + document_open_file(locale_file_path, FALSE, NULL, NULL); + } + + g_free(utf8_filename); + g_free(locale_file_path); + } + + g_dir_close(dir); +} + + +static gboolean file_exists(gchar *utf8_file_path)
Instead of this function you can just check `doc->real_path != NULL`.
- }
+} + + +static void persistent_temp_files_document_close_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *file_path, *locale_file_path, *short_filename; + + file_path = doc->file_name; + + if (enable_persistent_temp_files && file_path != NULL && ! geany_is_quitting()) + { + if (is_temp_saved_file_doc(doc) && file_exists(doc->file_name)) + { + short_filename = document_get_basename_for_display(doc, -1); + locale_file_path = utils_get_locale_from_utf8(file_path);
You can use `doc->real_path` instead.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
I don't think you want `main_status.quitting` here - IMO you want `main_status.closing_all` instead. Note that the plugin should work both for: - the default session (list of open files) when no project is open - project session when some project is open
I think you can just safely rely on Geany's session management and let Geany store persistent temp files into the corresponding session and reopen them from there.
You need to use `main_status.closing_all` to detect situations when a session is being closed including all its files and this can happen either when a project is closed (for project sessions) or when Geany quits (default session).
return;
}
- close(fd); /* close the returned file descriptor as we only need the filename */ + old_file_name = g_path_get_basename(old_file_path); + + if (is_temp_saved_file(old_file_name) && ! g_str_equal(old_file_path, new_file_path)) + { + /* we have to store old/new filename pair in a global hashtable to be able to somehow + pass it to document-save callback that is called directly after this one */ + g_hash_table_insert(
You can use ```C gpointer plugin_get_document_data(struct GeanyPlugin *plugin, struct GeanyDocument *doc, const gchar *key);
void plugin_set_document_data(struct GeanyPlugin *plugin, struct GeanyDocument *doc, const gchar *key, gpointer data); ``` to assign/retrieve arbitrary data to/from a document which is probably more elegant in this case.
+ if (file_path == NULL) + { + return FALSE; + } + + filename = g_path_get_basename(file_path); + matched = is_temp_saved_file(filename); + + g_free(filename); + + return matched; +} + + +static void load_all_temp_files_into_editor(const gchar *path)
Seems to work when I comment-out this function call.
@techee commented on this pull request.
+ if (file_path == NULL) + { + return FALSE; + } + + filename = g_path_get_basename(file_path); + matched = is_temp_saved_file(filename); + + g_free(filename); + + return matched; +} + + +static void load_all_temp_files_into_editor(const gchar *path)
Another note - automatic Geany session management will only work when Preferences->General->Startup->Load files from the last session is used. But I think it's kind of expected that Geany wouldn't auto-load persistent temp files in when this option is disabled.
@techee commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
Maybe one more idea - wouldn't it be better to introduce this feature just as a special mode of "Instant save"? Because I think that's what this feature really is - it just picks the file names automatically, prevents some accidental tab closing but it's instant save otherwise. It would also prevent possible confusion why the Enable button in preferences gets disabled.
@elextr commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
Rather better would be buffer saving, all modified buffers get saved and can be restored on restart. Saving buffers means that there is no need to do encoding conversion, or trailing space removal or any of the other modifications that are made on save and just write metadata (filename if it exists and other per document settings) as well. That way documents both with and without names would be safe. Might need to be in core to interact properly with existing session management, although that may also be simplified.
@techee commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
Rather better would be buffer saving, all modified buffers get saved and can be restored on restart. Saving buffers means that there is no need to do encoding conversion, or trailing space removal or any of the other modifications that are made on save and just write metadata (filename if it exists and other per document settings) as well. That way documents both with and without names would be safe. Might need to be in core to interact properly with existing session management, although that may also be simplified.
Agree in principle but someone has to rewrite the session management in Geany and someone has to review and approve it. And until something like that happens, possibly imperfect solution like this PR is just fine IMO and can be removed if we have something better.
thanks for your comments, will look at it
@LiquidCake pushed 1 commit.
a8c882687ff5fc633ba055c165dc7ae94c9daf9a persistent-temp-files-plugin: code review - fixes
@LiquidCake pushed 1 commit.
7ef14a845f76aabcdf8917d6d419e41e4ac0be82 persistent-temp-files-plugin: code review - fixes
@LiquidCake pushed 1 commit.
6616f7e1443a11d820666006be881c8616328b69 persistent-temp-files-plugin: code review - fixes
@LiquidCake pushed 1 commit.
064011ad2e00ecb18e2b5177b2149c8908098027 persistent-temp-files-plugin: code review - fixes
@LiquidCake commented on this pull request.
return;
}
- close(fd); /* close the returned file descriptor as we only need the filename */ + old_file_name = g_path_get_basename(old_file_path); + + if (is_temp_saved_file(old_file_name) && ! g_str_equal(old_file_path, new_file_path)) + { + /* we have to store old/new filename pair in a global hashtable to be able to somehow + pass it to document-save callback that is called directly after this one */ + g_hash_table_insert(
done
@LiquidCake commented on this pull request.
ui_set_statusbar(TRUE, "%s", message);
+ g_warning("%s", message); + g_free(message); + g_free(new_filename); + return; + } + + close(fd); /* close the returned file descriptor as we only need the filename */ + + doc->file_name = new_filename; + + if (ft != NULL && ft->id == GEANY_FILETYPES_NONE) + document_set_filetype(doc, filetypes_lookup_by_name(default_ft)); + + /* force saving the file to enable all the related actions(tab name, filetype, etc.) */ + document_save_file(doc, TRUE);
this method was re-used from Instantsave, which seems to need arbitrary file type setting. Anyway im not re-using this code anymore - switched to `document_save_file_as` as you suggested. So Instantsave's code is left intact
@LiquidCake commented on this pull request.
+ if (file_path == NULL) + { + return FALSE; + } + + filename = g_path_get_basename(file_path); + matched = is_temp_saved_file(filename); + + g_free(filename); + + return matched; +} + + +static void load_all_temp_files_into_editor(const gchar *path)
removed, see comment about `is_quitting vs closing_all`
@LiquidCake commented on this pull request.
}
+ else + { + g_remove(locale_file_path); + + ui_set_statusbar(TRUE, _("Empty temp file %s was deleted"), short_filename); + } + + g_free(short_filename); + g_free(locale_file_path); + } + } +} + + +static void persistent_temp_files_document_before_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data)
using before-save-as wont work because at the moment that event is fired we dont yet have "new" document name defined, and we need both "old" and "new"
@LiquidCake commented on this pull request.
@@ -347,8 +706,13 @@ static gboolean on_document_focus_out(GObject *object, GeanyEditor *editor,
PluginCallback plugin_callbacks[] = { { "document-new", (GCallback) &instantsave_document_new_cb, FALSE, NULL }, + { "document-new", (GCallback) &persistent_temp_files_document_new_cb, FALSE, NULL },
done
@LiquidCake commented on this pull request.
locale_file_path = g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL);
+ + if (is_temp_saved_file(utf8_filename)) + { + document_open_file(locale_file_path, FALSE, NULL, NULL); + } + + g_free(utf8_filename); + g_free(locale_file_path); + } + + g_dir_close(dir); +} + + +static gboolean file_exists(gchar *utf8_file_path)
done
@LiquidCake commented on this pull request.
- }
+} + + +static void persistent_temp_files_document_close_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *file_path, *locale_file_path, *short_filename; + + file_path = doc->file_name; + + if (enable_persistent_temp_files && file_path != NULL && ! geany_is_quitting()) + { + if (is_temp_saved_file_doc(doc) && file_exists(doc->file_name)) + { + short_filename = document_get_basename_for_display(doc, -1); + locale_file_path = utils_get_locale_from_utf8(file_path);
done
@LiquidCake commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
initial idea was to force all temp files to be open in Geany all the time, or at least open all of them on startup. Even ones created by concurrent Geany instances or manually on disk. Thats why we had that function "load all temp files" and why we used "is quitting" instead of "is closing all" - to force user saving or deleting temp files after clicking "close all" and avoid dialogs only when closing window.
Now after few changes (temp files are now properly numbered by integers and are properly readable in "recent files" menu) i guess we got a better solution and this idea of keeping all existing temp files open is probably not actual. So i did as was suggested - switched to "closing all" status and started completely relying on session management mechanism to re-open those temp files which it finds appropriate. This seems to work fine, including general session vs project sessions.
@LiquidCake commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
about extending Instantsave - this was an initial idea i tried, but then i decided to do a separate function since unification becomes too complicated both in terms of code and in terms of usage - e.g. instantsave's target directory has a bit different usecase then ours, and there are other complications.
Btw i changed mutual exclusion of instantsave vs temp_files from blocking one another's checkbox to just automatically de-activating one when another is activated. This seems a good-enough UX to me as newly created files start to look differently when you switch mode anyway
@techee requested changes on this pull request.
See some more review comments.
Maybe one more idea - wouldn't it be better to introduce this feature just as a special mode of "Instant save"? Because I think that's what this feature really is - it just picks the file names automatically, prevents some accidental tab closing but it's instant save otherwise. It would also prevent possible confusion why the Enable button in preferences gets disabled.
What I meant by this is that the whole "persistent temp files" tab in config could go away and the "Instant save" tab would be reused for this feature too. Te rationale about this is:
It reduces confusion because these features are mutually exclusive and enabling one disables the other and users don't see clearly why this happens. I'm slightly worried that users won't get the "persistent temp files" naming and what this feature is good for (as usual, nobody will read the documentation). So what I would suggest is to have a checkbox with something like "Scribble mode" inside the "Instant save" tab enabling this feature. Users probably already know the Scribble tab in Geany's message window and kind of know what to expect. Also, the generated filenames could start with scribble_ making it consistent with existing Geany features. If it's implemented this way, the code below would just go away, only the save interval would have to be added into "Instant save" (grayed out when "Scribble mode" not enabled) and the text explaining the persistence of the directory where the temp files are stored should be changed a little.
Now before implementing anything, I'd suggest waiting for others what they think about it - this would make sense to me but others may have a different opinion.
- }
+ + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX); + + max_temp_file_number = 0; + + foreach_dir(filename, dir) + { + utf8_filename = utils_get_utf8_from_locale(filename); + + if (is_temp_saved_file(utf8_filename)) + { + temp_file_number_str = g_utf8_substring(utf8_filename, temp_file_name_prefix_len, -1); + temp_file_number = atoi(temp_file_number_str); + + if (temp_file_number > max_temp_file_number)
`max_temp_file_number = MAX(max_temp_file_number, temp_file_number)` would be a little simpler.
temp_file_number = atoi(temp_file_number_str);
+ + if (temp_file_number > max_temp_file_number) + { + max_temp_file_number = temp_file_number; + } + + g_free(temp_file_number_str); + } + + g_free(utf8_filename); + } + + g_dir_close(dir); + + return g_strdup_printf(_("%s%d"), PERSISTENT_TEMP_FILE_NAME_PREFIX, max_temp_file_number + 1);
I don't think the returned file name should be translatable (i.e. remove `_()`).
+static gchar* create_new_temp_file_name(void)
+{ + GDir *dir; + GError *error = NULL; + gchar *utf8_filename, *temp_file_number_str; + gint temp_file_number, max_temp_file_number; + const gchar *filename; + + dir = g_dir_open(persistent_temp_files_target_dir, 0, &error); + if (dir == NULL) + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Persistent temp files directory not found")); + return NULL; + } + + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX);
The Geany code style is to place declarations at the beginning of the block - i.e. `static glong temp_file_name_prefix_len;` would go to the beginning of the function and you'd just assign the variable here.
Similarly, some of the declarations at the beginning of the function could go at the block beginnings of `for` and `if` blocks below so they are closer to their actual usage. Possibly also in the rest of the code.
+{
+ /* if total text length is 1 while line count is 2 - then the only character of whole text is a linebreak, + which is how completely empty document saved by Geany to disk looks like */ + return sci_get_length(doc->editor->sci) == 0 + || (sci_get_length(doc->editor->sci) == 1 && sci_get_line_count(doc->editor->sci) == 2); +} + + +/* Open document in idle - useful when trying to re-open document from 'document-close' callback + * + * @param pointer to document path (locale-encoded) + * + * @return always FALSE = Just a one shot execution + * + */ +static gboolean open_document_once_idle(gpointer p_locale_file_path)
It's a bit unfortunate this is necessary - maybe Geany could add some signal allowing plugins to disallow document closing.
/* we have to store old/new filename pair inside document data to be able to somehow
+ pass it to document-save callback that is called directly after this one */ + plugin_set_document_data_full(geany_plugin, doc, new_file_path, g_strdup(old_file_path), g_free); + } + + g_free(old_file_name); + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path, *old_file_path; + + new_file_path = DOC_FILENAME(doc); + old_file_path = plugin_get_document_data(geany_plugin, doc, new_file_path);
Instead of indexing it by `new_file_path` you could use just some static string like `"old_path"` - the extra data is attached to a document so there's no risk you'll get some other's document path.
GtkWidget *page_label = NULL;
+ + get_current_tab_label(&page_label); + + old_file_path = gtk_widget_get_tooltip_text(page_label); + new_file_path = DOC_FILENAME(doc); + + if (old_file_path == NULL) + { + ui_set_statusbar(TRUE, _("plugin error: failed to delete initial temp file " + "('failed to get notebook tab label text')")); + + return; + } + + old_file_name = g_path_get_basename(old_file_path);
I' just move `g_path_get_basename()` inside `is_temp_saved_file()` so it could be dropped here (and also inside ``is_temp_saved_file_doc()``)
- const gchar *filename;
+ + dir = g_dir_open(persistent_temp_files_target_dir, 0, &error); + if (dir == NULL) + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Persistent temp files directory not found")); + return NULL; + } + + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX); + + max_temp_file_number = 0; + + foreach_dir(filename, dir) + { + utf8_filename = utils_get_utf8_from_locale(filename);
The conversion is probably an overkill here - the file name will just always be ASCII so `locale == UTF8`.
+ g_free(old_file_name); + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path, *old_file_path; + + new_file_path = DOC_FILENAME(doc); + old_file_path = plugin_get_document_data(geany_plugin, doc, new_file_path); + + if (old_file_path != NULL) + { + gchar *old_file_name = g_path_get_basename(old_file_path);
Also wouldn't be necessary if moved inside `is_temp_saved_file()`.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is 'closing all' tabs right now. + * + * @return @c TRUE if the Geany is 'closing all' tabs right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_closing_all_tabs(void)
I'd probably call it `geany_is_closing_all_documents()`.
}
+ else + { + g_remove(locale_file_path); + + ui_set_statusbar(TRUE, _("Empty temp file %s was deleted"), short_filename); + } + + g_free(short_filename); + g_free(locale_file_path); + } + } +} + + +static void persistent_temp_files_document_before_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data)
What I had in mind was using `document-before-save-as` together with `document-save`. In `before-save-as` you'd get the old name, store it to `doc` using `plugin_set_document_data()` and inside the `save-as` handler you'd retrieve it using `plugin_get_document_data()`.
Basically I wanted to get rid of the slightly hacky `get_current_tab_label()` as it relies on the fact that the tooltip contains the path which is not guaranteed to stay this way.
@techee commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
about extending Instantsave - this was an initial idea i tried, but then i decided to do a separate function since unification becomes too complicated both in terms of code and in terms of usage - e.g. instantsave's target directory has a bit different usecase then ours, and there are other complications.
OK, I noticed this post only now, my previous post is a bit outdated then.
I agree the semantics of the target directory are a bit different so maybe not a good idea to merge these two. But I'd still try to unify the two features a bit. First, I think the "Instant save" tab naming is quite horrible (I had to look up in Geany's documentation what it really does). So I'd suggest calling it something like "Empty files" which would contain 2 radio buttons: - Save on creation (which would serve for instant save settings) - this would contain the target directory - Use as Scribble - it would contain the current temp files settings
The settings in each group would be grayed-out as necessary when the other feature is enabled, default filetype combo box could be below and be shared by both.
@LiquidCake pushed 1 commit.
583afefbbbe8d1135085cac8c05ac539406e9393 persistent-temp-files-plugin: code review 2 - fixes
@LiquidCake commented on this pull request.
}
+ else + { + g_remove(locale_file_path); + + ui_set_statusbar(TRUE, _("Empty temp file %s was deleted"), short_filename); + } + + g_free(short_filename); + g_free(locale_file_path); + } + } +} + + +static void persistent_temp_files_document_before_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data)
right, missed that, done
@LiquidCake commented on this pull request.
- }
+ + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX); + + max_temp_file_number = 0; + + foreach_dir(filename, dir) + { + utf8_filename = utils_get_utf8_from_locale(filename); + + if (is_temp_saved_file(utf8_filename)) + { + temp_file_number_str = g_utf8_substring(utf8_filename, temp_file_name_prefix_len, -1); + temp_file_number = atoi(temp_file_number_str); + + if (temp_file_number > max_temp_file_number)
done
@LiquidCake commented on this pull request.
temp_file_number = atoi(temp_file_number_str);
+ + if (temp_file_number > max_temp_file_number) + { + max_temp_file_number = temp_file_number; + } + + g_free(temp_file_number_str); + } + + g_free(utf8_filename); + } + + g_dir_close(dir); + + return g_strdup_printf(_("%s%d"), PERSISTENT_TEMP_FILE_NAME_PREFIX, max_temp_file_number + 1);
done
@LiquidCake commented on this pull request.
+static gchar* create_new_temp_file_name(void)
+{ + GDir *dir; + GError *error = NULL; + gchar *utf8_filename, *temp_file_number_str; + gint temp_file_number, max_temp_file_number; + const gchar *filename; + + dir = g_dir_open(persistent_temp_files_target_dir, 0, &error); + if (dir == NULL) + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Persistent temp files directory not found")); + return NULL; + } + + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX);
fixed
@LiquidCake commented on this pull request.
/* we have to store old/new filename pair inside document data to be able to somehow
+ pass it to document-save callback that is called directly after this one */ + plugin_set_document_data_full(geany_plugin, doc, new_file_path, g_strdup(old_file_path), g_free); + } + + g_free(old_file_name); + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path, *old_file_path; + + new_file_path = DOC_FILENAME(doc); + old_file_path = plugin_get_document_data(geany_plugin, doc, new_file_path);
done
@LiquidCake commented on this pull request.
GtkWidget *page_label = NULL;
+ + get_current_tab_label(&page_label); + + old_file_path = gtk_widget_get_tooltip_text(page_label); + new_file_path = DOC_FILENAME(doc); + + if (old_file_path == NULL) + { + ui_set_statusbar(TRUE, _("plugin error: failed to delete initial temp file " + "('failed to get notebook tab label text')")); + + return; + } + + old_file_name = g_path_get_basename(old_file_path);
done
@LiquidCake commented on this pull request.
- const gchar *filename;
+ + dir = g_dir_open(persistent_temp_files_target_dir, 0, &error); + if (dir == NULL) + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Persistent temp files directory not found")); + return NULL; + } + + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX); + + max_temp_file_number = 0; + + foreach_dir(filename, dir) + { + utf8_filename = utils_get_utf8_from_locale(filename);
removed
@LiquidCake commented on this pull request.
- const gchar *filename;
+ + dir = g_dir_open(persistent_temp_files_target_dir, 0, &error); + if (dir == NULL) + { + dialogs_show_msgbox(GTK_MESSAGE_ERROR, _("Persistent temp files directory not found")); + return NULL; + } + + static glong temp_file_name_prefix_len = strlen(PERSISTENT_TEMP_FILE_NAME_PREFIX); + + max_temp_file_number = 0; + + foreach_dir(filename, dir) + { + utf8_filename = utils_get_utf8_from_locale(filename);
removed
@LiquidCake commented on this pull request.
+ g_free(old_file_name); + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path, *old_file_path; + + new_file_path = DOC_FILENAME(doc); + old_file_path = plugin_get_document_data(geany_plugin, doc, new_file_path); + + if (old_file_path != NULL) + { + gchar *old_file_name = g_path_get_basename(old_file_path);
done
@LiquidCake commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is 'closing all' tabs right now. + * + * @return @c TRUE if the Geany is 'closing all' tabs right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_closing_all_tabs(void)
renamed
@LiquidCake commented on this pull request.
@@ -462,6 +462,18 @@ gboolean main_is_realized(void)
}
+/** + * Checks whether Geany is quitting completely right now. + * + * @return @c TRUE if the Geany is exiting right now or @c FALSE otherwise. + **/ +GEANY_API_SYMBOL +gboolean geany_is_quitting(void)
i guess this is doable. Not sure we necessary want to allow picking default filetype for temp files but maybe it can be useful in some case
So what I would suggest is to have a checkbox with something like "Scribble mode" inside the "Instant save" tab enabling this feature
"Does it save the Geany Scribble window then?" [user who didn't read the docs question]
Whilst I didn't get the "persistent temp files" either and I still don't know what the use-case is beyond persisting unnamed buffers and I don't understand what the reason for saving unnamed buffers, if its unnamed how do I know whats in it to switch to it when they are all named "Untitled"? So I can't suggest anything better than the descriptive "save unnamed buffers".
Whilst I didn't get the "persistent temp files" either and I still don't know what the use-case is beyond persisting unnamed buffers and I don't understand what the reason for saving unnamed buffers is, if its unnamed how do I know whats in it to switch to it when they are all named "Untitled"?
They will get some file name (see the screencast, I personally would suggest names `scribble_1`, `scribble_2`, etc.)
So I can't suggest anything better than the descriptive "save unnamed buffers".
That's not the whole thing the patch does though - it really is some "scribble mode" with additional features: 1. On opening an unnamed buffer, it saves the file (same as instant save) 2. It auto-saves this buffer in user-specified intervals so the user doesn't have to care about saving it 3. When you close the buffer, you are asked if you really want to close it - when you do, the underlying file is deleted so, again, you don't have to care about files here. When you select that you don't want to close it, the open buffer is preserved - with a slight limitation. The limitation is that plugins can't interrupt file closing so what the plugin does it that it reopens the underlying file after it's closed (and of course it doesn't delete it in this case) - since this only happens when the user makes a mistake and tries to close the file by accident, I think it's fine to have it implemented this way. 4. When you use "Save as", the underlying "scribble" file is deleted automatically and only the file under which you saved it is preserved.
Now to why people (https://github.com/geany/geany/issues/905), including me, want to have this feature - the Scribble panel is extremely limited and the basic editor features don't work there (see https://github.com/geany/geany/issues/3558). Having a full-blown Scintilla editor used for Scribble with all editing options and syntax highlighting in a big window would be really nice. And this PR is quite a nice way how to implement it in a pretty much invisible and care-free way for users.
I know the scribble window is very limited, thats why I'm confused by you wanting to call this "scribble".
I know the scribble window is very limited, thats why I'm confused by you wanting to call this "scribble".
Well, it doesn't really have to be called this way, I'd just call it based on what it's supposed to do instead of how it's implemented (i.e. "persistent temp files"). Could be for instance "persistent notepad" and the individual tabs would be `notepad_1`, `notepad_2`, etc.
Ok, at least "notepad" is a term thats not used so far AFAICT so it won't get confused with something else.
@techee requested changes on this pull request.
Looks pretty good, I've just added a few more comments.
There are two things that should still be resolved: 1. The naming of this feature and its placement in preferences 2. How it should behave on project opens/closes.
For (1) my current suggestion is (if others dislike Scribble) "Persistent notebooks" and the corresponding preferences would be merged "Instant save" and this feature under the "Untitled Document Actions". It would look something like this: ``` Untitled Document Behavior
() Default () Instant Save Directory to save files in [entry ] Current description () Persistent Notebook Directory to save persistent notebook files in [entry ] Save interval [entry ] Default filetype [Combo] ``` and things will get greyed-out depending on the selection.
Now to (2), i.e. behavior on project opens/closes. I've been playing with it a little and I find it kind of stupid that persistent files don't survive project opens: 1. One may need to "transfer" some piece of code from one project to another and opening it separately adds extra complexity. 2. There might be more and more "stale" temp files stored as some projects get deleted and unused and these would become inaccessible from the normal UI.
One option could possibly be a new menu item "Open all persistent notebooks" which would open all temp files stored in the target directory but I think even better if these are opened automatically. (And I know, I was the one who suggested it should just rely on Geany's session handling and now I think I was wrong, sorry.)
Ideally this feature should do nothing if all the persistent files are already stored in the session and preserve the active document stored in the session. Only when some persistent files are missing in the current session, those should be opened.
Implementing would be a bit fiddly as Geany doesn't report when it opened a project or the default session but I think doable.
There could be a variable, let's call it `session_opening` indicating whether a session (default or project) is being opened. It would be set to TRUE in these situations: 1. `plugin_init()` - even though the session isn't loading now, the plugin load should open the notebooks upon load. 2. "project-open" signal handler - project session files will be loaded afterwards 3. "project-close" signal handler - project is being closed, defult session will be loaded afterwards
Then the plugin should connect to the "document-activate" signal. When session is being opened, it's only fired for the tab that becomes active once all the session files are loaded so you can be sure it's called only once. In its handler you can check if `session_opening == TRUE` (and reset it back to FALSE). If TRUE, it means it was session loading and not normal tab switching.
Then you can go through the contents of the temp file directory, check if there are open documents for all the files, and if not, open them.
In any case, before spending any more time on this, better to finally ask someone wise ;-). @b4n What do you think about this feature and the possible ways it could be implemented?
@@ -304,6 +314,274 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint
}
+static gboolean is_temp_saved_file(const gchar *file_path_utf8)
Since you already pass the whole path in most cases (I think there's just one case where the directory will have to be added), this function could also check whether the directory corresponds to the configured target directory. Better to be on the save side since closing temp saved files may lead to their deletion.
- ret = gtk_dialog_run(GTK_DIALOG(dialog));
+ + gtk_widget_destroy(dialog); + + return ret; +} + + +static void show_unsaved_dialog_for_persistent_temp_files_tab_closing( + GeanyDocument *doc, const gchar *short_filename) +{ + gchar *msg, *old_file_path, *locale_file_path; + const gchar *msg2; + gint response; + + msg = g_strdup_printf(_("The file '%s' is not saved."), short_filename);
Possibly to be updated later when we agree on some "correct" naming of this feature. But I'd not refer to file here - could be something like "Notebook is not saved".
{
+ /* we have to store old filename inside document data to be able to somehow + pass it to document-save callback that is called directly after this one */ + plugin_set_document_data_full(geany_plugin, doc, "file-name-before-save-as", + g_strdup(old_file_path_utf8), g_free); + } + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path_utf8, *old_file_path_utf8; + + new_file_path_utf8 = DOC_FILENAME(doc); + old_file_path_utf8 = plugin_get_document_data(geany_plugin, doc, "file-name-before-save-as");
Call `plugin_set_document_data(..., "file-name-before-save-as", NULL)` so the previous `old_file_path_utf8` isn't attached to the document (could cause problems when ordinary "save" would think it's "save as").
+ +static void show_unsaved_dialog_for_persistent_temp_files_tab_closing( + GeanyDocument *doc, const gchar *short_filename) +{ + gchar *msg, *old_file_path, *locale_file_path; + const gchar *msg2; + gint response; + + msg = g_strdup_printf(_("The file '%s' is not saved."), short_filename); + msg2 = _("Do you want to save it before closing?"); + + response = run_unsaved_dialog_for_persistent_temp_files_tab_closing(msg, msg2); + g_free(msg); + + locale_file_path = g_strdup(doc->real_path);
The `g_strdup()` and maybe even the whole `locale_file_path` could be removed here and `doc->real_path` could be used instead everywhere.
@LiquidCake pushed 1 commit.
c5eb438483f58875e97f24428ab2c8ab4c1fb60b persistent-temp-files-plugin: code review 3 - fixes
@LiquidCake commented on this pull request.
@@ -304,6 +314,274 @@ static void instantsave_document_new_cb(GObject *obj, GeanyDocument *doc, gpoint
}
+static gboolean is_temp_saved_file(const gchar *file_path_utf8)
added dir check
@LiquidCake commented on this pull request.
{
+ /* we have to store old filename inside document data to be able to somehow + pass it to document-save callback that is called directly after this one */ + plugin_set_document_data_full(geany_plugin, doc, "file-name-before-save-as", + g_strdup(old_file_path_utf8), g_free); + } + } +} + + +static void persistent_temp_files_document_save_cb(GObject *obj, GeanyDocument *doc, gpointer user_data) +{ + gchar *new_file_path_utf8, *old_file_path_utf8; + + new_file_path_utf8 = DOC_FILENAME(doc); + old_file_path_utf8 = plugin_get_document_data(geany_plugin, doc, "file-name-before-save-as");
should not cause issues but you're right, added cleanup
@LiquidCake commented on this pull request.
+ +static void show_unsaved_dialog_for_persistent_temp_files_tab_closing( + GeanyDocument *doc, const gchar *short_filename) +{ + gchar *msg, *old_file_path, *locale_file_path; + const gchar *msg2; + gint response; + + msg = g_strdup_printf(_("The file '%s' is not saved."), short_filename); + msg2 = _("Do you want to save it before closing?"); + + response = run_unsaved_dialog_for_persistent_temp_files_tab_closing(msg, msg2); + g_free(msg); + + locale_file_path = g_strdup(doc->real_path);
strdup is needed because path is changed after save actions is performed below. But i lowered scope of that variable and used real_path where possible
@LiquidCake pushed 1 commit.
16b252d8b4244391470f5380707ce5515bb74ebd persistent-temp-files-plugin: code review 3 - fixes
github-comments@lists.geany.org