/* * mooedit-fileops.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit is free software; you can * redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define MOOEDIT_COMPILATION #include "mooedit/mooedit-private.h" #include "mooedit/mooeditor-impl.h" #include "mooedit/mooedit-fileops.h" #include "mooedit/mooeditdialogs.h" #include "mooedit/mootextbuffer.h" #include "mooedit/mooeditprefs.h" #include "mooutils/moofileicon.h" #include "mooutils/moofilewatch.h" #include "mooutils/mooencodings.h" #include "mooutils/mooi18n.h" #include "mooutils/mootype-macros.h" #include "mooutils/mooutils-messages.h" #include "mooutils/mooutils-fs.h" #include "mooutils/moocompat.h" #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #if GLIB_CHECK_VERSION(2,6,0) # include #endif #undef LOAD_BINARY #define ENCODING_LOCALE "LOCALE" #ifndef O_BINARY #define O_BINARY 0 #endif MOO_DEFINE_QUARK (MooEditFileErrorQuark, _moo_edit_file_error_quark) static GSList *UNTITLED = NULL; static GHashTable *UNTITLED_NO = NULL; static void block_buffer_signals (MooEdit *edit); static void unblock_buffer_signals (MooEdit *edit); static void check_file_status (MooEdit *edit); static void file_modified_on_disk (MooEdit *edit); static void file_deleted (MooEdit *edit); static void add_status (MooEdit *edit, MooEditStatus s); static gboolean moo_edit_load_local (MooEdit *edit, GFile *file, const char *encoding, const char *cached_encoding, GError **error); static gboolean moo_edit_reload_local (MooEdit *edit, const char *encoding, GError **error); static gboolean moo_edit_save_local (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error); static gboolean moo_edit_save_copy_local (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error); static void _moo_edit_start_file_watch (MooEdit *edit); gboolean _moo_signal_accumulator_save_response (G_GNUC_UNUSED GSignalInvocationHint *ihint, GValue *return_accu, const GValue *handler_return) { int ret = g_value_get_enum (handler_return); if (ret == MOO_EDIT_SAVE_RESPONSE_CANCEL) { g_value_set_enum (return_accu, MOO_EDIT_SAVE_RESPONSE_CANCEL); return FALSE; } else { g_value_set_enum (return_accu, MOO_EDIT_SAVE_RESPONSE_CONTINUE); if (ret != MOO_EDIT_SAVE_RESPONSE_CONTINUE) moo_critical ("invalid save response value %d", ret); return TRUE; } } static const char * normalize_encoding (const char *encoding, gboolean for_save) { if (!encoding || !encoding[0] || !strcmp (encoding, MOO_ENCODING_AUTO)) encoding = for_save ? MOO_ENCODING_UTF8 : NULL; return encoding; } gboolean _moo_edit_file_is_new (GFile *file) { gboolean is_new; char *filename; moo_return_val_if_fail (G_IS_FILE (file), FALSE); filename = g_file_get_path (file); moo_return_val_if_fail (filename != NULL, FALSE); is_new = !g_file_test (filename, G_FILE_TEST_EXISTS); g_free (filename); return is_new; } gboolean _moo_edit_load_file (MooEdit *edit, GFile *file, const char *encoding, const char *cached_encoding, GError **error) { char *encoding_copy; char *cached_encoding_copy; gboolean result; GError *error_here = NULL; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); encoding_copy = g_strdup (normalize_encoding (encoding, FALSE)); cached_encoding_copy = cached_encoding ? g_strdup (normalize_encoding (cached_encoding, FALSE)) : NULL; result = moo_edit_load_local (edit, file, encoding_copy, cached_encoding_copy, &error_here); if (error_here) g_propagate_error (error, error_here); g_free (encoding_copy); return result; } gboolean _moo_edit_reload_file (MooEdit *edit, const char *encoding, GError **error) { GError *error_here = NULL; gboolean result; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); result = moo_edit_reload_local (edit, encoding, &error_here); if (error_here) g_propagate_error (error, error_here); return result; } gboolean _moo_edit_save_file (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error) { GError *error_here = NULL; char *encoding_copy; gboolean result; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); encoding_copy = g_strdup (normalize_encoding (encoding, TRUE)); result = moo_edit_save_local (edit, file, encoding_copy, flags, &error_here); if (error_here) g_propagate_error (error, error_here); g_free (encoding_copy); return result; } gboolean _moo_edit_save_file_copy (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error) { char *encoding_copy; gboolean result; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); encoding_copy = g_strdup (normalize_encoding (encoding, TRUE)); result = moo_edit_save_copy_local (edit, file, encoding_copy, flags, error); g_free (encoding_copy); return result; } static void set_encoding_error (GError **error) { GError *tmp_error = NULL; g_set_error (&tmp_error, MOO_EDIT_FILE_ERROR, MOO_EDIT_FILE_ERROR_ENCODING, "%s", *error ? (*error)->message : "ERROR"); g_clear_error (error); g_propagate_error (error, tmp_error); } MooLineEndType moo_edit_get_line_end_type (MooEdit *edit) { moo_return_val_if_fail (MOO_IS_EDIT (edit), MOO_LE_DEFAULT); if (edit->priv->line_end_type == MOO_LE_NONE) return MOO_LE_DEFAULT; else return edit->priv->line_end_type; } static void moo_edit_set_line_end_type_full (MooEdit *edit, MooLineEndType le, gboolean quiet) { moo_return_if_fail (MOO_IS_EDIT (edit)); moo_return_if_fail (le > 0); if (edit->priv->line_end_type != le) { edit->priv->line_end_type = le; if (!quiet) g_object_notify (G_OBJECT (edit), "line-end-type"); } } void moo_edit_set_line_end_type (MooEdit *edit, MooLineEndType le) { moo_return_if_fail (MOO_IS_EDIT (edit)); moo_edit_set_line_end_type_full (edit, le, FALSE); } /***************************************************************************/ /* File loading */ typedef enum { SUCCESS, ERROR_FILE, ERROR_ENCODING } LoadResult; static LoadResult do_load (MooEdit *edit, GFile *file, const char *encoding, GError **error); #ifdef LOAD_BINARY static LoadResult load_binary (MooEdit *edit, const char *file, GError **error); #endif static GSList * get_encodings (void) { const char *encodings; char **raw, **p; GSList *result; encodings = moo_prefs_get_string (moo_edit_setting (MOO_EDIT_PREFS_ENCODINGS)); if (!encodings || !encodings[0]) encodings = _moo_get_default_encodings (); result = NULL; raw = g_strsplit (encodings, ",", 0); for (p = raw; p && *p; ++p) { const char *enc; if (!g_ascii_strcasecmp (*p, ENCODING_LOCALE)) { if (g_get_charset (&enc)) enc = "UTF-8"; } else { enc = *p; } if (!g_slist_find_custom (result, enc, (GCompareFunc) g_ascii_strcasecmp)) result = g_slist_prepend (result, g_strdup (enc)); } if (!result) { g_critical ("%s: oops", G_STRLOC); result = g_slist_prepend (NULL, g_strdup ("UTF-8")); } g_strfreev (raw); return g_slist_reverse (result); } static LoadResult try_load (MooEdit *edit, GFile *file, const char *encoding, GError **error) { MooEditView *view; GtkTextBuffer *buffer; gboolean enable_highlight; LoadResult result = ERROR_FILE; moo_return_val_if_fail (MOO_IS_EDIT (edit), result); moo_return_val_if_fail (G_IS_FILE (file), result); moo_return_val_if_fail (encoding && encoding[0], result); view = moo_edit_get_view (edit); buffer = moo_edit_get_buffer (edit); gtk_text_buffer_set_text (buffer, "", 0); g_object_get (view, "enable-highlight", &enable_highlight, (char*) 0); g_object_set (view, "enable-highlight", FALSE, (char*) 0); result = do_load (edit, file, encoding, error); g_object_set (view, "enable-highlight", enable_highlight, (char*) 0); return result; } static gboolean check_regular (GFile *file, GError **error) { GFileInfo *info; GFileType type; gboolean retval = TRUE; if (!g_file_is_native (file)) return TRUE; if (!(info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, (GFileQueryInfoFlags) 0, NULL, NULL))) return TRUE; type = g_file_info_get_file_type (info); if (type != G_FILE_TYPE_REGULAR && type != G_FILE_TYPE_UNKNOWN) { g_set_error (error, MOO_EDIT_FILE_ERROR, MOO_EDIT_FILE_ERROR_FAILED, "%s", D_("Not a regular file", "glib20")); retval = FALSE; } g_object_unref (info); return retval; } static gboolean moo_edit_load_local (MooEdit *edit, GFile *file, const char *encoding, const char *cached_encoding, GError **error) { GtkTextIter start; GtkTextBuffer *buffer; MooTextView *view; gboolean undo; LoadResult result = ERROR_FILE; char *freeme = NULL; MooLineEndType saved_le; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); if (!check_regular (file, error)) return FALSE; if (moo_edit_is_empty (edit)) undo = FALSE; else undo = TRUE; view = MOO_TEXT_VIEW (moo_edit_get_view (edit)); buffer = moo_edit_get_buffer (edit); block_buffer_signals (edit); if (undo) gtk_text_buffer_begin_user_action (buffer); else moo_text_view_begin_not_undoable_action (view); moo_text_buffer_begin_non_interactive_action (MOO_TEXT_BUFFER (buffer)); saved_le = edit->priv->line_end_type; if (!encoding) { GSList *encodings; encodings = get_encodings (); if (cached_encoding) encodings = g_slist_prepend (encodings, g_strdup (cached_encoding)); result = ERROR_ENCODING; while (encodings) { char *enc; enc = (char*) encodings->data; encodings = g_slist_delete_link (encodings, encodings); g_clear_error (error); result = try_load (edit, file, enc, error); if (result != ERROR_ENCODING) { encoding = freeme = enc; break; } g_free (enc); } g_slist_foreach (encodings, (GFunc) g_free, NULL); g_slist_free (encodings); } else { result = try_load (edit, file, encoding, error); } #ifdef LOAD_BINARY if (result == ERROR_ENCODING) { g_clear_error (error); result = load_binary (edit, file, &statbuf, error); } #endif if (result == ERROR_ENCODING) set_encoding_error (error); if (result != SUCCESS) gtk_text_buffer_set_text (buffer, "", 0); unblock_buffer_signals (edit); if (result == SUCCESS) { /* XXX */ gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_place_cursor (buffer, &start); edit->priv->status = (MooEditStatus) 0; moo_edit_set_modified (edit, FALSE); _moo_edit_set_file (edit, file, encoding); if (edit->priv->line_end_type != saved_le) g_object_notify (G_OBJECT (edit), "line-end-type"); _moo_edit_start_file_watch (edit); } else { _moo_edit_stop_file_watch (edit); } if (undo) gtk_text_buffer_end_user_action (buffer); else moo_text_view_end_not_undoable_action (view); moo_text_buffer_end_non_interactive_action (MOO_TEXT_BUFFER (buffer)); g_free (freeme); return result == SUCCESS; } static LoadResult do_load (MooEdit *edit, GFile *gfile, const char *encoding, GError **error) { GIOChannel *file = NULL; GIOStatus status; GtkTextBuffer *buffer; MooLineEndType le = MOO_LE_NONE; GString *text = NULL; char *line = NULL; LoadResult result = ERROR_FILE; char *path; moo_return_val_if_fail (G_IS_FILE (gfile), ERROR_FILE); moo_return_val_if_fail (encoding != NULL, ERROR_FILE); if (!(path = g_file_get_path (gfile))) { g_set_error (error, MOO_EDIT_FILE_ERROR, MOO_EDIT_FILE_ERROR_NOT_IMPLEMENTED, "Loading remote files is not implemented"); return ERROR_FILE; } file = g_io_channel_new_file (path, "r", error); g_free (path); if (!file) return ERROR_FILE; if (g_io_channel_set_encoding (file, encoding, error) != G_IO_STATUS_NORMAL) { result = ERROR_ENCODING; goto error_out; } text = g_string_new (NULL); buffer = moo_edit_get_buffer (edit); while (TRUE) { gboolean insert_line_term = FALSE; gsize len, line_term_pos; MooLineEndType le_here = MOO_LE_NONE; status = g_io_channel_read_line (file, &line, &len, &line_term_pos, error); if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_EOF) { if ((*error)->domain == G_CONVERT_ERROR) result = ERROR_ENCODING; else result = ERROR_FILE; goto error_out; } if (line) { if (!g_utf8_validate (line, len, NULL)) { result = ERROR_ENCODING; g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, "Invalid UTF8 data read from file"); goto error_out; } if (line_term_pos != len) { char *line_term = line + line_term_pos; insert_line_term = TRUE; len = line_term_pos; if (!strcmp ("\xe2\x80\xa9", line_term)) { insert_line_term = FALSE; len = len + 3; } else if (!strcmp (line_term, "\r")) { le_here = MOO_LE_MAC; } else if (!strcmp (line_term, "\n")) { le_here = MOO_LE_UNIX; } else if (!strcmp (line_term, "\r\n")) { le_here = MOO_LE_WIN32; } if (le_here) { if (le && le != le_here) le = MOO_LE_MIX; else le = le_here; } } g_string_append_len (text, line, len); if (insert_line_term) g_string_append_c (text, '\n'); #define MAX_TEXT_BUF_LEN 1000000 if (text->len > MAX_TEXT_BUF_LEN) { gtk_text_buffer_insert_at_cursor (buffer, text->str, text->len); g_string_truncate (text, 0); } #undef MAX_TEXT_BUF_LEN g_free (line); } if (status == G_IO_STATUS_EOF) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); break; } } if (text->len) gtk_text_buffer_insert_at_cursor (buffer, text->str, text->len); switch (edit->priv->line_end_type) { case MOO_LE_MIX: break; default: if (le != MOO_LE_NONE) moo_edit_set_line_end_type_full (edit, le, TRUE); break; } g_string_free (text, TRUE); g_clear_error (error); return SUCCESS; error_out: if (text) g_string_free (text, TRUE); if (file) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); } g_free (line); return result; } #ifdef LOAD_BINARY #define TEXT_BUF_LEN (1 << 16) #define REPLACEMENT_CHAR '\1' #define LINE_LEN 80 static LoadResult load_binary (MooEdit *edit, const char *filename, GError **error) { GIOChannel *file = NULL; GIOStatus status; GtkTextBuffer *buffer; moo_return_val_if_fail (filename != NULL, FALSE); buffer = moo_edit_get_buffer (edit); file = g_io_channel_new_file (filename, "r", error); if (!file) return ERROR_FILE; g_io_channel_set_encoding (file, NULL, NULL); while (TRUE) { gsize len, line_len; char buf[TEXT_BUF_LEN]; status = g_io_channel_read_chars (file, buf, TEXT_BUF_LEN, &len, error); if (status != G_IO_STATUS_NORMAL && status != G_IO_STATUS_EOF) goto error_out; if (len) { guint i; for (i = 0, line_len = 0; i < len; ++i, ++line_len) { if (buf[i] && !(buf[i] & 0x80)) continue; if (line_len > LINE_LEN) { buf[i] = '\n'; line_len = 0; } else { buf[i] = REPLACEMENT_CHAR; } } gtk_text_buffer_insert_at_cursor (buffer, buf, len); } if (status == G_IO_STATUS_EOF) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); break; } } g_clear_error (error); return SUCCESS; error_out: if (file) { g_io_channel_shutdown (file, TRUE, NULL); g_io_channel_unref (file); } return ERROR_FILE; } #undef TEXT_BUF_LEN #undef REPLACEMENT_CHAR #undef LINE_LEN #endif /* XXX */ static gboolean moo_edit_reload_local (MooEdit *edit, const char *encoding, GError **error) { GtkTextIter start, end; GtkTextBuffer *buffer; gboolean result, enable_highlight; GFile *file; MooEditView *view; file = moo_edit_get_file (edit); moo_return_val_if_fail (G_IS_FILE (file), FALSE); view = moo_edit_get_view (edit); buffer = moo_edit_get_buffer (edit); block_buffer_signals (edit); gtk_text_buffer_begin_user_action (buffer); gtk_text_buffer_get_bounds (buffer, &start, &end); gtk_text_buffer_delete (buffer, &start, &end); g_object_get (view, "enable-highlight", &enable_highlight, (char*) 0); g_object_set (view, "enable-highlight", FALSE, (char*) 0); result = _moo_edit_load_file (edit, file, encoding ? encoding : edit->priv->encoding, NULL, error); g_object_set (view, "enable-highlight", enable_highlight, (char*) 0); gtk_text_buffer_end_user_action (buffer); unblock_buffer_signals (edit); if (result) { edit->priv->status = (MooEditStatus) 0; moo_edit_set_modified (edit, FALSE); _moo_edit_start_file_watch (edit); g_clear_error (error); } g_object_unref (file); return result; } /***************************************************************************/ /* File saving */ static char * get_contents_with_fixed_line_end (GtkTextBuffer *buffer, const char *le, guint le_len) { GtkTextIter line_start; GString *contents; contents = g_string_new (NULL); gtk_text_buffer_get_start_iter (buffer, &line_start); do { GtkTextIter line_end = line_start; if (!gtk_text_iter_ends_line (&line_start)) { char *line; gsize len; gtk_text_iter_forward_to_line_end (&line_end); line = gtk_text_buffer_get_text (buffer, &line_start, &line_end, TRUE); len = strlen (line); g_string_append_len (contents, line, len); g_free (line); } if (!gtk_text_iter_is_end (&line_end)) g_string_append_len (contents, le, le_len); } while (gtk_text_iter_forward_line (&line_start)); return g_string_free (contents, FALSE); } static char * get_contents (MooEdit *edit) { gboolean normalize_le = FALSE; const char *le = "\n"; gsize le_len = 1; MooLineEndType line_end_type; GtkTextBuffer *buffer; char *contents; line_end_type = edit->priv->line_end_type; if (!line_end_type) line_end_type = MOO_LE_DEFAULT; switch (line_end_type) { case MOO_LE_UNIX: normalize_le = TRUE; le = "\n"; le_len = 1; break; case MOO_LE_WIN32: normalize_le = TRUE; le = "\r\n"; le_len = 2; break; case MOO_LE_MAC: normalize_le = TRUE; le = "\r"; le_len = 1; break; case MOO_LE_MIX: break; default: moo_assert_not_reached (); } buffer = moo_edit_get_buffer (edit); if (normalize_le) { contents = get_contents_with_fixed_line_end (buffer, le, le_len); } else { GtkTextIter start, end; gtk_text_buffer_get_bounds (buffer, &start, &end); contents = gtk_text_buffer_get_text (buffer, &start, &end, TRUE); } return contents; } static gboolean do_write (GFile *file, const char *data, gsize len, MooEditSaveFlags flags, GError **error) { MooFileWriter *writer; MooFileWriterFlags writer_flags; gboolean success = FALSE; moo_return_val_if_fail (G_IS_FILE (file), FALSE); writer_flags = (flags & MOO_EDIT_SAVE_BACKUP) ? MOO_FILE_WRITER_SAVE_BACKUP : (MooFileWriterFlags) 0; if ((writer = moo_file_writer_new_for_file (file, writer_flags, error))) { moo_file_writer_write (writer, data, len); success = moo_file_writer_close (writer, error); } return success; } static gboolean do_save_local (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error, gboolean *retval) { char *utf8_contents; const char *to_save; gsize to_save_size; GError *encoding_error = NULL; char *freeme = NULL; gboolean success; success = TRUE; *retval = TRUE; utf8_contents = get_contents (edit); moo_release_assert (utf8_contents != NULL); if (encoding && (!g_ascii_strcasecmp (encoding, "UTF-8") || !g_ascii_strcasecmp (encoding, "UTF8"))) encoding = NULL; if (encoding) { gsize bytes_read; gsize bytes_written; char *encoded = g_convert (utf8_contents, -1, encoding, "UTF-8", &bytes_read, &bytes_written, &encoding_error); if (encoded) { to_save = encoded; to_save_size = bytes_written; freeme = encoded; } else { *retval = FALSE; to_save = utf8_contents; to_save_size = strlen (utf8_contents); } } else { to_save = utf8_contents; to_save_size = strlen (utf8_contents); } if (!do_write (file, to_save, to_save_size, flags, error)) { *retval = FALSE; success = FALSE; } else if (encoding_error) { g_propagate_error (error, encoding_error); set_encoding_error (error); } g_free (freeme); g_free (utf8_contents); return success; } static gboolean moo_edit_save_local (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error) { gboolean result; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); if (do_save_local (edit, file, encoding, flags, error, &result)) { edit->priv->status = (MooEditStatus) 0; _moo_edit_set_file (edit, file, result ? encoding : MOO_ENCODING_UTF8); moo_edit_set_modified (edit, FALSE); _moo_edit_start_file_watch (edit); } return result; } static gboolean moo_edit_save_copy_local (MooEdit *edit, GFile *file, const char *encoding, MooEditSaveFlags flags, GError **error) { gboolean result; moo_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); moo_return_val_if_fail (G_IS_FILE (file), FALSE); do_save_local (edit, file, encoding, flags, error, &result); return result; } /***************************************************************************/ /* Aux stuff */ static void block_buffer_signals (MooEdit *edit) { g_signal_handler_block (moo_edit_get_buffer (edit), edit->priv->modified_changed_handler_id); } static void unblock_buffer_signals (MooEdit *edit) { g_signal_handler_unblock (moo_edit_get_buffer (edit), edit->priv->modified_changed_handler_id); } static void file_watch_callback (G_GNUC_UNUSED MooFileWatch *watch, MooFileEvent *event, gpointer data) { MooEdit *edit = MOO_EDIT (data); moo_return_if_fail (MOO_IS_EDIT (data)); moo_return_if_fail (event->monitor_id == edit->priv->file_monitor_id); moo_return_if_fail (edit->priv->filename != NULL); moo_return_if_fail (!(edit->priv->status & MOO_EDIT_CHANGED_ON_DISK)); switch (event->code) { case MOO_FILE_EVENT_CHANGED: edit->priv->modified_on_disk = TRUE; break; case MOO_FILE_EVENT_DELETED: edit->priv->deleted_from_disk = TRUE; edit->priv->file_monitor_id = 0; break; case MOO_FILE_EVENT_ERROR: /* XXX and what to do now? */ break; case MOO_FILE_EVENT_CREATED: g_critical ("%s: oops", G_STRLOC); break; } check_file_status (edit); } static void _moo_edit_start_file_watch (MooEdit *edit) { MooFileWatch *watch; GError *error = NULL; watch = _moo_editor_get_file_watch (edit->priv->editor); moo_return_if_fail (watch != NULL); if (edit->priv->file_monitor_id) moo_file_watch_cancel_monitor (watch, edit->priv->file_monitor_id); edit->priv->file_monitor_id = 0; moo_return_if_fail ((edit->priv->status & MOO_EDIT_CHANGED_ON_DISK) == 0); moo_return_if_fail (edit->priv->filename != NULL); edit->priv->file_monitor_id = moo_file_watch_create_monitor (watch, edit->priv->filename, file_watch_callback, edit, NULL, &error); if (!edit->priv->file_monitor_id) { g_warning ("%s: could not start watch for '%s'", G_STRLOC, edit->priv->filename); if (error) { g_warning ("%s: %s", G_STRLOC, error->message); g_error_free (error); } return; } } void _moo_edit_stop_file_watch (MooEdit *edit) { MooFileWatch *watch; watch = _moo_editor_get_file_watch (edit->priv->editor); moo_return_if_fail (watch != NULL); if (edit->priv->file_monitor_id) moo_file_watch_cancel_monitor (watch, edit->priv->file_monitor_id); edit->priv->file_monitor_id = 0; } static void check_file_status (MooEdit *edit) { moo_return_if_fail (edit->priv->filename != NULL); moo_return_if_fail (!(edit->priv->status & MOO_EDIT_CHANGED_ON_DISK)); if (edit->priv->deleted_from_disk) file_deleted (edit); else if (edit->priv->modified_on_disk) file_modified_on_disk (edit); } static void file_modified_on_disk (MooEdit *edit) { moo_return_if_fail (edit->priv->filename != NULL); edit->priv->modified_on_disk = FALSE; edit->priv->deleted_from_disk = FALSE; _moo_edit_stop_file_watch (edit); add_status (edit, MOO_EDIT_MODIFIED_ON_DISK); } static void file_deleted (MooEdit *edit) { moo_return_if_fail (edit->priv->filename != NULL); edit->priv->modified_on_disk = FALSE; edit->priv->deleted_from_disk = FALSE; _moo_edit_stop_file_watch (edit); add_status (edit, MOO_EDIT_DELETED); } static void add_status (MooEdit *edit, MooEditStatus s) { edit->priv->status = (MooEditStatus) (edit->priv->status | s); g_signal_emit_by_name (edit, "doc-status-changed", NULL); } void _moo_edit_remove_untitled (MooEdit *doc) { gpointer n = g_hash_table_lookup (UNTITLED_NO, doc); if (n) { UNTITLED = g_slist_remove (UNTITLED, n); g_hash_table_remove (UNTITLED_NO, doc); } } static int add_untitled (MooEdit *edit) { int n; if (!(n = GPOINTER_TO_INT (g_hash_table_lookup (UNTITLED_NO, edit)))) { for (n = 1; ; ++n) { if (!g_slist_find (UNTITLED, GINT_TO_POINTER (n))) { UNTITLED = g_slist_prepend (UNTITLED, GINT_TO_POINTER (n)); break; } } g_hash_table_insert (UNTITLED_NO, edit, GINT_TO_POINTER (n)); } return n; } static char * moo_file_get_display_basename (GFile *file) { char *name; const char *slash; moo_return_val_if_fail (G_IS_FILE (file), NULL); name = moo_file_get_display_name (file); moo_return_val_if_fail (name != NULL, NULL); slash = strrchr (name, '/'); #ifdef G_OS_WIN32 { const char *backslash = strrchr (name, '\\'); if (backslash && (!slash || backslash > slash)) slash = backslash; } #endif if (slash) memmove (name, slash + 1, strlen (slash + 1) + 1); return name; } char * _moo_edit_normalize_filename_for_comparison (const char *filename) { moo_return_val_if_fail (filename != NULL, NULL); #ifdef __WIN32__ /* XXX */ char *tmp = g_utf8_normalize (filename, -1, G_NORMALIZE_ALL_COMPOSE); char *ret = g_utf8_strdown (tmp, -1); g_free (tmp); return ret; #else return g_strdup (filename); #endif } char *_moo_edit_normalize_uri_for_comparison (const char *uri) { return _moo_edit_normalize_filename_for_comparison (uri); } char * _moo_file_get_normalized_name (GFile *file) { char *ret; char *tmp = NULL; char *tmp2 = NULL; moo_return_val_if_fail (G_IS_FILE (file), NULL); tmp = g_file_get_path (file); if (tmp) { tmp2 = _moo_normalize_file_path (tmp); ret = _moo_edit_normalize_filename_for_comparison (tmp2); } else { tmp = g_file_get_uri (file); moo_return_val_if_fail (tmp != NULL, NULL); ret = _moo_edit_normalize_uri_for_comparison (tmp); } g_free (tmp2); g_free (tmp); return ret; } void _moo_edit_set_file (MooEdit *edit, GFile *file, const char *encoding) { GFile *tmp; GSList *free_list = NULL; tmp = edit->priv->file; free_list = g_slist_prepend (free_list, edit->priv->filename); free_list = g_slist_prepend (free_list, edit->priv->norm_name); free_list = g_slist_prepend (free_list, edit->priv->display_filename); free_list = g_slist_prepend (free_list, edit->priv->display_basename); if (!UNTITLED_NO) UNTITLED_NO = g_hash_table_new (g_direct_hash, g_direct_equal); if (!file) { int n = add_untitled (edit); edit->priv->file = NULL; edit->priv->filename = NULL; edit->priv->norm_name = NULL; if (n == 1) edit->priv->display_filename = g_strdup (_("Untitled")); else edit->priv->display_filename = g_strdup_printf (_("Untitled %d"), n); edit->priv->display_basename = g_strdup (edit->priv->display_filename); } else { _moo_edit_remove_untitled (edit); edit->priv->file = g_file_dup (file); edit->priv->filename = g_file_get_path (file); edit->priv->norm_name = _moo_file_get_normalized_name (file); edit->priv->display_filename = moo_file_get_display_name (file); edit->priv->display_basename = moo_file_get_display_basename (file); } if (!encoding) moo_edit_set_encoding (edit, _moo_edit_get_default_encoding ()); else moo_edit_set_encoding (edit, encoding); g_signal_emit_by_name (edit, "filename-changed", NULL); _moo_edit_status_changed (edit); _moo_edit_queue_recheck_config (edit); moo_file_free (tmp); g_slist_foreach (free_list, (GFunc) g_free, NULL); g_slist_free (free_list); } GdkPixbuf * _moo_edit_get_icon (MooEdit *doc, GtkWidget *widget, GtkIconSize size) { if (doc->priv->filename) return moo_get_icon_for_path (doc->priv->filename, widget, size); else if (doc->priv->file) return moo_get_icon_for_path (doc->priv->display_basename, widget, size); else return moo_get_icon_for_path (NULL, widget, size); }