Revision: 3484 http://geany.svn.sourceforge.net/geany/?rev=3484&view=rev Author: eht16 Date: 2009-01-18 18:19:58 +0000 (Sun, 18 Jan 2009)
Log Message: ----------- Add document_save_file_as and document_rename_file to the plugin API. If GIO is available, use GFileMonitor to watch for file disk changes and indicate them immediately using an orange tab label colour. Break plugin ABI for this and the last commits.
Modified Paths: -------------- trunk/ChangeLog trunk/TODO trunk/plugins/geanyfunctions.h trunk/src/dialogs.c trunk/src/document.c trunk/src/document.h trunk/src/documentprivate.h trunk/src/plugindata.h trunk/src/plugins.c
Modified: trunk/ChangeLog =================================================================== --- trunk/ChangeLog 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/ChangeLog 2009-01-18 18:19:58 UTC (rev 3484) @@ -8,6 +8,12 @@ Remove filetype O-Matrix (probably unused for years). * src/keybindings.c, src/keybindings.h: Reorder some keybindings. + * src/dialogs.c, src/document.c, src/document.h, src/documentprivate.h, + src/plugindata.h, src/plugins.c, plugins/geanyfunctions.h: + Add document_save_file_as and document_rename_file to the plugin API. + If GIO is available, use GFileMonitor to watch for file disk changes + and indicate them immediately using an orange tab label colour. + Break plugin ABI for this and the last commits.
2009-01-17 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
Modified: trunk/TODO =================================================================== --- trunk/TODO 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/TODO 2009-01-18 18:19:58 UTC (rev 3484) @@ -30,7 +30,6 @@ target...) o (tango-like icons for the symbol list) o (show autocompletion symbol icons - see SCI_REGISTERIMAGE) - o (GFileMonitor support, if/when GIO gets merged with GLib)
1.0:
Modified: trunk/plugins/geanyfunctions.h =================================================================== --- trunk/plugins/geanyfunctions.h 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/plugins/geanyfunctions.h 2009-01-18 18:19:58 UTC (rev 3484) @@ -40,6 +40,10 @@ geany_functions->p_document->close #define document_index \ geany_functions->p_document->index +#define document_save_file_as \ + geany_functions->p_document->save_file_as +#define document_rename_file \ + geany_functions->p_document->rename_file #define editor_get_indent_prefs \ geany_functions->p_editor->get_indent_prefs #define editor_create_widget \
Modified: trunk/src/dialogs.c =================================================================== --- trunk/src/dialogs.c 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/dialogs.c 2009-01-18 18:19:58 UTC (rev 3484) @@ -382,8 +382,7 @@
#if ! GEANY_USE_WIN32_DIALOG -static void handle_save_as(const gchar *utf8_filename, gboolean open_new_tab, - gboolean rename_file) +static void handle_save_as(const gchar *utf8_filename, gboolean open_new_tab, gboolean rename_file) { GeanyDocument *doc = document_get_current();
@@ -400,22 +399,16 @@ { if (rename_file) { - gchar *old_filename = utils_get_locale_from_utf8(doc->file_name); - gchar *new_filename = utils_get_locale_from_utf8(utf8_filename); - - g_rename(old_filename, new_filename); - g_free(old_filename); - g_free(new_filename); + document_rename_file(doc, utf8_filename); } /* create a new tm_source_file object otherwise tagmanager won't work correctly */ tm_workspace_remove_object(doc->tm_file, TRUE, TRUE); doc->tm_file = NULL; } document_save_file_as(doc, utf8_filename); - }
- if (! open_new_tab) build_menu_update(doc); + } }
Modified: trunk/src/document.c =================================================================== --- trunk/src/document.c 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/document.c 2009-01-18 18:19:58 UTC (rev 3484) @@ -47,6 +47,10 @@ /* gstdio.h also includes sys/stat.h */ #include <glib/gstdio.h>
+#ifdef HAVE_GIO +# include <gio/gio.h> +#endif + #include "document.h" #include "documentprivate.h" #include "filetypes.h" @@ -337,9 +341,7 @@ new_doc->encoding = NULL; new_doc->has_bom = FALSE; new_doc->editor = NULL; - new_doc->mtime = 0; new_doc->changed = FALSE; - new_doc->last_check = time(NULL); new_doc->real_path = NULL;
new_doc->priv = g_new0(GeanyDocumentPrivate, 1); @@ -384,6 +386,97 @@ }
+#ifdef HAVE_GIO +static void file_monitor_changed_cb(G_GNUC_UNUSED GFileMonitor *monitor, G_GNUC_UNUSED GFile *file, + G_GNUC_UNUSED GFile *other_file, GFileMonitorEvent event, GeanyDocument *doc) +{ + if (file_prefs.disk_check_timeout == 0) + return; + + switch (event) + { + case G_FILE_MONITOR_EVENT_CHANGED: + { + if (doc->priv->file_disk_status == FILE_IGNORE) + { /* ignore this change completely, used after saving a file */ + doc->priv->file_disk_status = FILE_OK; + return; + } + doc->priv->file_disk_status = FILE_CHANGED; + break; + } + case G_FILE_MONITOR_EVENT_DELETED: + { + doc->priv->file_disk_status = FILE_MISSING; + break; + } + /* ignore */ + case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: + case G_FILE_MONITOR_EVENT_UNMOUNTED: + /* we ignore 'created' for now since it causes trouble when renaming files */ + case G_FILE_MONITOR_EVENT_CREATED: + return; + } + + if (doc->priv->file_disk_status != FILE_OK) + { + ui_update_tab_status(doc); + } +} +#endif + + +void document_stop_file_monitoring(GeanyDocument *doc) +{ + g_return_if_fail(doc != NULL); + + if (doc->priv->monitor != NULL) + { + g_object_unref(doc->priv->monitor); + doc->priv->monitor = NULL; + } +} + + +static void file_monitor_setup(GeanyDocument *doc) +{ + g_return_if_fail(doc != NULL); + /* Disable file monitoring completely for remote files (i.e. remote GIO files) as GFileMonitor + * doesn't work at all for remote files and legacy polling is too slow. */ + if (! doc->priv->is_remote) + { +#ifdef HAVE_GIO + gchar *locale_filename; + GFile *file; + + /* stop any previous monitoring */ + document_stop_file_monitoring(doc); + + locale_filename = utils_get_locale_from_utf8(doc->file_name); + if (locale_filename != NULL && g_file_test(locale_filename, G_FILE_TEST_EXISTS)) + { + /* get a file monitor and connect to the 'changed' signal */ + file = g_file_new_for_path(locale_filename); + doc->priv->monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL); + g_signal_connect(doc->priv->monitor, "changed", G_CALLBACK(file_monitor_changed_cb), doc); + + /* we set the rate limit according to the GUI pref but it's most probably not used */ + g_file_monitor_set_rate_limit(doc->priv->monitor, file_prefs.disk_check_timeout * 1000); + + g_object_unref(file); + } + g_free(locale_filename); +#else + doc->priv->last_check = time(NULL); + doc->priv->mtime = 0; +#endif + } + doc->priv->file_disk_status = FILE_OK; +} + + /* Creates a new document and editor, adding a tab in the notebook. * @return The created document */ static GeanyDocument *document_create(const gchar *utf8_filename) @@ -438,7 +531,6 @@ return this; }
- /** * Close the given document. * @@ -497,6 +589,8 @@ editor_destroy(doc->editor); doc->editor = NULL;
+ document_stop_file_monitoring(doc); + doc->is_valid = FALSE; doc->file_name = NULL; doc->real_path = NULL; @@ -571,7 +665,7 @@ sci_set_undo_collection(doc->editor->sci, TRUE); sci_empty_undo_buffer(doc->editor->sci);
- doc->mtime = time(NULL); + doc->priv->mtime = time(NULL);
doc->encoding = g_strdup(encodings[file_prefs.default_new_encoding].charset); /* store the opened encoding for undo/redo */ @@ -593,6 +687,8 @@ sci_set_line_numbers(doc->editor->sci, editor_prefs.show_linenumber_margin, 0); sci_goto_pos(doc->editor->sci, 0, TRUE);
+ file_monitor_setup(doc); + /* "the" SCI signal (connect after initial setup(i.e. adding text)) */ g_signal_connect(doc->editor->sci, "sci-notify", G_CALLBACK(editor_sci_notify_cb), doc->editor);
@@ -1082,6 +1178,7 @@ g_return_val_if_fail(doc != NULL, NULL); /* really should not happen */
doc->priv->is_remote = utils_is_remote_path(locale_filename); + file_monitor_setup(doc); }
sci_set_undo_collection(doc->editor->sci, FALSE); /* avoid creation of an undo action */ @@ -1099,7 +1196,7 @@
sci_set_undo_collection(doc->editor->sci, TRUE);
- doc->mtime = filedata.mtime; /* get the modification time from file and keep it */ + doc->priv->mtime = filedata.mtime; /* get the modification time from file and keep it */ g_free(doc->encoding); /* if reloading, free old encoding */ doc->encoding = filedata.enc; doc->has_bom = filedata.bom; @@ -1252,6 +1349,7 @@
static gboolean document_update_timestamp(GeanyDocument *doc) { +#ifndef HAVE_GIO struct stat st; gchar *locale_filename;
@@ -1266,8 +1364,9 @@ return FALSE; }
- doc->mtime = st.st_mtime; /* get the modification time from file and keep it */ + doc->priv->mtime = st.st_mtime; /* get the modification time from file and keep it */ g_free(locale_filename); +#endif return TRUE; }
@@ -1326,22 +1425,47 @@ }
-/* - * Save the %document, detecting the filetype. +/** + * Renames the file in @a doc to @a new_filename. Only the file on disk is actually renamed, + * you still have to call @ref document_save_file_as() to change the @a doc object. + * It also stops monitoring for file changes to prevent receiving too many file change events + * while renaming. File monitoring is setup again in @ref document_save_file_as(). * + * @param doc The current document which should be renamed. + * @param new_filename The new filename in UTF-8 encoding. + */ +void document_rename_file(GeanyDocument *doc, const gchar *new_filename) +{ + gchar *old_locale_filename = utils_get_locale_from_utf8(doc->file_name); + gchar *new_locale_filename = utils_get_locale_from_utf8(new_filename); + + /* stop file monitoring to avoid getting events for deleting/creating files, + * it's re-setup in document_save_file_as() */ + document_stop_file_monitoring(doc); + + g_rename(old_locale_filename, new_locale_filename); + + g_free(old_locale_filename); + g_free(new_locale_filename); +} + + +/** + * Saves the document, detecting the filetype. + * * @param doc The document for the file to save. * @param utf8_fname The new name for the document, in UTF-8, or NULL. * @return @c TRUE if the file was saved or @c FALSE if the file could not be saved. + * * @see document_save_file(). */ gboolean document_save_file_as(GeanyDocument *doc, const gchar *utf8_fname) { gboolean ret;
- if (doc == NULL) - return FALSE; + g_return_val_if_fail(doc != NULL, FALSE);
- if (utf8_fname) + if (utf8_fname != NULL) { g_free(doc->file_name); doc->file_name = g_strdup(utf8_fname); @@ -1365,6 +1489,12 @@ replace_header_filename(doc);
ret = document_save_file(doc, TRUE); + + /* file monitoring support, add file monitoring after the file has been saved + * to ignore any earlier events */ + file_monitor_setup(doc); + doc->priv->file_disk_status = FILE_IGNORE; + if (ret) ui_add_recent_file(doc->file_name); return ret; @@ -1551,6 +1681,9 @@ err = write_data_to_disk(doc, data, len); g_free(data);
+ /* ignore file changed notification after writing the file */ + doc->priv->file_disk_status = FILE_IGNORE; + if (err != 0) { ui_set_statusbar(TRUE, _("Error saving file (%s)."), g_strerror(err)); @@ -2472,6 +2605,7 @@ { static GdkColor red = {0, 0xFFFF, 0, 0}; static GdkColor green = {0, 0, 0x7FFF, 0}; + static GdkColor orange = {0, 0xFFFF, 0x7FFF, 0}; GdkColor *color = NULL;
if (doc == NULL) @@ -2479,6 +2613,11 @@
if (doc->changed) color = &red; + else if (doc->priv->file_disk_status == FILE_MISSING || + doc->priv->file_disk_status == FILE_CHANGED) + { + color = &orange; + } else if (doc->readonly) color = &green;
@@ -2606,62 +2745,103 @@ }
+static gboolean check_resave_missing_file(GeanyDocument *doc) +{ + gboolean want_reload = FALSE; + + /* file is missing - set unsaved state */ + document_set_text_changed(doc, TRUE); + /* don't prompt more than once */ + setptr(doc->real_path, NULL); + + if (dialogs_show_question_full(NULL, GTK_STOCK_SAVE, GTK_STOCK_CANCEL, + _("Try to resave the file?"), + _("File "%s" was not found on disk!"), doc->file_name)) + { + dialogs_show_save_as(); + want_reload = TRUE; + } + return want_reload; +} + + +static time_t check_disk_status_real(GeanyDocument *doc, gboolean force) +{ + time_t t = 0; +#ifndef HAVE_GIO + struct stat st; + gchar *locale_filename; + + t = time(NULL); + + if (! force && doc->priv->last_check > (t - file_prefs.disk_check_timeout)) + return 0; + + doc->priv->last_check = t; + + locale_filename = utils_get_locale_from_utf8(doc->file_name); + if (g_stat(locale_filename, &st) != 0) + { + doc->priv->file_disk_status = FILE_MISSING; + return 0; + } + else if (doc->priv->mtime > t || st.st_mtime > t) + { + g_warning("%s: Something is wrong with the time stamps.", __func__); + } + else if (doc->priv->mtime < st.st_mtime) + { + doc->priv->file_disk_status = FILE_CHANGED; + /* return st.st_mtime to set it after the file has been possibly reloaded */ + t = st.st_mtime; + } + g_free(locale_filename); +#endif + return t; +} + + /* Set force to force a disk check, otherwise it is ignored if there was a check * in the last file_prefs.disk_check_timeout seconds. * @return @c TRUE if the file has changed. */ gboolean document_check_disk_status(GeanyDocument *doc, gboolean force) { - struct stat st; + gboolean ret = FALSE; time_t t; - gchar *locale_filename; - gboolean ret = FALSE;
if (file_prefs.disk_check_timeout == 0) return FALSE; if (doc == NULL) return FALSE; /* ignore documents that have never been saved to disk */ - if (doc->real_path == NULL) return FALSE; - - t = time(NULL); - - if (! force && doc->last_check > (t - file_prefs.disk_check_timeout)) + if (doc->real_path == NULL) return FALSE;
- doc->last_check = t; + /* check the file's mtime in case we don't have GIO support, otherwise this is a no-op */ + t = check_disk_status_real(doc, force);
- locale_filename = utils_get_locale_from_utf8(doc->file_name); - if (g_stat(locale_filename, &st) != 0) + switch (doc->priv->file_disk_status) { - /* file is missing - set unsaved state */ - document_set_text_changed(doc, TRUE); - /* don't prompt more than once */ - setptr(doc->real_path, NULL); - - if (dialogs_show_question_full(NULL, GTK_STOCK_SAVE, GTK_STOCK_CANCEL, - _("Try to resave the file?"), - _("File "%s" was not found on disk!"), doc->file_name)) + case FILE_CHANGED: { - dialogs_show_save_as(); + check_reload(doc); + doc->priv->mtime = t; + ret = TRUE; + break; } - } - else if (doc->mtime > t || st.st_mtime > t) - { - geany_debug("Strange: Something is wrong with the time stamps."); - } - else if (doc->mtime < st.st_mtime) - { - if (check_reload(doc)) + case FILE_MISSING: { - /* Update the modification time */ - doc->mtime = st.st_mtime; + check_resave_missing_file(doc); + ret = TRUE; + break; } - else - doc->mtime = st.st_mtime; /* Ignore this change on disk completely */ + default: + break; + }
- ret = TRUE; /* file has changed */ - } - g_free(locale_filename); + doc->priv->file_disk_status = FILE_OK; + ui_update_tab_status(doc); + return ret; }
Modified: trunk/src/document.h =================================================================== --- trunk/src/document.h 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/document.h 2009-01-18 18:19:58 UTC (rev 3484) @@ -93,10 +93,6 @@ gboolean readonly; /** Whether this %document has been changed since it was last saved. */ gboolean changed; - /** Time of the last disk check. */ - time_t last_check; - /** Modification time of this %document on disk. */ - time_t mtime; /** The link-dereferenced, locale-encoded file name. * If non-NULL, this indicates the file once existed on disk (not just as an * unsaved document with a filename set). @@ -157,6 +153,7 @@
void document_set_filetype(GeanyDocument *doc, GeanyFiletype *type);
+void document_rename_file(GeanyDocument *doc, const gchar *new_filename);
GeanyDocument *document_index(gint idx);
Modified: trunk/src/documentprivate.h =================================================================== --- trunk/src/documentprivate.h 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/documentprivate.h 2009-01-18 18:19:58 UTC (rev 3484) @@ -26,6 +26,7 @@ #ifndef GEANY_DOCUMENT_PRIVATE_H #define GEANY_DOCUMENT_PRIVATE_H
+ /* available UNDO actions, UNDO_SCINTILLA is a pseudo action to trigger Scintilla's * undo management */ enum @@ -36,7 +37,16 @@ UNDO_ACTIONS_MAX };
+typedef enum +{ + FILE_OK, + FILE_CHANGED, + FILE_MISSING, + FILE_IGNORE +} +FileDiskStatus;
+ typedef struct FileEncoding { gchar *encoding; @@ -69,7 +79,16 @@ gint symbol_list_sort_mode; /* indicates whether a file is on a remote filesystem, works only with GIO/GVFS */ gboolean is_remote; - } + /* File status on disk of the document, can be 'FILE_CHANGED', 'FILE_MISSING' (deleted) or + * 'FILE_OK' if there are no known changes */ + FileDiskStatus file_disk_status; + /* Reference to a GFileMonitor object, only used when GIO file monitoring is used. */ + gpointer monitor; + /* Time of the last disk check, only used when legacy file monitoring is used. */ + time_t last_check; + /* Modification time of the document on disk, only used when legacy file monitoring is used. */ + time_t mtime; +} GeanyDocumentPrivate;
#endif
Modified: trunk/src/plugindata.h =================================================================== --- trunk/src/plugindata.h 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/plugindata.h 2009-01-18 18:19:58 UTC (rev 3484) @@ -45,13 +45,13 @@ enum { /** The Application Programming Interface (API) version, incremented * whenever any plugin data types are modified or appended to. */ - GEANY_API_VERSION = 125, + GEANY_API_VERSION = 126,
/** The Application Binary Interface (ABI) version, incremented whenever * existing fields in the plugin data types have to be changed or reordered. */ /* This should usually stay the same if fields are only appended, assuming only pointers to * structs and not structs themselves are declared by plugins. */ - GEANY_ABI_VERSION = 56 + GEANY_ABI_VERSION = 57 };
/** Check the plugin can be loaded by Geany. @@ -248,8 +248,10 @@ void (*set_encoding) (struct GeanyDocument *doc, const gchar *new_encoding); void (*set_text_changed) (struct GeanyDocument *doc, gboolean changed); void (*set_filetype) (struct GeanyDocument *doc, struct GeanyFiletype *type); - gboolean (*close) (GeanyDocument *doc); + gboolean (*close) (struct GeanyDocument *doc); struct GeanyDocument* (*index)(gint idx); + gboolean (*save_file_as) (struct GeanyDocument *doc, const gchar *utf8_fname); + void (*rename_file) (struct GeanyDocument *doc, const gchar *new_filename); } DocumentFuncs;
Modified: trunk/src/plugins.c =================================================================== --- trunk/src/plugins.c 2009-01-18 18:19:09 UTC (rev 3483) +++ trunk/src/plugins.c 2009-01-18 18:19:58 UTC (rev 3484) @@ -131,7 +131,9 @@ &document_set_text_changed, &document_set_filetype, &document_close, - &document_index + &document_index, + &document_save_file_as, + &document_rename_file };
static EditorFuncs editor_funcs = {
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.