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