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