1 /*
2 * Copyright (C) 2018 Jiri Techet <techet@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include "vi.h"
24
25 #include <gtk/gtk.h>
26 #include <geanyplugin.h>
27
28 #define CONF_GROUP "Settings"
29 #define CONF_ENABLE_VIM "enable_vim"
30 #define CONF_START_IN_INSERT "start_in_insert"
31 #define CONF_INSERT_FOR_DUMMIES "insert_for_dummies"
32
33 GeanyPlugin *geany_plugin;
34 GeanyData *geany_data;
35
36 PLUGIN_VERSION_CHECK(235)
37
38 PLUGIN_SET_TRANSLATABLE_INFO(
39 LOCALEDIR,
40 GETTEXT_PACKAGE,
41 _("Vimode"),
42 _("Vim mode for Geany"),
43 VERSION,
44 "Jiří Techet <techet@gmail.com>"
45 )
46
47 enum
48 {
49 KB_ENABLE_VIM,
50 KB_INSERT_FOR_DUMMIES,
51 KB_COUNT
52 };
53
54 struct
55 {
56 GtkWidget *parent_item;
57 GtkWidget *enable_vim_item;
58 GtkWidget *insert_for_dummies_item;
59 GtkWidget *start_in_insert_item;
60 } menu_items =
61 {
62 NULL, NULL, NULL, NULL
63 };
64
65 static gboolean start_in_insert;
66 static ViCallback cb;
67
68
get_config_filename(void)69 static gchar *get_config_filename(void)
70 {
71 return g_build_filename(geany_data->app->configdir, "plugins", PLUGIN, PLUGIN".conf", NULL);
72 }
73
74
load_config(void)75 static void load_config(void)
76 {
77 gchar *filename = get_config_filename();
78 GKeyFile *kf = g_key_file_new();
79
80 if (g_key_file_load_from_file(kf, filename, G_KEY_FILE_NONE, NULL))
81 {
82 vi_set_enabled(utils_get_setting_boolean(kf, CONF_GROUP, CONF_ENABLE_VIM, TRUE));
83 vi_set_insert_for_dummies(utils_get_setting_boolean(kf,
84 CONF_GROUP, CONF_INSERT_FOR_DUMMIES, FALSE));
85 start_in_insert = utils_get_setting_boolean(kf,
86 CONF_GROUP, CONF_START_IN_INSERT, FALSE);
87 }
88
89 g_key_file_free(kf);
90 g_free(filename);
91 }
92
93
save_config(void)94 static void save_config(void)
95 {
96 GKeyFile *kf = g_key_file_new();
97 gchar *filename = get_config_filename();
98 gchar *dirname = g_path_get_dirname(filename);
99 gchar *data;
100 gsize length;
101
102 g_key_file_set_boolean(kf, CONF_GROUP, CONF_ENABLE_VIM, vi_get_enabled());
103 g_key_file_set_boolean(kf, CONF_GROUP, CONF_INSERT_FOR_DUMMIES, vi_get_insert_for_dummies());
104 g_key_file_set_boolean(kf, CONF_GROUP, CONF_START_IN_INSERT, start_in_insert);
105
106 utils_mkdir(dirname, TRUE);
107 data = g_key_file_to_data(kf, &length, NULL);
108 g_file_set_contents(filename, data, length, NULL);
109
110 g_free(data);
111 g_key_file_free(kf);
112 g_free(filename);
113 g_free(dirname);
114 }
115
116
on_enable_vim_mode(void)117 static void on_enable_vim_mode(void)
118 {
119 gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item));
120 vi_set_enabled(enabled);
121 vi_set_mode(start_in_insert ? VI_MODE_INSERT : VI_MODE_COMMAND);
122 if (!enabled)
123 ui_set_statusbar(FALSE, "Vim Mode Disabled");
124 save_config();
125 }
126
127
on_enable_vim_mode_kb(GeanyKeyBinding * kb,guint key_id,gpointer data)128 static gboolean on_enable_vim_mode_kb(GeanyKeyBinding *kb, guint key_id, gpointer data)
129 {
130 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item),
131 !vi_get_enabled());
132 return TRUE;
133 }
134
135
on_insert_for_dummies(void)136 static void on_insert_for_dummies(void)
137 {
138 gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item));
139 vi_set_insert_for_dummies(enabled);
140 ui_set_statusbar(FALSE, _("Insert Mode for Dummies: %s"), enabled ? _("ON") : _("OFF"));
141 save_config();
142 }
143
144
on_insert_for_dummies_kb(GeanyKeyBinding * kb,guint key_id,gpointer data)145 static gboolean on_insert_for_dummies_kb(GeanyKeyBinding *kb, guint key_id, gpointer data)
146 {
147 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item),
148 !vi_get_insert_for_dummies());
149 return TRUE;
150 }
151
152
on_start_in_insert(void)153 static void on_start_in_insert(void)
154 {
155 gboolean enabled = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_items.start_in_insert_item));
156 start_in_insert = enabled;
157 save_config();
158 }
159
160
on_doc_open(G_GNUC_UNUSED GObject * obj,GeanyDocument * doc,G_GNUC_UNUSED gpointer user_data)161 static void on_doc_open(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc,
162 G_GNUC_UNUSED gpointer user_data)
163 {
164 g_return_if_fail(doc != NULL);
165 vi_set_active_sci(doc->editor->sci);
166 }
167
168
on_doc_activate(G_GNUC_UNUSED GObject * obj,GeanyDocument * doc,G_GNUC_UNUSED gpointer user_data)169 static void on_doc_activate(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc,
170 G_GNUC_UNUSED gpointer user_data)
171 {
172 g_return_if_fail(doc != NULL);
173 vi_set_active_sci(doc->editor->sci);
174 }
175
176
on_doc_close(G_GNUC_UNUSED GObject * obj,GeanyDocument * doc,G_GNUC_UNUSED gpointer user_data)177 static void on_doc_close(G_GNUC_UNUSED GObject * obj, GeanyDocument * doc,
178 G_GNUC_UNUSED gpointer user_data)
179 {
180 g_return_if_fail(doc != NULL);
181 /* This makes sure we don't hold an invalid scintilla inside the plugin and
182 * that vi_set_active_sci() doesn't crash in plugin_cleanup(). Fortunately
183 * Geany calls document-close before document-activate in which case this
184 * wouldn't work correctly. */
185 vi_set_active_sci(NULL);
186 }
187
188
on_editor_notify(GObject * object,GeanyEditor * editor,SCNotification * nt,gpointer data)189 static gboolean on_editor_notify(GObject *object, GeanyEditor *editor,
190 SCNotification *nt, gpointer data)
191 {
192 return vi_notify_sci(nt);
193 }
194
195
on_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)196 static gboolean on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
197 {
198 GeanyDocument *doc = document_get_current();
199 ScintillaObject *sci = doc != NULL ? doc->editor->sci : NULL;
200
201 if (!sci || gtk_window_get_focus(GTK_WINDOW(geany->main_widgets->window)) != GTK_WIDGET(sci))
202 return FALSE;
203
204 return vi_notify_key_press(event);
205 }
206
207
208 PluginCallback plugin_callbacks[] = {
209 {"document-open", (GCallback) &on_doc_open, TRUE, NULL},
210 {"document-activate", (GCallback) &on_doc_activate, TRUE, NULL},
211 {"document-close", (GCallback) &on_doc_close, TRUE, NULL},
212 {"editor-notify", (GCallback) &on_editor_notify, TRUE, NULL},
213 {"key-press", (GCallback) &on_key_press, TRUE, NULL},
214 {NULL, NULL, FALSE, NULL}
215 };
216
217
get_mode_name(ViMode vi_mode)218 static const gchar *get_mode_name(ViMode vi_mode)
219 {
220 switch (vi_mode)
221 {
222 case VI_MODE_COMMAND:
223 return "NORMAL";
224 break;
225 case VI_MODE_COMMAND_SINGLE:
226 return "(insert)";
227 break;
228 case VI_MODE_INSERT:
229 return "INSERT";
230 break;
231 case VI_MODE_REPLACE:
232 return "REPLACE";
233 break;
234 case VI_MODE_VISUAL:
235 return "VISUAL";
236 break;
237 case VI_MODE_VISUAL_LINE:
238 return "VISUAL LINE";
239 break;
240 case VI_MODE_VISUAL_BLOCK:
241 return "VISUAL BLOCK";
242 break;
243 }
244 return "";
245 }
246
247
on_mode_change(ViMode mode)248 static void on_mode_change(ViMode mode)
249 {
250 ui_set_statusbar(FALSE, "Vim Mode: -- %s --", get_mode_name(mode));
251 }
252
253
on_save(gboolean force)254 static gboolean on_save(gboolean force)
255 {
256 GeanyDocument *doc = document_get_current();
257 if (doc != NULL)
258 return document_save_file(doc, force);
259 return TRUE;
260 }
261
262
on_save_all(gboolean force)263 static gboolean on_save_all(gboolean force)
264 {
265 gint i;
266 gboolean success = TRUE;
267 foreach_document(i)
268 success = success && document_save_file(documents[i], force);
269 return success;
270 }
271
272
on_quit(gboolean force)273 static void on_quit(gboolean force)
274 {
275 //TODO: we need to extend Geany API for this
276 }
277
278
plugin_init(GeanyData * data)279 void plugin_init(GeanyData *data)
280 {
281 GeanyDocument *doc = document_get_current();
282 GeanyKeyGroup *group;
283 GtkWidget *menu;
284
285 load_config();
286
287 /* menu items and keybindings */
288 group = plugin_set_key_group(geany_plugin, "vimode", KB_COUNT, NULL);
289
290 menu_items.parent_item = gtk_menu_item_new_with_mnemonic(_("_Vim Mode"));
291 gtk_container_add(GTK_CONTAINER(geany->main_widgets->tools_menu), menu_items.parent_item);
292
293 menu = gtk_menu_new ();
294 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_items.parent_item), menu);
295
296 menu_items.enable_vim_item = gtk_check_menu_item_new_with_mnemonic(_("Enable _Vim Mode"));
297 gtk_container_add(GTK_CONTAINER(menu), menu_items.enable_vim_item);
298 g_signal_connect((gpointer) menu_items.enable_vim_item, "activate", G_CALLBACK(on_enable_vim_mode), NULL);
299 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.enable_vim_item), vi_get_enabled());
300 keybindings_set_item_full(group, KB_ENABLE_VIM, 0, 0, "enable_vim",
301 _("Enable Vim Mode"), NULL, on_enable_vim_mode_kb, NULL, NULL);
302
303 menu_items.insert_for_dummies_item = gtk_check_menu_item_new_with_mnemonic(_("Insert Mode for _Dummies"));
304 gtk_container_add(GTK_CONTAINER(menu), menu_items.insert_for_dummies_item);
305 g_signal_connect((gpointer) menu_items.insert_for_dummies_item, "activate",
306 G_CALLBACK(on_insert_for_dummies), NULL);
307 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.insert_for_dummies_item), vi_get_insert_for_dummies());
308 keybindings_set_item_full(group, KB_INSERT_FOR_DUMMIES, 0, 0, "insert_for_dummies",
309 _("Insert Mode for Dummies"), NULL, on_insert_for_dummies_kb, NULL, NULL);
310
311 menu_items.start_in_insert_item = gtk_check_menu_item_new_with_mnemonic(_("Start in _Insert Mode"));
312 gtk_container_add(GTK_CONTAINER(menu), menu_items.start_in_insert_item);
313 g_signal_connect((gpointer) menu_items.start_in_insert_item, "activate",
314 G_CALLBACK(on_start_in_insert), NULL);
315 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_items.start_in_insert_item), start_in_insert);
316
317 gtk_widget_show_all(menu_items.parent_item);
318
319 cb.on_mode_change = on_mode_change;
320 cb.on_save = on_save;
321 cb.on_save_all = on_save_all;
322 cb.on_quit = on_quit;
323 vi_init(geany_data->main_widgets->window, &cb);
324 vi_set_mode(start_in_insert ? VI_MODE_INSERT : VI_MODE_COMMAND);
325
326 if (doc)
327 vi_set_active_sci(doc->editor->sci);
328 }
329
330
plugin_cleanup(void)331 void plugin_cleanup(void)
332 {
333 vi_cleanup();
334 gtk_widget_destroy(menu_items.parent_item);
335 }
336
337
plugin_help(void)338 void plugin_help(void)
339 {
340 utils_open_browser("http://plugins.geany.org/vimode.html");
341 }
342