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.

git-svn-id: https://geany.svn.sourceforge.net/svnroot/geany/trunk@3484 ea778897-0a13-0410-b9d1-a72fbfd435f5
This commit is contained in:
Enrico Tröger 2009-01-18 18:19:58 +00:00
parent 8c5c5bb27c
commit de3d3b42fb
9 changed files with 268 additions and 66 deletions

View File

@ -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>

1
TODO
View File

@ -30,7 +30,6 @@ Note: features included in brackets have lower priority.
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:

View File

@ -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 \

View File

@ -382,8 +382,7 @@ static void on_save_as_new_tab_toggled(GtkToggleButton *togglebutton, gpointer u
#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 @@ static void handle_save_as(const gchar *utf8_filename, gboolean open_new_tab,
{
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);
}
}

View File

@ -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 @@ static void init_doc_struct(GeanyDocument *new_doc)
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 @@ static void queue_colourise(GeanyDocument *doc)
}
#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 @@ static GeanyDocument *document_create(const gchar *utf8_filename)
return this;
}
/**
* Close the given document.
*
@ -497,6 +589,8 @@ gboolean document_remove_page(guint page_num)
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 @@ GeanyDocument *document_new_file(const gchar *utf8_filename, GeanyFiletype *ft,
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 @@ GeanyDocument *document_new_file(const gchar *utf8_filename, GeanyFiletype *ft,
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 @@ GeanyDocument *document_open_file_full(GeanyDocument *doc, const gchar *filename
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 @@ GeanyDocument *document_open_file_full(GeanyDocument *doc, const gchar *filename
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 @@ gboolean document_reload_file(GeanyDocument *doc, const gchar *forced_enc)
static gboolean document_update_timestamp(GeanyDocument *doc)
{
#ifndef HAVE_GIO
struct stat st;
gchar *locale_filename;
@ -1266,8 +1364,9 @@ static gboolean document_update_timestamp(GeanyDocument *doc)
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 @@ static void replace_header_filename(GeanyDocument *doc)
}
/*
* 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 @@ gboolean document_save_file_as(GeanyDocument *doc, const gchar *utf8_fname)
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 @@ gboolean document_save_file(GeanyDocument *doc, gboolean force)
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 @@ GdkColor *document_get_status_color(GeanyDocument *doc)
{
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 @@ GdkColor *document_get_status_color(GeanyDocument *doc)
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_reload(GeanyDocument *doc)
}
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;
time_t t;
gchar *locale_filename;
gboolean ret = FALSE;
time_t t;
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 */
ret = TRUE; /* file has changed */
default:
break;
}
g_free(locale_filename);
doc->priv->file_disk_status = FILE_OK;
ui_update_tab_status(doc);
return ret;
}

View File

@ -93,10 +93,6 @@ struct GeanyDocument
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_text_changed(GeanyDocument *doc, gboolean changed);
void document_set_filetype(GeanyDocument *doc, GeanyFiletype *type);
void document_rename_file(GeanyDocument *doc, const gchar *new_filename);
GeanyDocument *document_index(gint idx);

View File

@ -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,6 +37,15 @@ enum
UNDO_ACTIONS_MAX
};
typedef enum
{
FILE_OK,
FILE_CHANGED,
FILE_MISSING,
FILE_IGNORE
}
FileDiskStatus;
typedef struct FileEncoding
{
@ -69,7 +79,16 @@ typedef struct GeanyDocumentPrivate
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

View File

@ -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 @@ typedef struct DocumentFuncs
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;

View File

@ -131,7 +131,9 @@ static DocumentFuncs doc_funcs = {
&document_set_text_changed,
&document_set_filetype,
&document_close,
&document_index
&document_index,
&document_save_file_as,
&document_rename_file
};
static EditorFuncs editor_funcs = {