/* * mooedit.c * * Copyright (C) 2004-2008 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 . */ #define MOOEDIT_COMPILATION #include "mooedit/mooeditaction-factory.h" #include "mooedit/mooedit-private.h" #include "mooedit/mooedit-bookmarks.h" #include "mooedit/mootextview-private.h" #include "mooedit/mooeditdialogs.h" #include "mooedit/mooeditprefs.h" #include "mooedit/mootextbuffer.h" #include "mooedit/mooeditfiltersettings.h" #include "mooedit/mooeditor-private.h" #include "mooedit/moolangmgr.h" #include "marshals.h" #include "mooutils/mooi18n.h" #include "mooutils/mooutils-misc.h" #include "mooutils/mootype-macros.h" #include "mooutils/mooatom.h" #include "glade/mooeditprogress-gxml.h" #include #include #define KEY_ENCODING "encoding" #define KEY_LINE "line" GSList *_moo_edit_instances = NULL; static GObject *moo_edit_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_param); static void moo_edit_finalize (GObject *object); static void moo_edit_dispose (GObject *object); static void moo_edit_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void moo_edit_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static gboolean moo_edit_focus_in (GtkWidget *widget, GdkEventFocus *event); static gboolean moo_edit_focus_out (GtkWidget *widget, GdkEventFocus *event); static gboolean moo_edit_popup_menu (GtkWidget *widget); static gboolean moo_edit_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); static gboolean moo_edit_drag_drop (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time); static void moo_edit_filename_changed (MooEdit *edit, const char *new_filename); static void config_changed (MooEdit *edit, GParamSpec *pspec); static void moo_edit_config_notify (MooEdit *edit, guint var_id, GParamSpec *pspec); static void _moo_edit_freeze_config_notify (MooEdit *edit); static void _moo_edit_thaw_config_notify (MooEdit *edit); static void _moo_edit_update_config_from_global (MooEdit *edit); static GtkTextBuffer *get_buffer (MooEdit *edit); static void modified_changed_cb (GtkTextBuffer *buffer, MooEdit *edit); static void moo_edit_apply_style_scheme (MooTextView *view, MooTextStyleScheme *scheme); enum { DOC_STATUS_CHANGED, FILENAME_CHANGED, COMMENT, UNCOMMENT, CONFIG_NOTIFY, SAVE_BEFORE, SAVE_AFTER, BOOKMARKS_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; enum { PROP_0, PROP_EDITOR, PROP_ENABLE_BOOKMARKS, PROP_HAS_COMMENTS, PROP_ENCODING }; MOO_DEFINE_BOXED_TYPE_C (MooEditFileInfo, moo_edit_file_info) G_DEFINE_TYPE (MooEdit, moo_edit, MOO_TYPE_TEXT_VIEW) static void moo_edit_class_init (MooEditClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); MooTextViewClass *textview_class = MOO_TEXT_VIEW_CLASS (klass); gobject_class->set_property = moo_edit_set_property; gobject_class->get_property = moo_edit_get_property; gobject_class->constructor = moo_edit_constructor; gobject_class->finalize = moo_edit_finalize; gobject_class->dispose = moo_edit_dispose; widget_class->popup_menu = moo_edit_popup_menu; widget_class->drag_motion = moo_edit_drag_motion; widget_class->drag_drop = moo_edit_drag_drop; widget_class->focus_in_event = moo_edit_focus_in; widget_class->focus_out_event = moo_edit_focus_out; textview_class->line_mark_clicked = _moo_edit_line_mark_clicked; textview_class->apply_style_scheme = moo_edit_apply_style_scheme; klass->filename_changed = moo_edit_filename_changed; klass->config_notify = moo_edit_config_notify; g_type_class_add_private (klass, sizeof (MooEditPrivate)); g_object_class_install_property (gobject_class, PROP_EDITOR, g_param_spec_object ("editor", "editor", "editor", MOO_TYPE_EDITOR, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (gobject_class, PROP_ENABLE_BOOKMARKS, g_param_spec_boolean ("enable-bookmarks", "enable-bookmarks", "enable-bookmarks", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PROP_HAS_COMMENTS, g_param_spec_boolean ("has-comments", "has-comments", "has-comments", FALSE, G_PARAM_READABLE)); g_object_class_install_property (gobject_class, PROP_ENCODING, g_param_spec_string ("encoding", "encoding", "encoding", NULL, G_PARAM_READWRITE)); signals[CONFIG_NOTIFY] = g_signal_new ("config-notify", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED, G_STRUCT_OFFSET (MooEditClass, config_notify), NULL, NULL, _moo_marshal_VOID__UINT_POINTER, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_POINTER); signals[DOC_STATUS_CHANGED] = g_signal_new ("doc-status-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooEditClass, doc_status_changed), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[FILENAME_CHANGED] = g_signal_new ("filename-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooEditClass, filename_changed), NULL, NULL, _moo_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE); signals[COMMENT] = _moo_signal_new_cb ("comment", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (moo_edit_comment), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[UNCOMMENT] = _moo_signal_new_cb ("uncomment", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_CALLBACK (moo_edit_uncomment), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SAVE_BEFORE] = g_signal_new ("save-before", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooEditClass, save_before), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[SAVE_AFTER] = g_signal_new ("save-after", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (MooEditClass, save_after), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); signals[BOOKMARKS_CHANGED] = g_signal_new ("bookmarks-changed", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (MooEditClass, bookmarks_changed), NULL, NULL, _moo_marshal_VOID__VOID, G_TYPE_NONE, 0); _moo_edit_init_config (); _moo_edit_class_init_actions (klass); } static void moo_edit_init (MooEdit *edit) { MooIndenter *indent; edit->config = moo_edit_config_new (); g_signal_connect_swapped (edit->config, "notify", G_CALLBACK (config_changed), edit); edit->priv = G_TYPE_INSTANCE_GET_PRIVATE (edit, MOO_TYPE_EDIT, MooEditPrivate); edit->priv->actions = moo_action_collection_new ("MooEdit", "MooEdit"); indent = moo_indenter_new (edit); moo_text_view_set_indenter (MOO_TEXT_VIEW (edit), indent); g_object_unref (indent); } static GObject* moo_edit_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_param) { GObject *object; MooEdit *edit; GtkTextBuffer *buffer; object = G_OBJECT_CLASS (moo_edit_parent_class)->constructor ( type, n_construct_properties, construct_param); edit = MOO_EDIT (object); _moo_edit_add_class_actions (edit); _moo_edit_instances = g_slist_prepend (_moo_edit_instances, edit); edit->priv->modified_changed_handler_id = g_signal_connect (get_buffer (edit), "modified-changed", G_CALLBACK (modified_changed_cb), edit); _moo_edit_set_file (edit, NULL, NULL); buffer = get_buffer (edit); g_signal_connect_swapped (buffer, "line-mark-moved", G_CALLBACK (_moo_edit_line_mark_moved), edit); g_signal_connect_swapped (buffer, "line-mark-deleted", G_CALLBACK (_moo_edit_line_mark_deleted), edit); return object; } static void moo_edit_finalize (GObject *object) { MooEdit *edit = MOO_EDIT (object); if (edit->priv->file) g_object_unref (edit->priv->file); g_free (edit->priv->filename); g_free (edit->priv->display_filename); g_free (edit->priv->display_basename); g_free (edit->priv->encoding); g_free (edit->priv->progress_text); G_OBJECT_CLASS (moo_edit_parent_class)->finalize (object); } static void moo_edit_dispose (GObject *object) { MooEdit *edit = MOO_EDIT (object); _moo_edit_instances = g_slist_remove (_moo_edit_instances, edit); if (edit->config) { g_signal_handlers_disconnect_by_func (edit->config, (gpointer) config_changed, edit); g_object_unref (edit->config); edit->config = NULL; } if (edit->priv->apply_config_idle) { g_source_remove (edit->priv->apply_config_idle); edit->priv->apply_config_idle = 0; } edit->priv->focus_in_handler_id = 0; if (edit->priv->file_monitor_id) { _moo_edit_stop_file_watch (edit); edit->priv->file_monitor_id = 0; } if (edit->priv->progress) { g_critical ("%s: oops", G_STRLOC); edit->priv->progress = NULL; edit->priv->progressbar = NULL; } if (edit->priv->progress_timeout) { g_critical ("%s: oops", G_STRLOC); g_source_remove (edit->priv->progress_timeout); edit->priv->progress_timeout = 0; } if (edit->priv->update_bookmarks_idle) { g_source_remove (edit->priv->update_bookmarks_idle); edit->priv->update_bookmarks_idle = 0; } _moo_edit_delete_bookmarks (edit, TRUE); if (edit->priv->actions) { g_object_unref (edit->priv->actions); edit->priv->actions = NULL; } G_OBJECT_CLASS (moo_edit_parent_class)->dispose (object); } static void modified_changed_cb (GtkTextBuffer *buffer, MooEdit *edit) { moo_edit_set_modified (edit, gtk_text_buffer_get_modified (buffer)); } void moo_edit_set_modified (MooEdit *edit, gboolean modified) { gboolean buf_modified; GtkTextBuffer *buffer; g_return_if_fail (MOO_IS_EDIT (edit)); buffer = get_buffer (edit); buf_modified = gtk_text_buffer_get_modified (buffer); if (buf_modified != modified) { g_signal_handler_block (buffer, edit->priv->modified_changed_handler_id); gtk_text_buffer_set_modified (buffer, modified); g_signal_handler_unblock (buffer, edit->priv->modified_changed_handler_id); } if (modified) edit->priv->status |= MOO_EDIT_MODIFIED; else edit->priv->status &= ~MOO_EDIT_MODIFIED; moo_edit_status_changed (edit); } void moo_edit_set_clean (MooEdit *edit, gboolean clean) { g_return_if_fail (MOO_IS_EDIT (edit)); if (clean) edit->priv->status |= MOO_EDIT_CLEAN; else edit->priv->status &= ~MOO_EDIT_CLEAN; moo_edit_status_changed (edit); } gboolean moo_edit_get_clean (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return (edit->priv->status & MOO_EDIT_CLEAN) ? TRUE : FALSE; } void moo_edit_status_changed (MooEdit *edit) { g_return_if_fail (MOO_IS_EDIT (edit)); g_signal_emit (edit, signals[DOC_STATUS_CHANGED], 0, NULL); } #if 0 void _moo_edit_set_status (MooEdit *edit, MooEditStatus status) { g_return_if_fail (MOO_IS_EDIT (edit)); if (edit->priv->status != status) { edit->priv->status = status; moo_edit_status_changed (edit); } } #endif gboolean moo_edit_is_empty (MooEdit *edit) { GtkTextIter start, end; g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); if (MOO_EDIT_IS_BUSY (edit) || MOO_EDIT_IS_MODIFIED (edit) || !MOO_EDIT_IS_UNTITLED (edit)) return FALSE; gtk_text_buffer_get_bounds (get_buffer (edit), &start, &end); return !gtk_text_iter_compare (&start, &end); } gboolean moo_edit_is_untitled (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return MOO_EDIT_IS_UNTITLED (edit); } MooEditStatus moo_edit_get_status (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), 0); return edit->priv->status; } static void moo_edit_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { MooEdit *edit = MOO_EDIT (object); switch (prop_id) { case PROP_EDITOR: edit->priv->editor = g_value_get_object (value); break; case PROP_ENABLE_BOOKMARKS: moo_edit_set_enable_bookmarks (edit, g_value_get_boolean (value)); break; case PROP_ENCODING: _moo_edit_set_encoding (edit, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void moo_edit_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MooEdit *edit = MOO_EDIT (object); switch (prop_id) { case PROP_EDITOR: g_value_set_object (value, edit->priv->editor); break; case PROP_ENABLE_BOOKMARKS: g_value_set_boolean (value, edit->priv->enable_bookmarks); break; case PROP_HAS_COMMENTS: g_value_set_boolean (value, _moo_edit_has_comments (edit, NULL, NULL)); break; case PROP_ENCODING: g_value_set_string (value, edit->priv->encoding); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void moo_edit_apply_style_scheme (MooTextView *view, MooTextStyleScheme *scheme) { MOO_TEXT_VIEW_CLASS (moo_edit_parent_class)->apply_style_scheme (view, scheme); _moo_edit_update_bookmarks_style (MOO_EDIT (view)); } static gboolean moo_edit_focus_in (GtkWidget *widget, GdkEventFocus *event) { gboolean retval = FALSE; MooEdit *doc = MOO_EDIT (widget); _moo_editor_set_focused_doc (doc->priv->editor, doc); if (GTK_WIDGET_CLASS(moo_edit_parent_class)->focus_in_event) retval = GTK_WIDGET_CLASS(moo_edit_parent_class)->focus_in_event (widget, event); return retval; } static gboolean moo_edit_focus_out (GtkWidget *widget, GdkEventFocus *event) { gboolean retval = FALSE; MooEdit *doc = MOO_EDIT (widget); _moo_editor_unset_focused_doc (doc->priv->editor, doc); if (GTK_WIDGET_CLASS(moo_edit_parent_class)->focus_out_event) retval = GTK_WIDGET_CLASS(moo_edit_parent_class)->focus_out_event (widget, event); return retval; } static MooEditFileInfo * moo_edit_file_info_new (GFile *file, const char *encoding) { MooEditFileInfo *info = moo_new (MooEditFileInfo); info->file = file; info->encoding = g_strdup (encoding); return info; } MooEditFileInfo * moo_edit_file_info_new_path (const char *path, const char *encoding) { g_return_val_if_fail (path != NULL, NULL); return moo_edit_file_info_new (g_file_new_for_path (path), encoding); } MooEditFileInfo * moo_edit_file_info_new_uri (const char *uri, const char *encoding) { g_return_val_if_fail (uri != NULL, NULL); return moo_edit_file_info_new (g_file_new_for_uri (uri), encoding); } MooEditFileInfo * moo_edit_file_info_copy (MooEditFileInfo *info) { return info ? moo_edit_file_info_new (g_object_ref (info->file), info->encoding) : NULL; } void moo_edit_file_info_free (MooEditFileInfo *info) { if (info) { g_free (info->encoding); g_object_unref (info->file); moo_free (MooEditFileInfo, info); } } GFile * _moo_edit_get_file (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return edit->priv->file ? g_file_dup (edit->priv->file) : NULL; } char * moo_edit_get_filename (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return g_strdup (edit->priv->filename); } const char * moo_edit_get_display_name (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return edit->priv->display_filename; } const char * moo_edit_get_display_basename (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return edit->priv->display_basename; } char * moo_edit_get_uri (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return edit->priv->file ? g_file_get_uri (edit->priv->file) : NULL; } const char * moo_edit_get_encoding (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), NULL); return edit->priv->encoding; } void _moo_edit_set_encoding (MooEdit *edit, const char *encoding) { g_return_if_fail (MOO_IS_EDIT (edit)); g_return_if_fail (encoding != NULL); if (!_moo_str_equal (encoding, edit->priv->encoding)) { MOO_ASSIGN_STRING (edit->priv->encoding, encoding); g_object_notify (G_OBJECT (edit), "encoding"); } } static GtkTextBuffer * get_buffer (MooEdit *edit) { return gtk_text_view_get_buffer (GTK_TEXT_VIEW (edit)); } MooEditor * moo_edit_get_editor (MooEdit *doc) { g_return_val_if_fail (MOO_IS_EDIT (doc), NULL); return doc->priv->editor; } void _moo_edit_history_item_set_encoding (MdHistoryItem *item, const char *encoding) { g_return_if_fail (item != NULL); md_history_item_set (item, KEY_ENCODING, encoding); } void _moo_edit_history_item_set_line (MdHistoryItem *item, int line) { char *value = NULL; g_return_if_fail (item != NULL); if (line >= 0) value = g_strdup_printf ("%d", line + 1); md_history_item_set (item, KEY_LINE, value); g_free (value); } const char * _moo_edit_history_item_get_encoding (MdHistoryItem *item) { g_return_val_if_fail (item != NULL, NULL); return md_history_item_get (item, KEY_ENCODING); } int _moo_edit_history_item_get_line (MdHistoryItem *item) { const char *strval; g_return_val_if_fail (item != NULL, -1); strval = md_history_item_get (item, KEY_LINE); if (strval && strval[0]) return strtol (strval, NULL, 10) - 1; else return -1; } typedef void (*SetVarFunc) (MooEdit *edit, const char *name, char *val); static void parse_mode_string (MooEdit *edit, char *string, const char *var_val_separator, SetVarFunc func) { char **vars, **p; vars = NULL; g_strstrip (string); vars = g_strsplit (string, ";", 0); if (!vars) goto out; for (p = vars; *p != NULL; p++) { char *sep, *var, *value; g_strstrip (*p); sep = strstr (*p, var_val_separator); if (!sep || sep == *p || !sep[1]) goto out; var = g_strndup (*p, sep - *p); g_strstrip (var); if (!var[0]) { g_free (var); goto out; } value = sep + 1; g_strstrip (value); if (!value) { g_free (var); goto out; } func (edit, var, value); g_free (var); } out: g_strfreev (vars); } static void set_kate_var (MooEdit *edit, const char *name, const char *val) { if (!g_ascii_strcasecmp (name, "space-indent")) { gboolean spaces = FALSE; if (moo_edit_config_parse_bool (val, &spaces)) moo_edit_config_parse_one (edit->config, "indent-use-tabs", spaces ? "false" : "true", MOO_EDIT_CONFIG_SOURCE_FILE); } else { moo_edit_config_parse_one (edit->config, name, val, MOO_EDIT_CONFIG_SOURCE_FILE); } } static void parse_kate_mode_string (MooEdit *edit, char *string) { parse_mode_string (edit, string, " ", (SetVarFunc) set_kate_var); } static void set_emacs_var (MooEdit *edit, const char *name, char *val) { if (!g_ascii_strcasecmp (name, "mode")) { moo_edit_config_parse_one (edit->config, "lang", val, MOO_EDIT_CONFIG_SOURCE_FILE); } else if (!g_ascii_strcasecmp (name, "tab-width")) { moo_edit_config_parse_one (edit->config, "tab-width", val, MOO_EDIT_CONFIG_SOURCE_FILE); } else if (!g_ascii_strcasecmp (name, "c-basic-offset")) { moo_edit_config_parse_one (edit->config, "indent-width", val, MOO_EDIT_CONFIG_SOURCE_FILE); } else if (!g_ascii_strcasecmp (name, "indent-tabs-mode")) { if (!g_ascii_strcasecmp (val, "nil")) moo_edit_config_parse_one (edit->config, "indent-use-tabs", "false", MOO_EDIT_CONFIG_SOURCE_FILE); else moo_edit_config_parse_one (edit->config, "indent-use-tabs", "true", MOO_EDIT_CONFIG_SOURCE_FILE); } } static void parse_emacs_mode_string (MooEdit *edit, char *string) { MooLangMgr *mgr; g_strstrip (string); mgr = moo_lang_mgr_default (); if (_moo_lang_mgr_find_lang (mgr, string)) set_emacs_var (edit, "mode", string); else parse_mode_string (edit, string, ":", set_emacs_var); } static void parse_moo_mode_string (MooEdit *edit, char *string) { moo_edit_config_parse (edit->config, string, MOO_EDIT_CONFIG_SOURCE_FILE); } #define KATE_MODE_STRING "kate:" #define EMACS_MODE_STRING "-*-" #define MOO_MODE_STRING "-%-" static void try_mode_string (MooEdit *edit, char *string) { char *start, *end; if ((start = strstr (string, KATE_MODE_STRING))) { start += strlen (KATE_MODE_STRING); parse_kate_mode_string (edit, start); return; } if ((start = strstr (string, EMACS_MODE_STRING))) { start += strlen (EMACS_MODE_STRING); if ((end = strstr (start, EMACS_MODE_STRING)) && end > start) { end[0] = 0; parse_emacs_mode_string (edit, start); return; } } if ((start = strstr (string, MOO_MODE_STRING))) { start += strlen (MOO_MODE_STRING); if ((end = strstr (start, MOO_MODE_STRING)) && end > start) { end[0] = 0; parse_moo_mode_string (edit, start); return; } } } static void try_mode_strings (MooEdit *edit) { GtkTextBuffer *buffer = get_buffer (edit); GtkTextIter start, end; char *first = NULL, *second = NULL, *last = NULL; gtk_text_buffer_get_start_iter (buffer, &start); if (!gtk_text_iter_ends_line (&start)) { end = start; gtk_text_iter_forward_to_line_end (&end); first = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE); } if (gtk_text_iter_forward_line (&start)) { if (!gtk_text_iter_ends_line (&start)) { end = start; gtk_text_iter_forward_to_line_end (&end); second = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE); } } gtk_text_buffer_get_end_iter (buffer, &end); if (gtk_text_iter_starts_line (&end)) { gtk_text_iter_backward_line (&end); start = end; gtk_text_iter_forward_to_line_end (&end); } else { start = end; gtk_text_iter_set_line_offset (&start, 0); } if (gtk_text_iter_get_line (&start) != 1 && gtk_text_iter_get_line (&start) != 2) last = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE); if (first) try_mode_string (edit, first); if (second) try_mode_string (edit, second); if (last) try_mode_string (edit, last); g_free (first); g_free (second); g_free (last); } static void config_changed (MooEdit *edit, GParamSpec *pspec) { guint id = moo_edit_config_get_setting_id (pspec); GQuark detail = g_quark_from_string (pspec->name); g_return_if_fail (id != 0); g_signal_emit (edit, signals[CONFIG_NOTIFY], detail, id, pspec); } static void moo_edit_set_lang (MooEdit *edit, MooLang *lang) { MooLang *old_lang; old_lang = moo_text_view_get_lang (MOO_TEXT_VIEW (edit)); if (old_lang != lang) { moo_text_view_set_lang (MOO_TEXT_VIEW (edit), lang); _moo_lang_mgr_update_config (moo_lang_mgr_default (), edit->config, _moo_lang_id (lang)); _moo_edit_update_config_from_global (edit); g_object_notify (G_OBJECT (edit), "has-comments"); } } static void moo_edit_apply_lang_config (MooEdit *edit) { const char *lang_id = moo_edit_config_get_string (edit->config, "lang"); MooLangMgr *mgr = moo_lang_mgr_default (); MooLang *lang = lang_id ? _moo_lang_mgr_find_lang (mgr, lang_id) : NULL; moo_edit_set_lang (edit, lang); } static void moo_edit_apply_config (MooEdit *edit) { GtkWrapMode wrap_mode; gboolean line_numbers; guint tab_width; char *word_chars; moo_edit_apply_lang_config (edit); moo_edit_config_get (edit->config, "wrap-mode", &wrap_mode, "show-line-numbers", &line_numbers, "tab-width", &tab_width, "word-chars", &word_chars, NULL); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (edit), wrap_mode); moo_text_view_set_show_line_numbers (MOO_TEXT_VIEW (edit), line_numbers); moo_text_view_set_tab_width (MOO_TEXT_VIEW (edit), tab_width); moo_text_view_set_word_chars (MOO_TEXT_VIEW (edit), word_chars); g_free (word_chars); } static gboolean do_apply_config (MooEdit *edit) { edit->priv->apply_config_idle = 0; moo_edit_apply_config (edit); return FALSE; } static void moo_edit_queue_apply_config (MooEdit *edit) { if (!edit->priv->apply_config_idle) edit->priv->apply_config_idle = moo_idle_add_full (G_PRIORITY_HIGH, (GSourceFunc) do_apply_config, edit, NULL); } static void moo_edit_config_notify (MooEdit *edit, guint var_id, G_GNUC_UNUSED GParamSpec *pspec) { if (var_id == _moo_edit_settings[MOO_EDIT_SETTING_LANG]) moo_edit_apply_lang_config (edit); else moo_edit_queue_apply_config (edit); } static void _moo_edit_update_config_from_global (MooEdit *edit) { g_return_if_fail (MOO_IS_EDIT (edit)); /* XXX */ moo_edit_config_unset_by_source (edit->config, MOO_EDIT_CONFIG_SOURCE_AUTO); } void _moo_edit_update_lang_config (void) { GSList *l; for (l = _moo_edit_instances; l != NULL; l = l->next) { MooEdit *edit = l->data; _moo_lang_mgr_update_config (moo_lang_mgr_default (), edit->config, _moo_lang_id (moo_text_view_get_lang (MOO_TEXT_VIEW (edit)))); } } static void moo_edit_filename_changed (MooEdit *edit, const char *filename) { gboolean lang_changed = FALSE; MooLang *lang = NULL, *old_lang = NULL; const char *lang_id = NULL; char *filter_config = NULL; old_lang = moo_text_view_get_lang (MOO_TEXT_VIEW (edit)); _moo_edit_freeze_config_notify (edit); moo_edit_config_unset_by_source (edit->config, MOO_EDIT_CONFIG_SOURCE_FILE); _moo_edit_update_config_from_global (edit); if (filename) { MooLangMgr *mgr = moo_lang_mgr_default (); lang = moo_lang_mgr_get_lang_for_file (mgr, filename); lang_id = lang ? _moo_lang_id (lang) : NULL; filter_config = _moo_edit_filter_settings_get_for_file (filename); } moo_edit_config_set (edit->config, MOO_EDIT_CONFIG_SOURCE_FILENAME, "lang", lang_id, "indent", NULL, NULL); if (filter_config) moo_edit_config_parse (edit->config, filter_config, MOO_EDIT_CONFIG_SOURCE_FILENAME); try_mode_strings (edit); lang_id = moo_edit_config_get_string (edit->config, "lang"); if (!lang_id) lang_id = MOO_LANG_NONE; lang_changed = strcmp (lang_id, _moo_lang_id (old_lang)) != 0; if (!lang_changed) { _moo_lang_mgr_update_config (moo_lang_mgr_default (), edit->config, _moo_lang_id (lang)); _moo_edit_update_config_from_global (edit); } _moo_edit_thaw_config_notify (edit); g_free (filter_config); } gboolean moo_edit_close (MooEdit *edit, gboolean ask_confirm) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return moo_editor_close_doc (edit->priv->editor, edit, ask_confirm); } gboolean moo_edit_save (MooEdit *edit, GError **error) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return _moo_editor_save (edit->priv->editor, edit, error); } gboolean moo_edit_save_as (MooEdit *edit, const char *filename, const char *encoding, GError **error) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return _moo_editor_save_as (edit->priv->editor, edit, filename, encoding, error); } gboolean moo_edit_save_copy (MooEdit *edit, const char *filename, const char *encoding, GError **error) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); return moo_editor_save_copy (edit->priv->editor, edit, filename, encoding, error); } static void _moo_edit_freeze_config_notify (MooEdit *edit) { g_return_if_fail (MOO_IS_EDIT (edit)); g_object_freeze_notify (G_OBJECT (edit->config)); } static void _moo_edit_thaw_config_notify (MooEdit *edit) { g_return_if_fail (MOO_IS_EDIT (edit)); g_object_thaw_notify (G_OBJECT (edit->config)); } static gboolean find_uri_atom (GdkDragContext *context) { GList *targets; GdkAtom atom; atom = moo_atom_uri_list (); targets = context->targets; while (targets) { if (targets->data == GUINT_TO_POINTER (atom)) return TRUE; targets = targets->next; } return FALSE; } static gboolean moo_edit_drag_motion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { if (find_uri_atom (context)) return FALSE; return GTK_WIDGET_CLASS(moo_edit_parent_class)->drag_motion (widget, context, x, y, time); } static gboolean moo_edit_drag_drop (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time) { if (find_uri_atom (context)) return FALSE; return GTK_WIDGET_CLASS(moo_edit_parent_class)->drag_drop (widget, context, x, y, time); } /*****************************************************************************/ /* Comment/uncomment */ /* TODO: all this stuff, it's pretty lame */ gboolean _moo_edit_has_comments (MooEdit *edit, gboolean *single_line, gboolean *multi_line) { MooLang *lang; gboolean single, multi; lang = moo_text_view_get_lang (MOO_TEXT_VIEW (edit)); if (!lang) return FALSE; single = _moo_lang_get_line_comment (lang) != NULL; multi = _moo_lang_get_block_comment_start (lang) && _moo_lang_get_block_comment_end (lang); if (single_line) *single_line = single; if (multi_line) *multi_line = multi; return single || multi; } static void line_comment (GtkTextBuffer *buffer, const char *comment_string, GtkTextIter *start, GtkTextIter *end) { int first, last, i; GtkTextIter iter; char *comment_and_space; g_return_if_fail (comment_string && comment_string[0]); first = gtk_text_iter_get_line (start); last = gtk_text_iter_get_line (end); if (!gtk_text_iter_equal (start, end) && gtk_text_iter_starts_line (end)) last -= 1; comment_and_space = g_strdup_printf ("%s ", comment_string); for (i = first; i <= last; ++i) { gtk_text_buffer_get_iter_at_line (buffer, &iter, i); if (gtk_text_iter_ends_line (&iter)) gtk_text_buffer_insert (buffer, &iter, comment_string, -1); else gtk_text_buffer_insert (buffer, &iter, comment_and_space, -1); } g_free (comment_and_space); } static void line_uncomment (GtkTextBuffer *buffer, const char *comment_string, GtkTextIter *start, GtkTextIter *end) { int first, last, i; guint chars; g_return_if_fail (comment_string && comment_string[0]); first = gtk_text_iter_get_line (start); last = gtk_text_iter_get_line (end); if (!gtk_text_iter_equal (start, end) && gtk_text_iter_starts_line (end)) last -= 1; chars = g_utf8_strlen (comment_string, -1); for (i = first; i <= last; ++i) { char *text; gtk_text_buffer_get_iter_at_line (buffer, start, i); *end = *start; gtk_text_iter_forward_chars (end, chars); text = gtk_text_iter_get_slice (start, end); if (!strcmp (comment_string, text)) { if (gtk_text_iter_get_char (end) == ' ') gtk_text_iter_forward_char (end); gtk_text_buffer_delete (buffer, start, end); } g_free (text); } } static void iter_to_line_end (GtkTextIter *iter) { if (!gtk_text_iter_ends_line (iter)) gtk_text_iter_forward_to_line_end (iter); } static void block_comment (GtkTextBuffer *buffer, const char *comment_start, const char *comment_end, GtkTextIter *start, GtkTextIter *end) { GtkTextMark *end_mark; g_return_if_fail (comment_start && comment_start[0]); g_return_if_fail (comment_end && comment_end[0]); if (gtk_text_iter_equal (start, end)) { gtk_text_iter_set_line_offset (start, 0); iter_to_line_end (end); } else { if (gtk_text_iter_starts_line (end)) { gtk_text_iter_backward_line (end); iter_to_line_end (end); } } end_mark = gtk_text_buffer_create_mark (buffer, NULL, end, FALSE); gtk_text_buffer_insert (buffer, start, comment_start, -1); gtk_text_buffer_get_iter_at_mark (buffer, start, end_mark); gtk_text_buffer_insert (buffer, start, comment_end, -1); gtk_text_buffer_delete_mark (buffer, end_mark); } static void block_uncomment (GtkTextBuffer *buffer, const char *comment_start, const char *comment_end, GtkTextIter *start, GtkTextIter *end) { GtkTextIter start1, start2, end1, end2; GtkTextMark *mark1, *mark2; GtkTextIter limit; gboolean found; g_return_if_fail (comment_start && comment_start[0]); g_return_if_fail (comment_end && comment_end[0]); if (!gtk_text_iter_equal (start, end) && gtk_text_iter_starts_line (end)) { gtk_text_iter_backward_line (end); iter_to_line_end (end); } limit = *end; found = moo_text_search_forward (start, comment_start, 0, &start1, &start2, &limit); if (!found) { gtk_text_iter_set_line_offset (&limit, 0); found = gtk_text_iter_backward_search (start, comment_start, 0, &start1, &start2, &limit); } if (!found) return; limit = start2; found = gtk_text_iter_backward_search (end, comment_end, 0, &end1, &end2, &limit); if (!found) { limit = *end; iter_to_line_end (&limit); found = moo_text_search_forward (end, comment_end, 0, &end1, &end2, &limit); } if (!found) return; g_assert (gtk_text_iter_compare (&start2, &end1) < 0); mark1 = gtk_text_buffer_create_mark (buffer, NULL, &end1, FALSE); mark2 = gtk_text_buffer_create_mark (buffer, NULL, &end2, FALSE); gtk_text_buffer_delete (buffer, &start1, &start2); gtk_text_buffer_get_iter_at_mark (buffer, &end1, mark1); gtk_text_buffer_get_iter_at_mark (buffer, &end2, mark2); gtk_text_buffer_delete (buffer, &end1, &end2); gtk_text_buffer_delete_mark (buffer, mark1); gtk_text_buffer_delete_mark (buffer, mark2); } void moo_edit_comment (MooEdit *edit) { MooLang *lang; GtkTextIter start, end; GtkTextBuffer *buffer; gboolean has_selection, single_line, multi_line; gboolean adjust_selection = FALSE, move_insert = FALSE; int sel_start_line = 0, sel_start_offset = 0; g_return_if_fail (MOO_IS_EDIT (edit)); lang = moo_text_view_get_lang (MOO_TEXT_VIEW (edit)); if (!_moo_edit_has_comments (edit, &single_line, &multi_line)) return; buffer = get_buffer (edit); has_selection = gtk_text_buffer_get_selection_bounds (buffer, &start, &end); gtk_text_buffer_begin_user_action (buffer); if (has_selection) { GtkTextIter iter; adjust_selection = TRUE; gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer)); move_insert = gtk_text_iter_equal (&iter, &start); sel_start_line = gtk_text_iter_get_line (&start); sel_start_offset = gtk_text_iter_get_line_offset (&start); } /* FIXME */ if (single_line) line_comment (buffer, _moo_lang_get_line_comment (lang), &start, &end); else block_comment (buffer, _moo_lang_get_block_comment_start (lang), _moo_lang_get_block_comment_end (lang), &start, &end); if (adjust_selection) { const char *mark = move_insert ? "insert" : "selection_bound"; gtk_text_buffer_get_iter_at_line_offset (buffer, &start, sel_start_line, sel_start_offset); gtk_text_buffer_move_mark_by_name (buffer, mark, &start); } gtk_text_buffer_end_user_action (buffer); } void moo_edit_uncomment (MooEdit *edit) { MooLang *lang; GtkTextIter start, end; GtkTextBuffer *buffer; gboolean single_line, multi_line; g_return_if_fail (MOO_IS_EDIT (edit)); lang = moo_text_view_get_lang (MOO_TEXT_VIEW (edit)); if (!_moo_edit_has_comments (edit, &single_line, &multi_line)) return; buffer = get_buffer (edit); gtk_text_buffer_get_selection_bounds (buffer, &start, &end); gtk_text_buffer_begin_user_action (buffer); /* FIXME */ if (single_line) line_uncomment (buffer, _moo_lang_get_line_comment (lang), &start, &end); else block_uncomment (buffer, _moo_lang_get_block_comment_start (lang), _moo_lang_get_block_comment_end (lang), &start, &end); gtk_text_buffer_end_user_action (buffer); } void _moo_edit_ensure_newline (MooEdit *edit) { GtkTextBuffer *buffer; GtkTextIter iter; g_return_if_fail (MOO_IS_EDIT (edit)); buffer = get_buffer (edit); gtk_text_buffer_get_end_iter (buffer, &iter); if (!gtk_text_iter_starts_line (&iter)) gtk_text_buffer_insert (buffer, &iter, "\n", -1); } /*****************************************************************************/ /* popup menu */ /* gtktextview.c */ static void popup_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { GtkTextView *text_view; GtkWidget *widget; GdkRectangle cursor_rect; GdkRectangle onscreen_rect; gint root_x, root_y; GtkTextIter iter; GtkRequisition req; GdkScreen *screen; gint monitor_num; GdkRectangle monitor; text_view = GTK_TEXT_VIEW (user_data); widget = GTK_WIDGET (text_view); g_return_if_fail (GTK_WIDGET_REALIZED (text_view)); screen = gtk_widget_get_screen (widget); gdk_window_get_origin (widget->window, &root_x, &root_y); gtk_text_buffer_get_iter_at_mark (gtk_text_view_get_buffer (text_view), &iter, gtk_text_buffer_get_insert (gtk_text_view_get_buffer (text_view))); gtk_text_view_get_iter_location (text_view, &iter, &cursor_rect); gtk_text_view_get_visible_rect (text_view, &onscreen_rect); gtk_widget_size_request (GTK_WIDGET (menu), &req); /* can't use rectangle_intersect since cursor rect can have 0 width */ if (cursor_rect.x >= onscreen_rect.x && cursor_rect.x < onscreen_rect.x + onscreen_rect.width && cursor_rect.y >= onscreen_rect.y && cursor_rect.y < onscreen_rect.y + onscreen_rect.height) { gtk_text_view_buffer_to_window_coords (text_view, GTK_TEXT_WINDOW_WIDGET, cursor_rect.x, cursor_rect.y, &cursor_rect.x, &cursor_rect.y); *x = root_x + cursor_rect.x + cursor_rect.width; *y = root_y + cursor_rect.y + cursor_rect.height; } else { /* Just center the menu, since cursor is offscreen. */ *x = root_x + (widget->allocation.width / 2 - req.width / 2); *y = root_y + (widget->allocation.height / 2 - req.height / 2); } /* Ensure sanity */ *x = CLAMP (*x, root_x, (root_x + widget->allocation.width)); *y = CLAMP (*y, root_y, (root_y + widget->allocation.height)); monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y); gtk_menu_set_monitor (menu, monitor_num); gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width)); *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height)); *push_in = FALSE; } void _moo_edit_do_popup (MooEdit *edit, GdkEventButton *event) { MooUiXml *xml; MooEditWindow *window; GtkMenu *menu; window = moo_edit_get_window (edit); xml = moo_editor_get_doc_ui_xml (edit->priv->editor); g_return_if_fail (xml != NULL); menu = moo_ui_xml_create_widget (xml, MOO_UI_MENU, "Editor/Popup", edit->priv->actions, window ? MOO_WINDOW(window)->accel_group : NULL); g_return_if_fail (menu != NULL); MOO_OBJECT_REF_SINK (menu); _moo_edit_check_actions (edit); if (event) { gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time); } else { gtk_menu_popup (menu, NULL, NULL, popup_position_func, edit, 0, gtk_get_current_event_time ()); gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); } g_object_unref (menu); } static gboolean moo_edit_popup_menu (GtkWidget *widget) { _moo_edit_do_popup (MOO_EDIT (widget), NULL); return TRUE; } /*****************************************************************************/ /* progress dialogs and stuff */ MooEditState moo_edit_get_state (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), MOO_EDIT_STATE_NORMAL); return edit->priv->state; } static void position_progress (MooEdit *edit) { GtkAllocation *allocation; int x, y; g_return_if_fail (MOO_IS_EDIT (edit)); g_return_if_fail (GTK_IS_WIDGET (edit->priv->progress)); if (!GTK_WIDGET_REALIZED (edit)) return; allocation = >K_WIDGET(edit)->allocation; x = allocation->width/2 - PROGRESS_WIDTH/2; y = allocation->height/2 - PROGRESS_HEIGHT/2; gtk_text_view_move_child (GTK_TEXT_VIEW (edit), edit->priv->progress, x, y); } static void update_progress (MooEdit *edit) { g_return_if_fail (MOO_IS_EDIT (edit)); g_return_if_fail (edit->priv->progress_text != NULL); g_return_if_fail (edit->priv->state != MOO_EDIT_STATE_NORMAL); if (edit->priv->progressbar) gtk_progress_bar_set_text (GTK_PROGRESS_BAR (edit->priv->progressbar), edit->priv->progress_text); } void _moo_edit_set_progress_text (MooEdit *edit, const char *text) { g_free (edit->priv->progress_text); edit->priv->progress_text = g_strdup (text); update_progress (edit); } static gboolean pulse_progress (MooEdit *edit) { g_return_val_if_fail (MOO_IS_EDIT (edit), FALSE); g_return_val_if_fail (GTK_IS_WIDGET (edit->priv->progressbar), FALSE); gtk_progress_bar_pulse (GTK_PROGRESS_BAR (edit->priv->progressbar)); update_progress (edit); return TRUE; } static void progress_cancel_clicked (MooEdit *doc) { g_return_if_fail (MOO_IS_EDIT (doc)); if (doc->priv->state && doc->priv->cancel_op) doc->priv->cancel_op (doc->priv->cancel_data); } static gboolean show_progress (MooEdit *edit) { ProgressDialogXml *xml; edit->priv->progress_timeout = 0; g_return_val_if_fail (!edit->priv->progress, FALSE); xml = progress_dialog_xml_new (); edit->priv->progress = GTK_WIDGET (xml->ProgressDialog); edit->priv->progressbar = GTK_WIDGET (xml->progressbar); g_assert (GTK_IS_WIDGET (edit->priv->progressbar)); g_signal_connect_swapped (xml->cancel, "clicked", G_CALLBACK (progress_cancel_clicked), MOO_EDIT (edit)); gtk_text_view_add_child_in_window (GTK_TEXT_VIEW (edit), edit->priv->progress, GTK_TEXT_WINDOW_WIDGET, 0, 0); position_progress (edit); update_progress (edit); edit->priv->progress_timeout = _moo_timeout_add (PROGRESS_TIMEOUT, (GSourceFunc) pulse_progress, edit); return FALSE; } void _moo_edit_set_state (MooEdit *edit, MooEditState state, const char *text, GDestroyNotify cancel, gpointer data) { g_return_if_fail (state == MOO_EDIT_STATE_NORMAL || edit->priv->state == MOO_EDIT_STATE_NORMAL); edit->priv->cancel_op = cancel; edit->priv->cancel_data = data; if (state == edit->priv->state) return; edit->priv->state = state; gtk_text_view_set_editable (GTK_TEXT_VIEW (edit), !state); if (!state) { if (edit->priv->progress) { GtkWidget *tmp = edit->priv->progress; edit->priv->progress = NULL; edit->priv->progressbar = NULL; gtk_widget_destroy (tmp); } g_free (edit->priv->progress_text); edit->priv->progress_text = NULL; if (edit->priv->progress_timeout) g_source_remove (edit->priv->progress_timeout); edit->priv->progress_timeout = 0; } else { if (!edit->priv->progress_timeout) edit->priv->progress_timeout = _moo_timeout_add (PROGRESS_TIMEOUT, (GSourceFunc) show_progress, edit); edit->priv->progress_text = g_strdup (text); } } #ifdef MOO_ENABLE_UNIT_TESTS #include #include #include static struct { char *working_dir; char *encodings_dir; } test_data; #ifdef __WIN32__ #define LE "\r\n" #else #define LE "\n" #endif #define TT1 "blah blah blah" #define TT2 "blah blah blah" LE "blah blah blah" #define TT3 LE LE LE LE #define TT4 "lala\nlala\n" #define TT5 "lala\r\nlala\r\n" #define TT6 "lala\rlala\r" static void check_contents (const char *filename, const char *expected) { char *contents; GError *error = NULL; if (!g_file_get_contents (filename, &contents, NULL, &error)) { TEST_FAILED_MSG ("could not load file '%s': %s", filename, error->message); g_error_free (error); return; } TEST_ASSERT_STR_EQ (contents, expected); g_free (contents); } static void test_basic (void) { MooEditor *editor; MooEdit *doc, *doc2; GtkTextBuffer *buffer; char *filename; editor = moo_editor_instance (); filename = g_build_filename (test_data.working_dir, "test.txt", NULL); doc = moo_editor_new_file (editor, NULL, NULL, filename, NULL); TEST_ASSERT (doc != NULL); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, ""); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (doc)); gtk_text_buffer_set_text (buffer, TT1, -1); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT1); gtk_text_buffer_set_text (buffer, TT2, -1); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT2); gtk_text_buffer_set_text (buffer, TT2 LE, -1); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT2 LE); gtk_text_buffer_set_text (buffer, TT3, -1); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT3); doc2 = moo_editor_open_file (editor, NULL, NULL, filename, NULL); TEST_ASSERT (doc2 == doc); TEST_ASSERT (moo_edit_close (doc, TRUE)); TEST_ASSERT (moo_editor_get_doc (editor, filename) == NULL); g_file_set_contents (filename, TT4, -1, NULL); doc = moo_editor_open_file (editor, NULL, NULL, filename, NULL); TEST_ASSERT (doc != NULL); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT4); TEST_ASSERT (moo_edit_close (doc, TRUE)); g_file_set_contents (filename, TT5, -1, NULL); doc = moo_editor_open_file (editor, NULL, NULL, filename, NULL); TEST_ASSERT (doc != NULL); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT5); TEST_ASSERT (moo_edit_close (doc, TRUE)); g_file_set_contents (filename, TT6, -1, NULL); doc = moo_editor_open_file (editor, NULL, NULL, filename, NULL); TEST_ASSERT (doc != NULL); TEST_ASSERT (moo_edit_save (doc, NULL)); check_contents (filename, TT6); TEST_ASSERT (moo_edit_close (doc, TRUE)); g_free (filename); } #define TEST_ASSERT_SAME_FILE_CONTENT(filename1, filename2) \ { \ char *contents1__ = NULL; \ char *contents2__ = NULL; \ g_file_get_contents (filename1, &contents1__, NULL, NULL); \ g_file_get_contents (filename2, &contents2__, NULL, NULL); \ if (!contents1__) \ moo_test_assert_msg (FALSE, __FILE__, __LINE__, \ "could not open file %s", \ filename1); \ if (!contents2__) \ moo_test_assert_msg (FALSE, __FILE__, __LINE__, \ "could not open file %s", \ filename2); \ if (contents1__ && contents2__) \ { \ gboolean equal = strcmp (contents1__, contents2__) == 0; \ TEST_ASSERT_MSG (equal, "contents of %s and %s differ", \ filename1, filename2); \ } \ g_free (contents2__); \ g_free (contents1__); \ } static void test_encodings_1 (const char *name, const char *working_dir) { char *filename; char *filename2; char *encoding; const char *dot; MooEditor *editor; MooEdit *doc; if ((dot = strchr (name, '.'))) encoding = g_strndup (name, dot - name); else encoding = g_strdup (name); filename = g_build_filename (test_data.encodings_dir, name, NULL); filename2 = g_build_filename (working_dir, name, NULL); editor = moo_editor_instance (); doc = moo_editor_open_file (editor, NULL, NULL, filename, encoding); TEST_ASSERT (doc != NULL); if (doc) { TEST_ASSERT (moo_edit_save_as (doc, filename2, NULL, NULL)); TEST_ASSERT_SAME_FILE_CONTENT (filename2, filename); TEST_ASSERT (moo_edit_close (doc, TRUE)); } g_free (encoding); g_free (filename2); g_free (filename); } static void test_encodings (void) { GDir *dir; const char *name; char *working_dir; dir = g_dir_open (test_data.encodings_dir, 0, NULL); if (!dir) { g_critical ("could not open encodings dir"); TEST_ASSERT (FALSE); return; } working_dir = g_build_filename (test_data.working_dir, "encodings", NULL); _moo_mkdir_with_parents (working_dir); while ((name = g_dir_read_name (dir))) test_encodings_1 (name, working_dir); g_free (working_dir); g_dir_close (dir); } static gboolean test_suite_init (G_GNUC_UNUSED gpointer data) { /* try to avoid accidental deletion of good stuff */ if (strcmp (g_get_prgname(), "run-tests") != 0) { g_critical ("fix the program name!"); return FALSE; } test_data.working_dir = g_build_filename (moo_test_get_working_dir (), "editor-work", NULL); test_data.encodings_dir = g_build_filename (moo_test_get_data_dir (), "encodings", NULL); if (_moo_mkdir_with_parents (test_data.working_dir) != 0) { g_critical ("could not create directory '%s'", test_data.working_dir); g_free (test_data.working_dir); test_data.working_dir = NULL; return FALSE; } moo_editor_create_instance (FALSE); return TRUE; } static void test_suite_cleanup (G_GNUC_UNUSED gpointer data) { GError *error = NULL; char *recent_file; char *cache_dir; MooEditor *editor; // if (!_moo_remove_dir (test_data.working_dir, TRUE, &error)) // { // g_critical ("could not remove directory '%s': %s", // test_data.working_dir, error->message); // g_error_free (error); // error = NULL; // } g_free (test_data.working_dir); g_free (test_data.encodings_dir); test_data.working_dir = NULL; test_data.encodings_dir = NULL; editor = moo_editor_instance (); moo_editor_close_all (editor, FALSE, FALSE); recent_file = _md_history_mgr_get_filename (_moo_editor_get_history_mgr (editor)); g_object_unref (moo_editor_instance ()); if (!g_file_test (recent_file, G_FILE_TEST_EXISTS)) g_critical ("recent file %s does not exist", recent_file); cache_dir = moo_get_user_cache_dir (); if (!_moo_remove_dir (cache_dir, TRUE, &error)) { g_critical ("could not remove directory '%s': %s", cache_dir, error->message); g_error_free (error); error = NULL; } g_free (cache_dir); g_free (recent_file); } void moo_test_editor (void) { MooTestSuite *suite = moo_test_suite_new ("Editor", test_suite_init, test_suite_cleanup, NULL); moo_test_suite_add_test (suite, "basic", (MooTestFunc) test_basic, NULL); moo_test_suite_add_test (suite, "encodings", (MooTestFunc) test_encodings, NULL); } #endif