diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 17:42:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 17:42:51 +0000 |
commit | ba429d344132c088177e853cce8ff7181570b221 (patch) | |
tree | 87ebf15269b4301737abd1735baabba71be93622 /plugins/modelines | |
parent | Initial commit. (diff) | |
download | gedit-upstream.tar.xz gedit-upstream.zip |
Adding upstream version 44.2.upstream/44.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'plugins/modelines')
-rw-r--r-- | plugins/modelines/gedit-modeline-plugin.c | 225 | ||||
-rw-r--r-- | plugins/modelines/gedit-modeline-plugin.h | 60 | ||||
-rw-r--r-- | plugins/modelines/language-mappings | 14 | ||||
-rw-r--r-- | plugins/modelines/meson.build | 42 | ||||
-rw-r--r-- | plugins/modelines/modeline-parser.c | 899 | ||||
-rw-r--r-- | plugins/modelines/modeline-parser.h | 36 | ||||
-rw-r--r-- | plugins/modelines/modelines.plugin.desktop.in | 8 |
7 files changed, 1284 insertions, 0 deletions
diff --git a/plugins/modelines/gedit-modeline-plugin.c b/plugins/modelines/gedit-modeline-plugin.c new file mode 100644 index 0000000..376d996 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.c @@ -0,0 +1,225 @@ +/* + * gedit-modeline-plugin.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2010 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <glib/gi18n-lib.h> +#include <gmodule.h> +#include "gedit-modeline-plugin.h" +#include "modeline-parser.h" + +#include <gedit/gedit-debug.h> +#include <gedit/gedit-view-activatable.h> +#include <gedit/gedit-view.h> + +struct _GeditModelinePluginPrivate +{ + GeditView *view; + + gulong document_loaded_handler_id; + gulong document_saved_handler_id; +}; + +enum +{ + PROP_0, + PROP_VIEW +}; + +static void gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface); + +G_DEFINE_DYNAMIC_TYPE_EXTENDED (GeditModelinePlugin, + gedit_modeline_plugin, + PEAS_TYPE_EXTENSION_BASE, + 0, + G_IMPLEMENT_INTERFACE_DYNAMIC (GEDIT_TYPE_VIEW_ACTIVATABLE, + gedit_view_activatable_iface_init) + G_ADD_PRIVATE_DYNAMIC (GeditModelinePlugin)) + +static void +gedit_modeline_plugin_constructed (GObject *object) +{ + gchar *data_dir; + + data_dir = peas_extension_base_get_data_dir (PEAS_EXTENSION_BASE (object)); + + modeline_parser_init (data_dir); + + g_free (data_dir); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->constructed (object); +} + +static void +gedit_modeline_plugin_init (GeditModelinePlugin *plugin) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin initializing"); + + plugin->priv = gedit_modeline_plugin_get_instance_private (plugin); + +} + +static void +gedit_modeline_plugin_dispose (GObject *object) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin disposing"); + + g_clear_object (&plugin->priv->view); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->dispose (object); +} + +static void +gedit_modeline_plugin_finalize (GObject *object) +{ + gedit_debug_message (DEBUG_PLUGINS, "GeditModelinePlugin finalizing"); + + modeline_parser_shutdown (); + + G_OBJECT_CLASS (gedit_modeline_plugin_parent_class)->finalize (object); +} + +static void +gedit_modeline_plugin_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + plugin->priv->view = GEDIT_VIEW (g_value_dup_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gedit_modeline_plugin_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GeditModelinePlugin *plugin = GEDIT_MODELINE_PLUGIN (object); + + switch (prop_id) + { + case PROP_VIEW: + g_value_set_object (value, plugin->priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_document_loaded_or_saved (GeditDocument *document, + GtkSourceView *view) +{ + modeline_parser_apply_modeline (view); +} + +static void +gedit_modeline_plugin_activate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + modeline_parser_apply_modeline (GTK_SOURCE_VIEW (plugin->priv->view)); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + plugin->priv->document_loaded_handler_id = + g_signal_connect (doc, "loaded", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); + plugin->priv->document_saved_handler_id = + g_signal_connect (doc, "saved", + G_CALLBACK (on_document_loaded_or_saved), + plugin->priv->view); +} + +static void +gedit_modeline_plugin_deactivate (GeditViewActivatable *activatable) +{ + GeditModelinePlugin *plugin; + GtkTextBuffer *doc; + + gedit_debug (DEBUG_PLUGINS); + + plugin = GEDIT_MODELINE_PLUGIN (activatable); + + doc = gtk_text_view_get_buffer (GTK_TEXT_VIEW (plugin->priv->view)); + + g_signal_handler_disconnect (doc, plugin->priv->document_loaded_handler_id); + g_signal_handler_disconnect (doc, plugin->priv->document_saved_handler_id); +} + +static void +gedit_modeline_plugin_class_init (GeditModelinePluginClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gedit_modeline_plugin_constructed; + object_class->dispose = gedit_modeline_plugin_dispose; + object_class->finalize = gedit_modeline_plugin_finalize; + object_class->set_property = gedit_modeline_plugin_set_property; + object_class->get_property = gedit_modeline_plugin_get_property; + + g_object_class_override_property (object_class, PROP_VIEW, "view"); +} + +static void +gedit_view_activatable_iface_init (GeditViewActivatableInterface *iface) +{ + iface->activate = gedit_modeline_plugin_activate; + iface->deactivate = gedit_modeline_plugin_deactivate; +} + +static void +gedit_modeline_plugin_class_finalize (GeditModelinePluginClass *klass) +{ +} + + +G_MODULE_EXPORT void +peas_register_types (PeasObjectModule *module) +{ + gedit_modeline_plugin_register_type (G_TYPE_MODULE (module)); + + peas_object_module_register_extension_type (module, + GEDIT_TYPE_VIEW_ACTIVATABLE, + GEDIT_TYPE_MODELINE_PLUGIN); +} + +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/gedit-modeline-plugin.h b/plugins/modelines/gedit-modeline-plugin.h new file mode 100644 index 0000000..d3557e4 --- /dev/null +++ b/plugins/modelines/gedit-modeline-plugin.h @@ -0,0 +1,60 @@ +/* + * gedit-modeline-plugin.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef GEDIT_MODELINE_PLUGIN_H +#define GEDIT_MODELINE_PLUGIN_H + +#include <glib.h> +#include <glib-object.h> +#include <libpeas/peas-extension-base.h> +#include <libpeas/peas-object-module.h> + +G_BEGIN_DECLS + +#define GEDIT_TYPE_MODELINE_PLUGIN (gedit_modeline_plugin_get_type ()) +#define GEDIT_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePlugin)) +#define GEDIT_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) +#define GEDIT_IS_MODELINE_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_IS_MODELINE_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GEDIT_TYPE_MODELINE_PLUGIN)) +#define GEDIT_MODELINE_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GEDIT_TYPE_MODELINE_PLUGIN, GeditModelinePluginClass)) + +typedef struct _GeditModelinePlugin GeditModelinePlugin; +typedef struct _GeditModelinePluginPrivate GeditModelinePluginPrivate; +typedef struct _GeditModelinePluginClass GeditModelinePluginClass; + +struct _GeditModelinePlugin { + PeasExtensionBase parent; + + /*< private >*/ + GeditModelinePluginPrivate *priv; +}; + +struct _GeditModelinePluginClass { + PeasExtensionBaseClass parent_class; +}; + +GType gedit_modeline_plugin_get_type (void) G_GNUC_CONST; + +G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module); + +G_END_DECLS + +#endif /* GEDIT_MODELINE_PLUGIN_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/language-mappings b/plugins/modelines/language-mappings new file mode 100644 index 0000000..47d0029 --- /dev/null +++ b/plugins/modelines/language-mappings @@ -0,0 +1,14 @@ +[vim] +cs=c-sharp +docbk=docbook +javascript=js +lhaskell=haskell-literate +spec=rpmspec +tex=latex +xhtml=html + +[emacs] +c++=cpp + +[kate] + diff --git a/plugins/modelines/meson.build b/plugins/modelines/meson.build new file mode 100644 index 0000000..3ff0e84 --- /dev/null +++ b/plugins/modelines/meson.build @@ -0,0 +1,42 @@ +libmodelines_sources = files( + 'gedit-modeline-plugin.c', + 'modeline-parser.c', +) + +libmodelines_deps = [ + libgedit_dep, +] + +libmodelines_sha = shared_module( + 'modelines', + sources: libmodelines_sources, + include_directories: root_include_dir, + dependencies: libmodelines_deps, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ), + name_suffix: module_suffix, +) + +custom_target( + 'modelines.plugin', + input: 'modelines.plugin.desktop.in', + output: 'modelines.plugin', + command: msgfmt_plugin_cmd, + install: true, + install_dir: join_paths( + pkglibdir, + 'plugins', + ) +) + +install_data( + 'language-mappings', + install_dir: join_paths( + pkgdatadir, + 'plugins', + 'modelines', + ) +) diff --git a/plugins/modelines/modeline-parser.c b/plugins/modelines/modeline-parser.c new file mode 100644 index 0000000..989e111 --- /dev/null +++ b/plugins/modelines/modeline-parser.c @@ -0,0 +1,899 @@ +/* + * modeline-parser.c + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <gtksourceview/gtksource.h> +#include <gedit/gedit-debug.h> +#include <gedit/gedit-document.h> +#include <gedit/gedit-settings.h> +#include <gedit/gedit-utils.h> +#include "modeline-parser.h" + +#define MODELINES_LANGUAGE_MAPPINGS_FILE "language-mappings" + +/* base dir to lookup configuration files */ +static gchar *modelines_data_dir; + +/* Mappings: language name -> Gedit language ID */ +static GHashTable *vim_languages = NULL; +static GHashTable *emacs_languages = NULL; +static GHashTable *kate_languages = NULL; + +typedef enum +{ + MODELINE_SET_NONE = 0, + MODELINE_SET_TAB_WIDTH = 1 << 0, + MODELINE_SET_INDENT_WIDTH = 1 << 1, + MODELINE_SET_WRAP_MODE = 1 << 2, + MODELINE_SET_SHOW_RIGHT_MARGIN = 1 << 3, + MODELINE_SET_RIGHT_MARGIN_POSITION = 1 << 4, + MODELINE_SET_LANGUAGE = 1 << 5, + MODELINE_SET_INSERT_SPACES = 1 << 6 +} ModelineSet; + +typedef struct _ModelineOptions +{ + gchar *language_id; + + /* these options are similar to the GtkSourceView properties of the + * same names. + */ + gboolean insert_spaces; + guint tab_width; + guint indent_width; + GtkWrapMode wrap_mode; + gboolean display_right_margin; + guint right_margin_position; + + ModelineSet set; +} ModelineOptions; + +#define MODELINE_OPTIONS_DATA_KEY "ModelineOptionsDataKey" + +static gboolean +has_option (ModelineOptions *options, + ModelineSet set) +{ + return options->set & set; +} + +void +modeline_parser_init (const gchar *data_dir) +{ + if (modelines_data_dir == NULL) + { + modelines_data_dir = g_strdup (data_dir); + } +} + +void +modeline_parser_shutdown () +{ + if (vim_languages != NULL) + g_hash_table_unref (vim_languages); + + if (emacs_languages != NULL) + g_hash_table_unref (emacs_languages); + + if (kate_languages != NULL) + g_hash_table_unref (kate_languages); + + vim_languages = NULL; + emacs_languages = NULL; + kate_languages = NULL; + + g_free (modelines_data_dir); + modelines_data_dir = NULL; +} + +static GHashTable * +load_language_mappings_group (GKeyFile *key_file, const gchar *group) +{ + GHashTable *table; + gchar **keys; + gsize length = 0; + int i; + + table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + keys = g_key_file_get_keys (key_file, group, &length, NULL); + + gedit_debug_message (DEBUG_PLUGINS, + "%" G_GSIZE_FORMAT " mappings in group %s", + length, group); + + for (i = 0; i < length; i++) + { + /* steal the name string */ + gchar *name = keys[i]; + gchar *id = g_key_file_get_string (key_file, group, name, NULL); + g_hash_table_insert (table, name, id); + } + g_free (keys); + + return table; +} + +/* lazy loading of language mappings */ +static void +load_language_mappings (void) +{ + gchar *fname; + GKeyFile *mappings; + GError *error = NULL; + + fname = g_build_filename (modelines_data_dir, + MODELINES_LANGUAGE_MAPPINGS_FILE, + NULL); + + mappings = g_key_file_new (); + + if (g_key_file_load_from_file (mappings, fname, 0, &error)) + { + gedit_debug_message (DEBUG_PLUGINS, + "Loaded language mappings from %s", + fname); + + vim_languages = load_language_mappings_group (mappings, "vim"); + emacs_languages = load_language_mappings_group (mappings, "emacs"); + kate_languages = load_language_mappings_group (mappings, "kate"); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Failed to loaded language mappings from %s: %s", + fname, error->message); + + g_error_free (error); + } + + g_key_file_free (mappings); + g_free (fname); +} + +static gchar * +get_language_id (const gchar *language_name, GHashTable *mapping) +{ + gchar *name; + gchar *language_id = NULL; + + name = g_ascii_strdown (language_name, -1); + + if (mapping != NULL) + { + language_id = g_hash_table_lookup (mapping, name); + + if (language_id != NULL) + { + g_free (name); + return g_strdup (language_id); + } + } + + /* by default assume that the gtksourcevuew id is the same */ + return name; +} + +static gchar * +get_language_id_vim (const gchar *language_name) +{ + if (vim_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, vim_languages); +} + +static gchar * +get_language_id_emacs (const gchar *language_name) +{ + if (emacs_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, emacs_languages); +} + +static gchar * +get_language_id_kate (const gchar *language_name) +{ + if (kate_languages == NULL) + load_language_mappings (); + + return get_language_id (language_name, kate_languages); +} + +static gboolean +skip_whitespaces (gchar **s) +{ + while (**s != '\0' && g_ascii_isspace (**s)) + (*s)++; + return **s != '\0'; +} + +/* Parse vi(m) modelines. + * Vi(m) modelines looks like this: + * - first form: [text]{white}{vi:|vim:|ex:}[white]{options} + * - second form: [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text] + * They can happen on the three first or last lines. + */ +static gchar * +parse_vim_modeline (gchar *s, + ModelineOptions *options) +{ + gboolean in_set = FALSE; + gboolean neg; + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0' && !(in_set && *s == ':')) + { + while (*s != '\0' && (*s == ':' || g_ascii_isspace (*s))) + s++; + + if (*s == '\0') + break; + + if (strncmp (s, "set ", 4) == 0 || + strncmp (s, "se ", 3) == 0) + { + s = strchr(s, ' ') + 1; + in_set = TRUE; + } + + neg = FALSE; + if (strncmp (s, "no", 2) == 0) + { + neg = TRUE; + s += 2; + } + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != '=' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (*s == '=') + { + s++; + while (*s != '\0' && *s != ':' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + } + + if (strcmp (key->str, "ft") == 0 || + strcmp (key->str, "filetype") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_vim (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "et") == 0 || + strcmp (key->str, "expandtab") == 0) + { + options->insert_spaces = !neg; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "ts") == 0 || + strcmp (key->str, "tabstop") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "sw") == 0 || + strcmp (key->str, "shiftwidth") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "wrap") == 0) + { + options->wrap_mode = neg ? GTK_WRAP_NONE : GTK_WRAP_WORD; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "textwidth") == 0 || + strcmp (key->str, "tw") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_SHOW_RIGHT_MARGIN | + MODELINE_SET_RIGHT_MARGIN_POSITION; + + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Parse emacs modelines. + * Emacs modelines looks like this: "-*- key1: value1; key2: value2 -*-" + * They can happen on the first line, or on the second one if the first line is + * a shebang (#!) + * See http://www.delorie.com/gnu/docs/emacs/emacs_486.html + */ +static gchar * +parse_emacs_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0' || strncmp (s, "-*-", 3) == 0) + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ':' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + + if (*s != ':') + continue; + s++; + + if (!skip_whitespaces (&s)) + break; + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Emacs modeline bit: %s = %s", + key->str, value->str); + + /* "Mode" key is case insenstive */ + if (g_ascii_strcasecmp (key->str, "Mode") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_emacs (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-offset") == 0 || + strcmp (key->str, "c-basic-offset") == 0 || + strcmp (key->str, "js-indent-level") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->indent_width = intval; + options->set |= MODELINE_SET_INDENT_WIDTH; + } + } + else if (strcmp (key->str, "indent-tabs-mode") == 0) + { + intval = strcmp (value->str, "nil") == 0; + options->insert_spaces = intval; + + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "autowrap") == 0) + { + intval = strcmp (value->str, "nil") != 0; + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return *s == '\0' ? s : s + 2; +} + +/* + * Parse kate modelines. + * Kate modelines are of the form "kate: key1 value1; key2 value2;" + * These can happen on the 10 first or 10 last lines of the buffer. + * See http://wiki.kate-editor.org/index.php/Modelines + */ +static gchar * +parse_kate_modeline (gchar *s, + ModelineOptions *options) +{ + guint intval; + GString *key, *value; + + key = g_string_sized_new (8); + value = g_string_sized_new (8); + + while (*s != '\0') + { + while (*s != '\0' && (*s == ';' || g_ascii_isspace (*s))) + s++; + if (*s == '\0') + break; + + g_string_assign (key, ""); + g_string_assign (value, ""); + + while (*s != '\0' && *s != ';' && !g_ascii_isspace (*s)) + { + g_string_append_c (key, *s); + s++; + } + + if (!skip_whitespaces (&s)) + break; + if (*s == ';') + continue; + + while (*s != '\0' && *s != ';' && + !g_ascii_isspace (*s)) + { + g_string_append_c (value, *s); + s++; + } + + gedit_debug_message (DEBUG_PLUGINS, + "Kate modeline bit: %s = %s", + key->str, value->str); + + if (strcmp (key->str, "hl") == 0 || + strcmp (key->str, "syntax") == 0) + { + g_free (options->language_id); + options->language_id = get_language_id_kate (value->str); + + options->set |= MODELINE_SET_LANGUAGE; + } + else if (strcmp (key->str, "tab-width") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->tab_width = intval; + options->set |= MODELINE_SET_TAB_WIDTH; + } + } + else if (strcmp (key->str, "indent-width") == 0) + { + intval = atoi (value->str); + if (intval) options->indent_width = intval; + } + else if (strcmp (key->str, "space-indent") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->insert_spaces = intval; + options->set |= MODELINE_SET_INSERT_SPACES; + } + else if (strcmp (key->str, "word-wrap") == 0) + { + intval = strcmp (value->str, "on") == 0 || + strcmp (value->str, "true") == 0 || + strcmp (value->str, "1") == 0; + + options->wrap_mode = intval ? GTK_WRAP_WORD : GTK_WRAP_NONE; + + options->set |= MODELINE_SET_WRAP_MODE; + } + else if (strcmp (key->str, "word-wrap-column") == 0) + { + intval = atoi (value->str); + + if (intval) + { + options->right_margin_position = intval; + options->display_right_margin = TRUE; + + options->set |= MODELINE_SET_RIGHT_MARGIN_POSITION | + MODELINE_SET_SHOW_RIGHT_MARGIN; + } + } + } + + g_string_free (key, TRUE); + g_string_free (value, TRUE); + + return s; +} + +/* Scan a line for vi(m)/emacs/kate modelines. + * Line numbers are counted starting at one. + */ +static void +parse_modeline (gchar *line, + gint line_number, + gint line_count, + ModelineOptions *options) +{ + gchar *s = line; + + /* look for the beginning of a modeline */ + while (s != NULL && *s != '\0') + { + if (s > line && !g_ascii_isspace (*(s - 1))) + { + s++; + continue; + } + + if ((line_number <= 3 || line_number > line_count - 3) && + (strncmp (s, "ex:", 3) == 0 || + strncmp (s, "vi:", 3) == 0 || + strncmp (s, "vim:", 4) == 0)) + { + gedit_debug_message (DEBUG_PLUGINS, "Vim modeline on line %d", line_number); + + while (*s != ':') + { + s++; + } + + s = parse_vim_modeline (s + 1, options); + } + else if (line_number <= 2 && strncmp (s, "-*-", 3) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Emacs modeline on line %d", line_number); + + s = parse_emacs_modeline (s + 3, options); + } + else if ((line_number <= 10 || line_number > line_count - 10) && + strncmp (s, "kate:", 5) == 0) + { + gedit_debug_message (DEBUG_PLUGINS, "Kate modeline on line %d", line_number); + + s = parse_kate_modeline (s + 5, options); + } + else + { + s++; + } + } +} + +static gboolean +check_previous (GtkSourceView *view, + ModelineOptions *previous, + ModelineSet set) +{ + GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (gtk_text_view_get_buffer (GTK_TEXT_VIEW (view))); + + /* Do not restore default when this is the first time */ + if (!previous) + return FALSE; + + /* Do not restore default when previous was not set */ + if (!(previous->set & set)) + return FALSE; + + /* Only restore default when setting has not changed */ + switch (set) + { + case MODELINE_SET_INSERT_SPACES: + return gtk_source_view_get_insert_spaces_instead_of_tabs (view) == + previous->insert_spaces; + break; + case MODELINE_SET_TAB_WIDTH: + return gtk_source_view_get_tab_width (view) == previous->tab_width; + break; + case MODELINE_SET_INDENT_WIDTH: + return gtk_source_view_get_indent_width (view) == previous->indent_width; + break; + case MODELINE_SET_WRAP_MODE: + return gtk_text_view_get_wrap_mode (GTK_TEXT_VIEW (view)) == + previous->wrap_mode; + break; + case MODELINE_SET_RIGHT_MARGIN_POSITION: + return gtk_source_view_get_right_margin_position (view) == + previous->right_margin_position; + break; + case MODELINE_SET_SHOW_RIGHT_MARGIN: + return gtk_source_view_get_show_right_margin (view) == + previous->display_right_margin; + break; + case MODELINE_SET_LANGUAGE: + { + GtkSourceLanguage *language = gtk_source_buffer_get_language (buffer); + + return (language == NULL && previous->language_id == NULL) || + (language != NULL && g_strcmp0 (gtk_source_language_get_id (language), + previous->language_id) == 0); + } + break; + default: + return FALSE; + break; + } +} + +static void +free_modeline_options (ModelineOptions *options) +{ + g_free (options->language_id); + g_slice_free (ModelineOptions, options); +} + +void +modeline_parser_apply_modeline (GtkSourceView *view) +{ + ModelineOptions options; + GtkTextBuffer *buffer; + GtkTextIter iter, liter; + gint line_count; + GSettings *settings; + + options.language_id = NULL; + options.set = MODELINE_SET_NONE; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); + gtk_text_buffer_get_start_iter (buffer, &iter); + + line_count = gtk_text_buffer_get_line_count (buffer); + + /* Parse the modelines on the 10 first lines... */ + while ((gtk_text_iter_get_line (&iter) < 10) && + !gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* ...and on the 10 last ones (modelines are not allowed in between) */ + if (!gtk_text_iter_is_end (&iter)) + { + gint cur_line; + guint remaining_lines; + + /* we are on the 11th line (count from 0) */ + cur_line = gtk_text_iter_get_line (&iter); + /* g_assert (10 == cur_line); */ + + remaining_lines = line_count - cur_line - 1; + + if (remaining_lines > 10) + { + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_iter_backward_lines (&iter, 9); + } + } + + while (!gtk_text_iter_is_end (&iter)) + { + gchar *line; + + liter = iter; + gtk_text_iter_forward_to_line_end (&iter); + line = gtk_text_buffer_get_text (buffer, &liter, &iter, TRUE); + + parse_modeline (line, + 1 + gtk_text_iter_get_line (&iter), + line_count, + &options); + + gtk_text_iter_forward_line (&iter); + + g_free (line); + } + + /* Try to set language */ + if (has_option (&options, MODELINE_SET_LANGUAGE) && options.language_id) + { + if (g_ascii_strcasecmp (options.language_id, "text") == 0) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + NULL); + } + else + { + GtkSourceLanguageManager *manager; + GtkSourceLanguage *language; + + manager = gtk_source_language_manager_get_default (); + + language = gtk_source_language_manager_get_language + (manager, options.language_id); + if (language != NULL) + { + gedit_document_set_language (GEDIT_DOCUMENT (buffer), + language); + } + else + { + gedit_debug_message (DEBUG_PLUGINS, + "Unknown language `%s'", + options.language_id); + } + } + } + + ModelineOptions *previous = g_object_get_data (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY); + + settings = g_settings_new ("org.gnome.gedit.preferences.editor"); + + /* Apply the options we got from modelines and restore defaults if + we set them before */ + if (has_option (&options, MODELINE_SET_INSERT_SPACES)) + { + gtk_source_view_set_insert_spaces_instead_of_tabs + (view, options.insert_spaces); + } + else if (check_previous (view, previous, MODELINE_SET_INSERT_SPACES)) + { + gboolean insert_spaces; + + insert_spaces = g_settings_get_boolean (settings, GEDIT_SETTINGS_INSERT_SPACES); + + gtk_source_view_set_insert_spaces_instead_of_tabs (view, insert_spaces); + } + + if (has_option (&options, MODELINE_SET_TAB_WIDTH)) + { + gtk_source_view_set_tab_width (view, options.tab_width); + } + else if (check_previous (view, previous, MODELINE_SET_TAB_WIDTH)) + { + guint tab_width; + + g_settings_get (settings, GEDIT_SETTINGS_TABS_SIZE, "u", &tab_width); + + gtk_source_view_set_tab_width (view, tab_width); + } + + if (has_option (&options, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, options.indent_width); + } + else if (check_previous (view, previous, MODELINE_SET_INDENT_WIDTH)) + { + gtk_source_view_set_indent_width (view, -1); + } + + if (has_option (&options, MODELINE_SET_WRAP_MODE)) + { + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), options.wrap_mode); + } + else if (check_previous (view, previous, MODELINE_SET_WRAP_MODE)) + { + GtkWrapMode mode; + + mode = g_settings_get_enum (settings, + GEDIT_SETTINGS_WRAP_MODE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (view), mode); + } + + if (has_option (&options, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + gtk_source_view_set_right_margin_position (view, options.right_margin_position); + } + else if (check_previous (view, previous, MODELINE_SET_RIGHT_MARGIN_POSITION)) + { + guint right_margin_pos; + + g_settings_get (settings, GEDIT_SETTINGS_RIGHT_MARGIN_POSITION, "u", + &right_margin_pos); + gtk_source_view_set_right_margin_position (view, + right_margin_pos); + } + + if (has_option (&options, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gtk_source_view_set_show_right_margin (view, options.display_right_margin); + } + else if (check_previous (view, previous, MODELINE_SET_SHOW_RIGHT_MARGIN)) + { + gboolean display_right_margin; + + display_right_margin = g_settings_get_boolean (settings, + GEDIT_SETTINGS_DISPLAY_RIGHT_MARGIN); + gtk_source_view_set_show_right_margin (view, display_right_margin); + } + + if (previous) + { + g_free (previous->language_id); + *previous = options; + previous->language_id = g_strdup (options.language_id); + } + else + { + previous = g_slice_new (ModelineOptions); + *previous = options; + previous->language_id = g_strdup (options.language_id); + + g_object_set_data_full (G_OBJECT (buffer), + MODELINE_OPTIONS_DATA_KEY, + previous, + (GDestroyNotify)free_modeline_options); + } + + g_object_unref (settings); + g_free (options.language_id); +} + +/* vi:ts=8 */ diff --git a/plugins/modelines/modeline-parser.h b/plugins/modelines/modeline-parser.h new file mode 100644 index 0000000..0fbf9a1 --- /dev/null +++ b/plugins/modelines/modeline-parser.h @@ -0,0 +1,36 @@ +/* + * modelie-parser.h + * Emacs, Kate and Vim-style modelines support for gedit. + * + * Copyright (C) 2005-2007 - Steve Frécinaux <code@istique.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MODELINE_PARSER_H +#define MODELINE_PARSER_H + +#include <glib.h> +#include <gtksourceview/gtksource.h> + +G_BEGIN_DECLS + +void modeline_parser_init (const gchar *data_dir); +void modeline_parser_shutdown (void); +void modeline_parser_apply_modeline (GtkSourceView *view); + +G_END_DECLS + +#endif /* MODELINE_PARSER_H */ +/* ex:set ts=8 noet: */ diff --git a/plugins/modelines/modelines.plugin.desktop.in b/plugins/modelines/modelines.plugin.desktop.in new file mode 100644 index 0000000..d334c55 --- /dev/null +++ b/plugins/modelines/modelines.plugin.desktop.in @@ -0,0 +1,8 @@ +[Plugin] +Module=modelines +IAge=3 +Name=Modelines +Description=Emacs, Kate and Vim-style modelines support for gedit. +Authors=Steve Frécinaux <steve@istique.net> +Copyright=Copyright © 2005 Steve Frécinaux +Website=http://www.gedit.org |