1 /*      defineformat.c
2  *
3  *      Copyright 2013 Pavel Roschin <rpg89(at)post(dot)ru>
4  *
5  *      This program is free software; you can redistribute it and/or modify
6  *      it under the terms of the GNU General Public License as published by
7  *      the Free Software Foundation; either version 2 of the License, or
8  *      (at your option) any later version.
9  *
10  *      This program is distributed in the hope that it will be useful,
11  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *      GNU General Public License for more details.
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,
17  *      MA 02110-1301, USA.
18  */
19 
20 
21 #ifdef HAVE_CONFIG_H
22 	#include "config.h" /* for the gettext domain */
23 #endif
24 
25 #include <string.h>
26 #ifdef HAVE_LOCALE_H
27 	#include <locale.h>
28 #endif
29 
30 #include <gdk/gdkkeysyms.h>
31 
32 #include <geanyplugin.h>
33 #include <geany.h>
34 
35 #include "Scintilla.h"
36 #include "SciLexer.h"
37 
38 /* #define DEBUG */
39 
40 #ifdef DEBUG
41 #define dprintf(arg...) fprintf(stderr, arg)
42 #else
43 #define dprintf(...);
44 #endif
45 
46 #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l)
47 
48 GeanyPlugin		*geany_plugin;
49 GeanyData		*geany_data;
50 
51 static GArray *lines_stack = NULL;
52 
53 static gint
get_line_end(ScintillaObject * sci,gint line)54 get_line_end(ScintillaObject *sci, gint line)
55 {
56 	gint end;
57 	gchar ch;
58 	end = sci_get_line_end_position(sci, line);
59 	ch = sci_get_char_at(sci, end - 1);
60 	while(ch == ' ' || ch == '\t')
61 	{
62 		end--;
63 		ch = sci_get_char_at(sci, end - 1);
64 	}
65 	return end;
66 }
67 
68 static gint
get_indent_pos(ScintillaObject * sci,gint line)69 get_indent_pos(ScintillaObject *sci, gint line)
70 {
71 	return (gint)SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t)line, 0);
72 }
73 
74 static const gchar *
get_char_range(ScintillaObject * sci,gint start,gint length)75 get_char_range(ScintillaObject *sci, gint start, gint length)
76 {
77 	return (const gchar *) SSM(sci, SCI_GETRANGEPOINTER, start, length);
78 }
79 
80 static gboolean
inside_define(ScintillaObject * sci,gint line,gboolean newline)81 inside_define(ScintillaObject *sci, gint line, gboolean newline)
82 {
83 	gint    lexer;
84 	gint    start_pos;
85 	gint    end_pos;
86 	gchar   end_char;
87 
88 	lexer = sci_get_lexer(sci);
89 	if(lexer != SCLEX_CPP)
90 		return FALSE;
91 
92 	end_pos = get_line_end(sci, line);
93 	end_char = sci_get_char_at(sci, end_pos - 1);
94 	if(end_char != '\\')
95 	{
96 		dprintf("End char is not \\, exit\n");
97 		return FALSE;
98 	}
99 	if(newline)
100 		line--;
101 	do {
102 		line--;
103 		end_pos = get_line_end(sci, line);
104 		end_char = sci_get_char_at(sci, end_pos - 1);
105 	} while(end_char == '\\' && line >= 0);
106 	line++;
107 	dprintf("Expecting define on line %d\n", line + 1);
108 	start_pos = (gint)SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t)line, 0);
109 	end_pos = sci_get_line_end_position(sci, line);
110 	if(start_pos == end_pos)
111 	{
112 		dprintf("line empty, exit\n");
113 		return FALSE;
114 	}
115 	const gchar *start_line = get_char_range(sci, start_pos, 7);
116 	g_return_val_if_fail(NULL != start_line, FALSE);
117 	if(0 != strncmp(start_line, "#define ", strlen("#define ")))
118 	{
119 		dprintf("Start line is not \"#define\", exit\n");
120 		return FALSE;
121 	}
122 	return TRUE;
123 }
124 
125 static void
define_format_newline(ScintillaObject * sci)126 define_format_newline(ScintillaObject *sci)
127 {
128 	gint    end_pos;
129 	gint    line;
130 
131 	line = sci_get_current_line(sci);
132 	if(!inside_define(sci, line, TRUE))
133 		return;
134 	line--;
135 	end_pos = sci_get_line_end_position(sci, line);
136 	sci_insert_text(sci, end_pos,  "\\");
137 	line += 2;
138 	dprintf("Added line: %d\n", line);
139 	g_array_append_val(lines_stack, line);
140 }
141 
142 static void
define_format_line(ScintillaObject * sci,gint current_line)143 define_format_line(ScintillaObject *sci, gint current_line)
144 {
145 	gint    length;
146 	gint    first_line;
147 	gint    first_end;
148 	gint    max = geany_data->editor_prefs->long_line_column;
149 
150 	if(!inside_define(sci, current_line, FALSE))
151 		return;
152 
153 	first_line = current_line;
154 	first_end = get_line_end(sci, first_line);
155 	for (first_end--; sci_get_char_at(sci, first_end - 1) == ' '; first_end--) {}
156 	SSM(sci, SCI_DELETERANGE, first_end, sci_get_line_end_position(sci, first_line) - first_end);
157 	length = first_end - get_indent_pos(sci, first_line) + sci_get_line_indentation(sci, first_line);
158 	for(; length < max - 1; length++, first_end++)
159 		sci_insert_text(sci, first_end, " ");
160 	sci_insert_text(sci, first_end, "\\");
161 }
162 
163 static gboolean
editor_notify_cb(GObject * object,GeanyEditor * editor,SCNotification * nt,gpointer data)164 editor_notify_cb(GObject *object, GeanyEditor *editor, SCNotification *nt, gpointer data)
165 {
166 	gint i = 0, val;
167 	gint old_position = 0;
168 	gint old_lposition = 0;
169 	gint old_line = 0;
170 	gint pos;
171 	if(NULL == editor || NULL == editor->sci)
172 		return FALSE;
173 	if(nt->nmhdr.code == SCN_CHARADDED)
174 	{
175 		if('\n' == nt->ch)
176 			define_format_newline(editor->sci);
177 	}
178 	if(nt->nmhdr.code == SCN_UPDATEUI)
179 	{
180 		if(g_array_index(lines_stack, gint, 0))
181 		{
182 			/* save current position */
183 			old_line = sci_get_current_line(editor->sci);
184 			old_lposition = sci_get_line_end_position(editor->sci, old_line) - sci_get_line_length(editor->sci, old_line);
185 			old_position = sci_get_current_position(editor->sci);
186 			sci_start_undo_action(editor->sci);
187 		}
188 		while((val = g_array_index(lines_stack, gint, i)))
189 		{
190 			i++;
191 			define_format_line(editor->sci, val - 1);
192 			dprintf("Removed from stack: %d\n", val);
193 		}
194 		if(i > 0)
195 		{
196 			sci_end_undo_action(editor->sci);
197 			g_array_remove_range(lines_stack, 0, i);
198 			/* restore current position */
199 			pos = sci_get_line_end_position(editor->sci, old_line) - sci_get_line_length(editor->sci, old_line);
200 			sci_set_current_position(editor->sci, old_position + pos - old_lposition, FALSE);
201 		}
202 	}
203 	if(nt->nmhdr.code == SCN_MODIFIED)
204 	{
205 		if(nt->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
206 		{
207 			if(nt->modificationType & (SC_PERFORMED_UNDO | SC_PERFORMED_REDO))
208 				return FALSE;
209 			gint line = sci_get_line_from_position(editor->sci, nt->position) + 1;
210 			if(sci_get_char_at(editor->sci, get_line_end(editor->sci, line - 1) - 1) == '\\')
211 			{
212 				gboolean found = FALSE;
213 				while((val = g_array_index(lines_stack, gint, i)))
214 				{
215 					if(val == line)
216 					{
217 						found = TRUE;
218 						break;
219 					}
220 					i++;
221 				}
222 				if(!found)
223 				{
224 					dprintf("Added line: %d\n", line);
225 					g_array_append_val(lines_stack, line);
226 				}
227 			}
228 		}
229 	}
230 	return FALSE;
231 }
232 
233 static PluginCallback plugin_defineformat_callbacks[] =
234 {
235 	{ "editor-notify", (GCallback) &editor_notify_cb, FALSE, NULL },
236 	{ NULL, NULL, FALSE, NULL }
237 };
238 
plugin_defineformat_init(GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)239 static gboolean plugin_defineformat_init(GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
240 {
241 	geany_plugin = plugin;
242 	geany_data = plugin->geany_data;
243 	lines_stack = g_array_new (TRUE, FALSE, sizeof(gint));
244 	return TRUE;
245 }
246 
plugin_defineformat_cleanup(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)247 static void plugin_defineformat_cleanup(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
248 {
249 	g_array_free(lines_stack, TRUE);
250 }
251 
plugin_defineformat_help(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)252 static void plugin_defineformat_help (G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
253 {
254 	utils_open_browser("https://plugins.geany.org/defineformat.html");
255 }
256 
257 
258 /* Load module */
259 G_MODULE_EXPORT
geany_load_module(GeanyPlugin * plugin)260 void geany_load_module(GeanyPlugin *plugin)
261 {
262 	/* Setup translation */
263 	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
264 
265 	/* Set metadata */
266 	plugin->info->name = _("Define formatter");
267 	plugin->info->description = _("Automatically align backslash in multi-line defines");
268 	plugin->info->version = "0.1";
269 	plugin->info->author = "Pavel Roschin <rpg89(at)post(dot)ru>";
270 
271 	/* Set functions */
272 	plugin->funcs->init = plugin_defineformat_init;
273 	plugin->funcs->cleanup = plugin_defineformat_cleanup;
274 	plugin->funcs->help = plugin_defineformat_help;
275 	plugin->funcs->callbacks = plugin_defineformat_callbacks;
276 
277 	/* Register! */
278 	GEANY_PLUGIN_REGISTER(plugin, 226);
279 }
280