1 /*
2  *      autoclose.c
3  *
4  *      Copyright 2013 Pavel Roschin <rpg89(at)post(dot)ru>
5  *
6  *      This program is free software; you can redistribute it and/or modify
7  *      it under the terms of the GNU General Public License as published by
8  *      the Free Software Foundation; either version 2 of the License, or
9  *      (at your option) any later version.
10  *
11  *      This program is distributed in the hope that it will be useful,
12  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *      GNU General Public License for more details.
15  *      You should have received a copy of the GNU General Public License
16  *      along with this program; if not, write to the Free Software
17  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
18  *      MA 02110-1301, USA.
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 AC_STOP_ACTION TRUE
39 #define AC_CONTINUE_ACTION FALSE
40 #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l)
41 
42 GeanyPlugin	*geany_plugin;
43 GeanyData	*geany_data;
44 
45 
46 typedef struct {
47 	/* close chars */
48 	gboolean parenthesis;
49 	gboolean abracket;
50 	gboolean abracket_htmlonly;
51 	gboolean cbracket;
52 	gboolean sbracket;
53 	gboolean dquote;
54 	gboolean squote;
55 	gboolean backquote;
56 	gboolean backquote_bashonly;
57 	/* settings */
58 	gboolean delete_pairing_brace;
59 	gboolean suppress_doubling;
60 	gboolean enclose_selections;
61 	gboolean comments_ac_enable;
62 	gboolean comments_enclose;
63 	gboolean keep_selection;
64 	gboolean make_indent_for_cbracket;
65 	gboolean move_cursor_to_beginning;
66 	gboolean improved_cbracket_indent;
67 	gboolean whitesmiths_style;
68 	gboolean close_functions;
69 	gboolean bcksp_remove_pair;
70 	gboolean jump_on_tab;
71 	/* others */
72 	gchar *config_file;
73 } AutocloseInfo;
74 
75 static AutocloseInfo *ac_info = NULL;
76 
77 typedef struct {
78 	/* used to place the caret after autoclosed items on tab (similar to eclipse) */
79 	gint jump_on_tab;
80 	/* used to reset jump_on_tab when needed */
81 	gint last_caret;
82 	/* used to reset jump_on_tab when needed */
83 	gint last_line;
84 	struct GeanyDocument *doc;
85 } AutocloseUserData;
86 
87 static gint
get_indent(ScintillaObject * sci,gint line)88 get_indent(ScintillaObject *sci, gint line)
89 {
90 	return (gint) SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t) line, 0);
91 }
92 
93 static gchar
char_at(ScintillaObject * sci,gint pos)94 char_at(ScintillaObject *sci, gint pos)
95 {
96 	return sci_get_char_at(sci, pos);
97 }
98 
99 static const gchar *
get_char_range(ScintillaObject * sci,gint start,gint length)100 get_char_range(ScintillaObject *sci, gint start, gint length)
101 {
102 	return (const gchar *) SSM(sci, SCI_GETRANGEPOINTER, start, length);
103 }
104 
105 static gboolean
blank_line(ScintillaObject * sci,gint line)106 blank_line(ScintillaObject *sci, gint line)
107 {
108 	return get_indent(sci, line) ==
109 		sci_get_line_end_position(sci, line);
110 }
111 
112 static void
unindent_line(ScintillaObject * sci,gint line,gint indent_width)113 unindent_line(ScintillaObject *sci, gint line, gint indent_width)
114 {
115 	gint indent = sci_get_line_indentation(sci, line);
116 	sci_set_line_indentation(sci, line, indent > 0 ? indent - indent_width : 0);
117 }
118 
119 static void
delete_line(ScintillaObject * sci,gint line)120 delete_line(ScintillaObject *sci, gint line)
121 {
122 	gint start = sci_get_position_from_line(sci, line);
123 	gint len = sci_get_line_length(sci, line);
124 	SSM(sci, SCI_DELETERANGE, start, len);
125 }
126 
127 static gint
get_lines_selected(ScintillaObject * sci)128 get_lines_selected(ScintillaObject *sci)
129 {
130 	gint start = (gint) SSM(sci, SCI_GETSELECTIONSTART, 0, 0);
131 	gint end = (gint) SSM(sci, SCI_GETSELECTIONEND, 0, 0);
132 	gint line_start;
133 	gint line_end;
134 
135 	if (start == end)
136 		return 0; /* no selection */
137 
138 	line_start = (gint) SSM(sci, SCI_LINEFROMPOSITION, (uptr_t) start, 0);
139 	line_end = (gint) SSM(sci, SCI_LINEFROMPOSITION, (uptr_t) end, 0);
140 
141 	return line_end - line_start + 1;
142 }
143 
144 static void
insert_text(ScintillaObject * sci,gint pos,const gchar * text)145 insert_text(ScintillaObject *sci, gint pos, const gchar *text)
146 {
147 	SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) text);
148 }
149 
150 static gint
get_selections(ScintillaObject * sci)151 get_selections(ScintillaObject *sci)
152 {
153 	return (gint) SSM(sci, SCI_GETSELECTIONS, 0, 0);
154 }
155 
156 static gint
get_caret_pos(ScintillaObject * sci,gint selection)157 get_caret_pos(ScintillaObject *sci, gint selection)
158 {
159 	return (gint) SSM(sci, SCI_GETSELECTIONNCARET, selection, 0);
160 }
161 
162 static gint
get_ancor_pos(ScintillaObject * sci,gint selection)163 get_ancor_pos(ScintillaObject *sci, gint selection)
164 {
165 	return (gint) SSM(sci, SCI_GETSELECTIONNANCHOR, selection, 0);
166 }
167 
168 static gboolean
char_is_quote(gchar ch)169 char_is_quote(gchar ch)
170 {
171 	return '\'' == ch || '"' == ch;
172 }
173 
174 static gboolean
char_is_curly_bracket(gchar ch)175 char_is_curly_bracket(gchar ch)
176 {
177 	return '{' == ch || '}' == ch;
178 }
179 
180 static gboolean
isspace_no_newline(gchar ch)181 isspace_no_newline(gchar ch)
182 {
183 	return g_ascii_isspace(ch) && ch != '\n' && ch != '\r';
184 }
185 
186 /**
187  * This function is based on Geany's source but has different meaning: check
188  * ability to enclose selection. Calls only for selected text so using
189  * sci_get_selection_start/end is ok here.
190  * */
191 static gboolean
lexer_has_braces(ScintillaObject * sci,gint lexer)192 lexer_has_braces(ScintillaObject *sci, gint lexer)
193 {
194 	gint sel_start;
195 	switch (lexer)
196 	{
197 		case SCLEX_CPP:
198 		case SCLEX_D:
199 		case SCLEX_PASCAL:
200 		case SCLEX_TCL:
201 		case SCLEX_CSS:
202 			return TRUE;
203 		case SCLEX_HTML:	/* for PHP & JS */
204 		case SCLEX_PERL:
205 		case SCLEX_BASH:
206 			/* PHP, Perl, bash has vars like ${var} */
207 			if (get_lines_selected(sci) > 1)
208 				return TRUE;
209 			sel_start = sci_get_selection_start(sci);
210 			if ('$' == char_at(sci, sel_start - 1))
211 				return FALSE;
212 			return TRUE;
213 		default:
214 			return FALSE;
215 	}
216 }
217 
218 static gboolean
lexer_cpp_like(gint lexer,gint style)219 lexer_cpp_like(gint lexer, gint style)
220 {
221 	if (lexer == SCLEX_CPP && style == SCE_C_IDENTIFIER)
222 		return TRUE;
223 	return FALSE;
224 }
225 
226 static gboolean
filetype_c_or_cpp(gint type)227 filetype_c_or_cpp(gint type)
228 {
229 	return type == GEANY_FILETYPES_C || type == GEANY_FILETYPES_CPP;
230 }
231 
232 static gboolean
filetype_cpp(gint type)233 filetype_cpp(gint type)
234 {
235 	return type == GEANY_FILETYPES_CPP;
236 }
237 
238 static gint
get_end_pos(ScintillaObject * sci,gint line)239 get_end_pos(ScintillaObject *sci, gint line)
240 {
241 	gint end;
242 	gchar ch;
243 	end = sci_get_line_end_position(sci, line);
244 	ch = char_at(sci, end - 1);
245 	/* ignore spaces and "}" */
246 	while (isspace_no_newline(ch) || '}' == ch)
247 	{
248 		end--;
249 		ch = char_at(sci, end - 1);
250 	}
251 	return end;
252 }
253 
254 static gboolean
check_chars(ScintillaObject * sci,gint ch,gchar * chars_left,gchar * chars_right)255 check_chars(
256 	ScintillaObject *sci,
257 	gint             ch,
258 	gchar           *chars_left,
259 	gchar           *chars_right)
260 {
261 	switch (ch)
262 	{
263 		case '(':
264 		case ')':
265 			if (!ac_info->parenthesis)
266 				return FALSE;
267 			*chars_left = '(';
268 			*chars_right = ')';
269 			break;
270 		case ';':
271 			if (!ac_info->close_functions)
272 				return FALSE;
273 			break;
274 		case '{':
275 		case '}':
276 			if (!ac_info->cbracket)
277 				return FALSE;
278 			*chars_left = '{';
279 			*chars_right = '}';
280 			break;
281 		case '[':
282 		case ']':
283 			if (!ac_info->sbracket)
284 				return FALSE;
285 			*chars_left = '[';
286 			*chars_right = ']';
287 			break;
288 		case '<':
289 		case '>':
290 			if (!ac_info->abracket)
291 				return FALSE;
292 			if (ac_info->abracket_htmlonly &&
293 					sci_get_lexer(sci) != SCLEX_HTML)
294 				return FALSE;
295 			*chars_left = '<';
296 			*chars_right = '>';
297 			break;
298 		case '\'':
299 			if (!ac_info->squote)
300 				return FALSE;
301 			*chars_left = *chars_right = ch;
302 			break;
303 		case '"':
304 			if (!ac_info->dquote)
305 				return FALSE;
306 			*chars_left = *chars_right = ch;
307 			break;
308 		case '`':
309 			if (!ac_info->backquote)
310 				return FALSE;
311 			if (ac_info->backquote_bashonly &&
312 					sci_get_lexer(sci) != SCLEX_BASH)
313 				return FALSE;
314 			*chars_left = *chars_right = ch;
315 			break;
316 		default:
317 			return FALSE;
318 	}
319 	return TRUE;
320 }
321 
322 static gboolean
improve_indent(ScintillaObject * sci,GeanyEditor * editor,gint pos)323 improve_indent(
324 	ScintillaObject *sci,
325 	GeanyEditor     *editor,
326 	gint             pos)
327 {
328 	gint ch, ch_next;
329 	gint line;
330 	gint indent, indent_width;
331 	gint end_pos;
332 	if (!ac_info->improved_cbracket_indent)
333 		return AC_CONTINUE_ACTION;
334 	ch = char_at(sci, pos - 1);
335 	if (ch != '{')
336 		return AC_CONTINUE_ACTION;
337 	/* if curly bracket completion is enabled - just make indents
338 	 * but ensure that second "}" exists. If disabled - make indent
339 	 * and complete second curly bracket */
340 	ch_next = char_at(sci, pos);
341 	if (ac_info->cbracket && ch_next != '}')
342 		return AC_CONTINUE_ACTION;
343 	line = sci_get_line_from_position(sci, pos);
344 	indent = sci_get_line_indentation(sci, line);
345 	indent_width = editor_get_indent_prefs(editor)->width;
346 	sci_start_undo_action(sci);
347 	if (ac_info->cbracket)
348 		SSM(sci, SCI_ADDTEXT, 2, (sptr_t)"\n\n");
349 	else
350 		SSM(sci, SCI_ADDTEXT, 3, (sptr_t)"\n\n}");
351 	if (ac_info->whitesmiths_style)
352 	{
353 		sci_set_line_indentation(sci, line,     indent);
354 		sci_set_line_indentation(sci, line + 1, indent);
355 		sci_set_line_indentation(sci, line + 2, indent);
356 	}
357 	else
358 	{
359 		sci_set_line_indentation(sci, line + 1, indent + indent_width);
360 		sci_set_line_indentation(sci, line + 2, indent);
361 	}
362 	/* move to the end of added line */
363 	end_pos = sci_get_line_end_position(sci, line + 1);
364 	sci_set_current_position(sci, end_pos, TRUE);
365 	sci_end_undo_action(sci);
366 	/* do not alow internal auto-indenter to do the work */
367 	return AC_STOP_ACTION;
368 }
369 
370 
371 /** Find previous string/last occurence from a position
372  *
373  * The function finds the last occurence of @a text before the given
374  * position @a pos. It will only search the range of @a max before
375  * @a pos. @a length gives the length of the string at @a text. @a text
376  * does not need to be NULL terminated.
377  *
378  * @param sci	 The ScintillaObject
379  * @param pos	 The position to search backwards from (excluding pos)
380  * @param max	 Maximum number of chars to search through (the function
381  *				 will search the range from pos to pos-max)
382  * @param length Length of the search text
383  * @param text	 Text to search for
384  * @returns The position where the text was found or -1 if not found
385  *
386  **/
ao_sci_find_prev_string(ScintillaObject * sci,gint pos,gint max,gint length,gchar * text)387 static gint ao_sci_find_prev_string(ScintillaObject *sci, gint pos, gint max, gint length, gchar *text)
388 {
389 	gint start = 0;
390 	gint found;
391 
392 	if (pos == 0)
393 	{
394 		return -1;
395 	}
396 	if (pos >= max)
397 	{
398 		start = pos - max;
399 	}
400 	scintilla_send_message(sci, SCI_SETTARGETRANGE, pos-1, start);
401 	found = scintilla_send_message(sci, SCI_SEARCHINTARGET, length, (sptr_t)text);
402 
403 	return found;
404 }
405 
406 
407 static gboolean
handle_backspace(AutocloseUserData * data,ScintillaObject * sci,gchar ch,gchar * ch_left,gchar * ch_right,GdkEventKey * event,gint indent_width)408 handle_backspace(
409 	AutocloseUserData *data,
410 	ScintillaObject   *sci,
411 	gchar              ch,
412 	gchar             *ch_left,
413 	gchar             *ch_right,
414 	GdkEventKey       *event,
415 	gint               indent_width)
416 {
417 	gint pos = sci_get_current_position(sci);
418 	gint end_pos;
419 	gint line_start, line_end, line;
420 	gint i;
421 	if (!ac_info->delete_pairing_brace)
422 		return AC_CONTINUE_ACTION;
423 	ch = char_at(sci, pos - 1);
424 
425 	if (!check_chars(sci, ch, ch_left, ch_right))
426 		return AC_CONTINUE_ACTION;
427 
428 	if (event->state & GDK_SHIFT_MASK)
429 	{
430 		if ((ch_left[0] == ch || ch_right[0] == ch) &&
431 				ac_info->bcksp_remove_pair)
432 		{
433 			if (ch_left[0] != ch_right[0])
434 			{
435 				end_pos = sci_find_matching_brace(sci, pos - 1);
436 			}
437 			else
438 			{
439 				end_pos = ao_sci_find_prev_string(sci, pos, pos, 1, ch_left);
440 			}
441 			if (-1 == end_pos)
442 				return AC_CONTINUE_ACTION;
443 			sci_start_undo_action(sci);
444 			line_start = sci_get_line_from_position(sci, pos);
445 			line_end = sci_get_line_from_position(sci, end_pos);
446 			SSM(sci, SCI_DELETERANGE, end_pos, 1);
447 			if (end_pos < pos)
448 				pos--;
449 			SSM(sci, SCI_DELETERANGE, pos - 1, 1);
450 			/* remove indentation magick */
451 			if (char_is_curly_bracket(ch))
452 			{
453 				if (line_start == line_end)
454 					goto final;
455 				if (line_start > line_end)
456 				{
457 					line = line_end;
458 					line_end = line_start;
459 					line_start = line;
460 				}
461 				if (blank_line(sci, line_start))
462 				{
463 					delete_line(sci, line_start);
464 					line_end--;
465 				}
466 				else
467 					line_start++;
468 				if (blank_line(sci, line_end))
469 					delete_line(sci, line_end);
470 				line_end--;
471 				/* unindent */
472 				for (i = line_start; i <= line_end; i++)
473 				{
474 					unindent_line(sci, i, indent_width);
475 				}
476 			}
477 final:
478 			sci_end_undo_action(sci);
479 			return AC_STOP_ACTION;
480 		}
481 	}
482 
483 	/* handle \'|' situation */
484 	if (char_is_quote(ch) && char_at(sci, pos - 2) == '\\')
485 		return AC_CONTINUE_ACTION;
486 
487 	if (ch_left[0] == ch && ch_right[0] == char_at(sci, pos))
488 	{
489 		SSM(sci, SCI_DELETERANGE, pos, 1);
490 		data->jump_on_tab = 0;
491 		return AC_CONTINUE_ACTION;
492 	}
493 	return AC_CONTINUE_ACTION;
494 }
495 
496 
497 static gboolean
enclose_selection(AutocloseUserData * data,ScintillaObject * sci,gchar ch,gint lexer,gint style,gchar * chars_left,gchar * chars_right,GeanyEditor * editor)498 enclose_selection(
499 	AutocloseUserData *data,
500 	ScintillaObject   *sci,
501 	gchar              ch,
502 	gint               lexer,
503 	gint               style,
504 	gchar             *chars_left,
505 	gchar             *chars_right,
506 	GeanyEditor       *editor)
507 {
508 	gint       i;
509 	gint       start, end;
510 	gboolean   in_comment;
511 	gint       start_line, start_pos, end_line, text_end_pos;
512 	gint       start_indent, indent_width, current_indent;
513 
514 	start = sci_get_selection_start(sci);
515 	end = sci_get_selection_end(sci);
516 
517 	/* case if selection covers mixed style */
518 	if (highlighting_is_code_style(lexer, sci_get_style_at(sci, start)) !=
519 		 highlighting_is_code_style(lexer, sci_get_style_at(sci, end)))
520 		in_comment = FALSE;
521 	else
522 		in_comment = !highlighting_is_code_style(lexer, style);
523 	if (!ac_info->comments_enclose && in_comment)
524 		return AC_CONTINUE_ACTION;
525 
526 	sci_start_undo_action(sci);
527 
528 
529 	/* Insert {} block - special case: make indents, move cursor to beginning */
530 	if (char_is_curly_bracket(ch) && lexer_has_braces(sci, lexer) &&
531 			ac_info->make_indent_for_cbracket && !in_comment)
532 	{
533 		start_line = sci_get_line_from_position(sci, start);
534 		start_pos = SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t)start_line, 0);
535 		insert_text(sci, start_pos, "{\n");
536 
537 		end_line = sci_get_line_from_position(sci, end);
538 		start_indent = sci_get_line_indentation(sci, start_line);
539 		indent_width = editor_get_indent_prefs(editor)->width;
540 		sci_set_line_indentation(sci, start_line, start_indent);
541 		if (!ac_info->whitesmiths_style)
542 			sci_set_line_indentation(sci, start_line + 1, start_indent + indent_width);
543 		else
544 			sci_set_line_indentation(sci, start_line + 1, start_indent);
545 		for (i = start_line + 2; i <= end_line; i++)
546 		{
547 			current_indent = sci_get_line_indentation(sci, i);
548 			if (!ac_info->whitesmiths_style)
549 				sci_set_line_indentation(sci, i, current_indent + indent_width);
550 			else
551 				sci_set_line_indentation(sci, i, current_indent);
552 		}
553 		text_end_pos = sci_get_line_end_position(sci, i - 1);
554 		sci_set_current_position(sci, text_end_pos, FALSE);
555 		SSM(sci, SCI_ADDTEXT, 2, (sptr_t)"\n}");
556 		sci_set_line_indentation(sci, i, start_indent);
557 		if (ac_info->move_cursor_to_beginning)
558 			sci_set_current_position(sci, start_pos, TRUE);
559 	}
560 	else
561 	{
562 		gint selections = get_selections(sci);
563 		/* specially handle rectangular selection */
564 		if (selections > 1)
565 		{
566 			gint *sels_left = g_malloc(selections * sizeof(gint));
567 			gint *sels_right = g_malloc(selections * sizeof(gint));
568 			gint caret = get_caret_pos(sci, 0);
569 			gint anchor = get_ancor_pos(sci, 0);
570 			gboolean caret_is_left = caret < anchor;
571 			gint pos_first = get_caret_pos(sci, 0);
572 			gint pos_second = get_caret_pos(sci, 1);
573 			gboolean selection_is_up_down = pos_first < pos_second;
574 			gint line;
575 			/* looks like a forward loop but actually lines processed in reverse order */
576 			for (i = 0; i < selections; i++)
577 			{
578 				if (selection_is_up_down)
579 					line = selections - i - 1;
580 				else
581 					line = i;
582 				if (caret_is_left)
583 				{
584 					sels_left[i]  = get_caret_pos(sci, line);
585 					sels_right[i] = get_ancor_pos(sci, line) + 1;
586 				}
587 				else
588 				{
589 					sels_right[i] = get_caret_pos(sci, line) + 1;
590 					sels_left[i]  = get_ancor_pos(sci, line);
591 				}
592 			}
593 			for (i = 0; i < selections; i++)
594 			{
595 				insert_text(sci, sels_left[i],  chars_left);
596 				insert_text(sci, sels_right[i], chars_right);
597 				sels_left[i]  += (selections - i - 1) * 2 + 1;
598 				sels_right[i] += (selections - i - 1) * 2 + 1;
599 			}
600 			if (ac_info->keep_selection)
601 			{
602 				i = 0;
603 				SSM(sci, SCI_SETSELECTION, sels_left[i], sels_right[i] - 1);
604 				for (i = 1; i < selections; i++)
605 					SSM(sci, SCI_ADDSELECTION, sels_left[i], sels_right[i] - 1);
606 			}
607 			g_free(sels_left);
608 			g_free(sels_right);
609 		}
610 		else /* normal selection */
611 		{
612 			insert_text(sci, start, chars_left);
613 			insert_text(sci, end + 1, chars_right);
614 			sci_set_current_position(sci, end + 1, TRUE);
615 			data->jump_on_tab += strlen(chars_right);
616 			data->last_caret = end + 1;
617 			data->last_line = sci_get_current_line(sci);
618 			if (ac_info->keep_selection)
619 			{
620 				sci_set_selection_start(sci, start + 1);
621 				sci_set_selection_end(sci, end + 1);
622 			}
623 		}
624 	}
625 	sci_end_undo_action(sci);
626 	return AC_STOP_ACTION;
627 }
628 
629 static gboolean
check_struct(ScintillaObject * sci,gint pos,const gchar * str)630 check_struct(
631 	ScintillaObject *sci,
632 	gint             pos,
633 	const gchar     *str)
634 {
635 	gchar ch;
636 	gint line, len;
637 	ch = char_at(sci, pos - 1);
638 	while (g_ascii_isspace(ch))
639 	{
640 		pos--;
641 		ch = char_at(sci, pos - 1);
642 	}
643 	line = sci_get_line_from_position(sci, pos);
644 	len = strlen(str);
645 	const gchar *sci_buf = get_char_range(sci, get_indent(sci, line), len);
646 	g_return_val_if_fail(sci_buf, FALSE);
647 	if (strncmp(sci_buf, str, len) == 0)
648 		return TRUE;
649 	return FALSE;
650 }
651 
652 static void
struct_semicolon(ScintillaObject * sci,gint pos,gchar * chars_right,gint filetype)653 struct_semicolon(
654 	ScintillaObject *sci,
655 	gint             pos,
656 	gchar           *chars_right,
657 	gint             filetype)
658 {
659 	if (filetype_c_or_cpp(filetype) &&
660 	   (check_struct(sci, pos, "struct") || check_struct(sci, pos, "typedef struct")))
661 	{
662 		chars_right[1] = ';';
663 		return;
664 	}
665 	if (filetype_cpp(filetype) && check_struct(sci, pos, "class"))
666 	{
667 		chars_right[1] = ';';
668 		return;
669 	}
670 }
671 
672 static gboolean
check_define(ScintillaObject * sci,gint line)673 check_define(
674 	ScintillaObject *sci,
675 	gint             line)
676 {
677 	const gchar* sci_buf = get_char_range(sci, get_indent(sci, line), 7);
678 	g_return_val_if_fail(sci_buf, FALSE);
679 	if (strncmp(sci_buf, "#define", 7) == 0)
680 		return TRUE;
681 	return FALSE;
682 }
683 
684 static gboolean
auto_close_chars(AutocloseUserData * data,GdkEventKey * event)685 auto_close_chars(
686 	AutocloseUserData *data,
687 	GdkEventKey       *event)
688 {
689 	ScintillaObject *sci;
690 	GeanyEditor     *editor;
691 	GeanyDocument   *doc;
692 	gint             ch, ch_next, ch_buf;
693 	gchar            chars_left[2]  = {0, 0};
694 	gchar            chars_right[3] = {0, 0, 0};
695 	gint             lexer, style;
696 	gint             pos, line, lex_offset;
697 	gboolean         has_sel;
698 	gint             filetype = 0;
699 
700 	g_return_val_if_fail(data, AC_CONTINUE_ACTION);
701 	doc = data->doc;
702 	g_return_val_if_fail(DOC_VALID(doc), AC_CONTINUE_ACTION);
703 	editor = doc->editor;
704 	g_return_val_if_fail(editor, AC_CONTINUE_ACTION);
705 	sci = editor->sci;
706 	g_return_val_if_fail(sci, AC_CONTINUE_ACTION);
707 
708 	if (doc->file_type)
709 		filetype = doc->file_type->id;
710 
711 	pos = sci_get_current_position(sci);
712 	line = sci_get_current_line(sci);
713 	ch = event->keyval;
714 
715 	if (ch == GDK_BackSpace)
716 	{
717 		return handle_backspace(data, sci, ch, chars_left, chars_right,
718 		                        event, editor_get_indent_prefs(editor)->width);
719 	}
720 	else if (ch == GDK_Return)
721 	{
722 		return improve_indent(sci, editor, pos);
723 	}
724 	else if (ch == GDK_Tab && ac_info->jump_on_tab)
725 	{
726 		/* jump behind inserted "); */
727 		if (data->jump_on_tab == 0)
728 			return AC_CONTINUE_ACTION;
729 		sci_set_current_position(sci, pos + data->jump_on_tab, FALSE);
730 		data->jump_on_tab = 0;
731 		return AC_STOP_ACTION;
732 	}
733 
734 	/* set up completion chars */
735 	if (!check_chars(sci, ch, chars_left, chars_right))
736 		return AC_CONTINUE_ACTION;
737 
738 	has_sel = sci_has_selection(sci);
739 
740 	/* do not suppress/complete in case: '\|' */
741 	if (char_is_quote(ch) && char_at(sci, pos - 1) == '\\' && !has_sel)
742 		return AC_CONTINUE_ACTION;
743 
744 	lexer = sci_get_lexer(sci);
745 
746 	/* in C-like languages - complete functions with ; */
747 	lex_offset = -1;
748 	ch_buf = char_at(sci, pos + lex_offset);
749 	while (g_ascii_isspace(ch_buf))
750 	{
751 		--lex_offset;
752 		ch_buf = char_at(sci, pos + lex_offset);
753 	}
754 
755 	style = sci_get_style_at(sci, pos + lex_offset);
756 
757 	/* add ; after functions */
758 	if (
759 		!has_sel &&
760 		ac_info->close_functions &&
761 		chars_left[0] == '(' &&
762 		lexer_cpp_like(lexer, style) &&
763 		pos == get_end_pos(sci, line) &&
764 		sci_get_line_indentation(sci, line) != 0 &&
765 		!check_define(sci, line)
766 	)
767 		chars_right[1] = ';';
768 
769 	style = sci_get_style_at(sci, pos);
770 
771 	/* suppress double completion symbols */
772 	ch_next = char_at(sci, pos);
773 	if (ch == ch_next && !has_sel && ac_info->suppress_doubling &&
774 	  !(chars_left[0] != chars_right[0] && ch == chars_left[0]))
775 	{
776 		/* jump_on_data may be 2 (due to autoclosing ");"). Need to decrement if ")" is pressed */
777 		if (data->jump_on_tab > 0)
778 			data->jump_on_tab -= 1;
779 		if ((!ac_info->comments_ac_enable && !highlighting_is_code_style(lexer, style)) &&
780 		      ch != '"' && ch != '\'')
781 			return AC_CONTINUE_ACTION;
782 
783 		/* suppress ; only at end of line */
784 		if (ch == ';' && pos + 1 != get_end_pos(sci, line))
785 			return AC_CONTINUE_ACTION;
786 		SSM(sci, SCI_DELETERANGE, pos, 1);
787 		return AC_CONTINUE_ACTION;
788 	}
789 
790 	if (ch == ';')
791 		return AC_CONTINUE_ACTION;
792 
793 	/* If we have selected text */
794 	if (has_sel && ac_info->enclose_selections)
795 		return enclose_selection(data, sci, ch, lexer, style, chars_left, chars_right, editor);
796 
797 	/* disable autocompletion inside comments and strings */
798 	if (!ac_info->comments_ac_enable && !highlighting_is_code_style(lexer, style))
799 		return AC_CONTINUE_ACTION;
800 
801 	if (ch == chars_right[0] && chars_left[0] != chars_right[0])
802 		return AC_CONTINUE_ACTION;
803 
804 	/* add ; after struct */
805 	struct_semicolon(sci, pos, chars_right, filetype);
806 
807 	/* just close char */
808 	SSM(sci, SCI_INSERTTEXT, pos, (sptr_t)chars_right);
809 	sci_set_current_position(sci, pos, TRUE);
810 	data->jump_on_tab += strlen(chars_right);
811 	data->last_caret = pos;
812 	data->last_line = sci_get_current_line(sci);
813 	return AC_CONTINUE_ACTION;
814 }
815 
816 static gboolean
on_key_press(GtkWidget * widget,GdkEventKey * event,gpointer user_data)817 on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
818 {
819 	AutocloseUserData *data = user_data;
820 	g_return_val_if_fail(data && DOC_VALID(data->doc), AC_CONTINUE_ACTION);
821 	return auto_close_chars(data, event);
822 }
823 
824 static void
on_sci_notify(ScintillaObject * sci,gint scn,SCNotification * nt,gpointer user_data)825 on_sci_notify(ScintillaObject *sci, gint scn, SCNotification *nt, gpointer user_data)
826 {
827 	AutocloseUserData *data = user_data;
828 
829 	if (!ac_info->jump_on_tab)
830 		return;
831 	g_return_if_fail(data);
832 
833 	/* reset jump_on_tab state when user clicked away */
834 	gboolean updated_sel  = nt->updated & SC_UPDATE_SELECTION;
835 	gboolean updated_text = nt->updated & SC_UPDATE_CONTENT;
836 	gint new_caret = sci_get_current_position(sci);
837 	gint new_line  = sci_get_current_line(sci);
838 	if (updated_sel && !updated_text)
839 	{
840 		gint delta = data->last_caret -  new_caret;
841 		gint delta_l = data->last_line - new_line;
842 		if (delta_l == 0 && data->jump_on_tab)
843 			data->jump_on_tab += delta;
844 		else
845 			data->jump_on_tab = 0;
846 	}
847 	data->last_caret = new_caret;
848 	data->last_line = new_line;
849 }
850 
851 static void
on_document_open(GObject * obj,GeanyDocument * doc,gpointer user_data)852 on_document_open(GObject *obj, GeanyDocument *doc, gpointer user_data)
853 {
854 	AutocloseUserData *data;
855 	ScintillaObject   *sci;
856 	g_return_if_fail(DOC_VALID(doc));
857 
858 	sci = doc->editor->sci;
859 	data = g_new0(AutocloseUserData, 1);
860 	data->doc = doc;
861 	plugin_signal_connect(geany_plugin, G_OBJECT(sci), "sci-notify",
862 		    FALSE, G_CALLBACK(on_sci_notify), data);
863 	plugin_signal_connect(geany_plugin, G_OBJECT(sci), "key-press-event",
864 			FALSE, G_CALLBACK(on_key_press), data);
865 	/* This will free the data when the sci is destroyed */
866 	g_object_set_data_full(G_OBJECT(sci), "autoclose-userdata", data, g_free);
867 }
868 
869 static PluginCallback plugin_autoclose_callbacks[] =
870 {
871 	{ "document-open",  (GCallback) &on_document_open, FALSE, NULL },
872 	{ "document-new",   (GCallback) &on_document_open, FALSE, NULL },
873 	{ NULL, NULL, FALSE, NULL }
874 };
875 
876 static void
configure_response_cb(GtkDialog * dialog,gint response,gpointer user_data)877 configure_response_cb(GtkDialog *dialog, gint response, gpointer user_data)
878 {
879 	if (response != GTK_RESPONSE_OK && response != GTK_RESPONSE_APPLY)
880 		return;
881 	GKeyFile *config = g_key_file_new();
882 	gchar    *config_dir = g_path_get_dirname(ac_info->config_file);
883 
884 	g_key_file_load_from_file(config, ac_info->config_file, G_KEY_FILE_NONE, NULL);
885 
886 #define SAVE_CONF_BOOL(name) G_STMT_START {                                    \
887     ac_info->name = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(            \
888                         g_object_get_data(G_OBJECT(dialog), "check_" #name))); \
889     g_key_file_set_boolean(config, "autoclose", #name, ac_info->name);         \
890 } G_STMT_END
891 
892 	SAVE_CONF_BOOL(parenthesis);
893 	SAVE_CONF_BOOL(abracket);
894 	SAVE_CONF_BOOL(abracket_htmlonly);
895 	SAVE_CONF_BOOL(cbracket);
896 	SAVE_CONF_BOOL(sbracket);
897 	SAVE_CONF_BOOL(dquote);
898 	SAVE_CONF_BOOL(squote);
899 	SAVE_CONF_BOOL(backquote);
900 	SAVE_CONF_BOOL(backquote_bashonly);
901 	SAVE_CONF_BOOL(comments_ac_enable);
902 	SAVE_CONF_BOOL(delete_pairing_brace);
903 	SAVE_CONF_BOOL(suppress_doubling);
904 	SAVE_CONF_BOOL(enclose_selections);
905 	SAVE_CONF_BOOL(comments_enclose);
906 	SAVE_CONF_BOOL(keep_selection);
907 	SAVE_CONF_BOOL(make_indent_for_cbracket);
908 	SAVE_CONF_BOOL(move_cursor_to_beginning);
909 	SAVE_CONF_BOOL(improved_cbracket_indent);
910 	SAVE_CONF_BOOL(whitesmiths_style);
911 	SAVE_CONF_BOOL(close_functions);
912 	SAVE_CONF_BOOL(bcksp_remove_pair);
913 	SAVE_CONF_BOOL(jump_on_tab);
914 
915 #undef SAVE_CONF_BOOL
916 
917 	if (!g_file_test(config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir(config_dir, TRUE) != 0)
918 	{
919 		dialogs_show_msgbox(GTK_MESSAGE_ERROR,
920 			_("Plugin configuration directory could not be created."));
921 	}
922 	else
923 	{
924 		/* write config to file */
925 		gchar *data;
926 		data = g_key_file_to_data(config, NULL, NULL);
927 		utils_write_file(ac_info->config_file, data);
928 		g_free(data);
929 	}
930 	g_free(config_dir);
931 	g_key_file_free(config);
932 }
933 
934 /* Called by Geany to initialize the plugin */
935 static gboolean
plugin_autoclose_init(GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)936 plugin_autoclose_init(GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
937 {
938 	guint i = 0;
939 
940 	geany_plugin = plugin;
941 	geany_data = plugin->geany_data;
942 
943 	foreach_document(i)
944 	{
945 		on_document_open(NULL, documents[i], NULL);
946 	}
947 	GKeyFile *config = g_key_file_new();
948 
949 	ac_info = g_new0(AutocloseInfo, 1);
950 
951 	ac_info->config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S,
952 		"plugins", G_DIR_SEPARATOR_S, "autoclose", G_DIR_SEPARATOR_S, "autoclose.conf", NULL);
953 
954 	g_key_file_load_from_file(config, ac_info->config_file, G_KEY_FILE_NONE, NULL);
955 
956 #define GET_CONF_BOOL(name, def) ac_info->name = utils_get_setting_boolean(config, "autoclose", #name, def)
957 
958 	GET_CONF_BOOL(parenthesis, TRUE);
959 	/* Angular bracket conflicts with conditional statements, enable only for HTML by default */
960 	GET_CONF_BOOL(abracket, TRUE);
961 	GET_CONF_BOOL(abracket_htmlonly, TRUE);
962 	GET_CONF_BOOL(cbracket, TRUE);
963 	GET_CONF_BOOL(sbracket, TRUE);
964 	GET_CONF_BOOL(dquote, TRUE);
965 	GET_CONF_BOOL(squote, TRUE);
966 	GET_CONF_BOOL(backquote, TRUE);
967 	GET_CONF_BOOL(backquote_bashonly, TRUE);
968 	GET_CONF_BOOL(comments_ac_enable, FALSE);
969 	GET_CONF_BOOL(delete_pairing_brace, TRUE);
970 	GET_CONF_BOOL(suppress_doubling, TRUE);
971 	GET_CONF_BOOL(enclose_selections, TRUE);
972 	GET_CONF_BOOL(comments_enclose, FALSE);
973 	GET_CONF_BOOL(keep_selection, TRUE);
974 	GET_CONF_BOOL(make_indent_for_cbracket, TRUE);
975 	GET_CONF_BOOL(move_cursor_to_beginning, TRUE);
976 	GET_CONF_BOOL(improved_cbracket_indent, TRUE);
977 	GET_CONF_BOOL(whitesmiths_style, FALSE);
978 	GET_CONF_BOOL(close_functions, TRUE);
979 	GET_CONF_BOOL(bcksp_remove_pair, FALSE);
980 	GET_CONF_BOOL(jump_on_tab, TRUE);
981 
982 #undef GET_CONF_BOOL
983 
984 	g_key_file_free(config);
985 	return TRUE;
986 }
987 
988 #define GET_CHECKBOX_ACTIVE(name) gboolean sens = gtk_toggle_button_get_active(\
989            GTK_TOGGLE_BUTTON(g_object_get_data(G_OBJECT(data), "check_" #name)))
990 
991 #define SET_SENS(name) gtk_widget_set_sensitive(                               \
992                         g_object_get_data(G_OBJECT(data), "check_" #name), sens)
993 
994 static void
ac_make_indent_for_cbracket_cb(GtkToggleButton * togglebutton,gpointer data)995 ac_make_indent_for_cbracket_cb(GtkToggleButton *togglebutton, gpointer data)
996 {
997 	GET_CHECKBOX_ACTIVE(make_indent_for_cbracket);
998 	SET_SENS(move_cursor_to_beginning);
999 }
1000 
1001 static void
ac_parenthesis_cb(GtkToggleButton * togglebutton,gpointer data)1002 ac_parenthesis_cb(GtkToggleButton *togglebutton, gpointer data)
1003 {
1004 	GET_CHECKBOX_ACTIVE(parenthesis);
1005 	SET_SENS(close_functions);
1006 }
1007 
1008 static void
ac_cbracket_cb(GtkToggleButton * togglebutton,gpointer data)1009 ac_cbracket_cb(GtkToggleButton *togglebutton, gpointer data)
1010 {
1011 	GET_CHECKBOX_ACTIVE(cbracket);
1012 	SET_SENS(make_indent_for_cbracket);
1013 	SET_SENS(move_cursor_to_beginning);
1014 }
1015 
1016 static void
ac_abracket_htmlonly_cb(GtkToggleButton * togglebutton,gpointer data)1017 ac_abracket_htmlonly_cb(GtkToggleButton *togglebutton, gpointer data)
1018 {
1019 	GET_CHECKBOX_ACTIVE(abracket);
1020 	SET_SENS(abracket_htmlonly);
1021 }
1022 
1023 static void
ac_backquote_bashonly_cb(GtkToggleButton * togglebutton,gpointer data)1024 ac_backquote_bashonly_cb(GtkToggleButton *togglebutton, gpointer data)
1025 {
1026 	GET_CHECKBOX_ACTIVE(backquote);
1027 	SET_SENS(backquote_bashonly);
1028 }
1029 
1030 static void
ac_enclose_selections_cb(GtkToggleButton * togglebutton,gpointer data)1031 ac_enclose_selections_cb(GtkToggleButton *togglebutton, gpointer data)
1032 {
1033 	GET_CHECKBOX_ACTIVE(enclose_selections);
1034 	SET_SENS(keep_selection);
1035 	SET_SENS(comments_enclose);
1036 }
1037 
1038 static void
ac_delete_pairing_brace_cb(GtkToggleButton * togglebutton,gpointer data)1039 ac_delete_pairing_brace_cb(GtkToggleButton *togglebutton, gpointer data)
1040 {
1041 	GET_CHECKBOX_ACTIVE(delete_pairing_brace);
1042 	SET_SENS(bcksp_remove_pair);
1043 }
1044 
1045 static GtkWidget *
plugin_autoclose_configure(G_GNUC_UNUSED GeanyPlugin * plugin,GtkDialog * dialog,G_GNUC_UNUSED gpointer pdata)1046 plugin_autoclose_configure(G_GNUC_UNUSED GeanyPlugin *plugin, GtkDialog *dialog, G_GNUC_UNUSED gpointer pdata)
1047 {
1048 	GtkWidget *widget, *vbox, *frame, *container, *scrollbox;
1049 	vbox = gtk_vbox_new(FALSE, 0);
1050 	scrollbox = gtk_scrolled_window_new(NULL, NULL);
1051 	gtk_widget_set_size_request(GTK_WIDGET(scrollbox), -1, 400);
1052 #if GTK_CHECK_VERSION(3, 8, 0)
1053 	gtk_container_add(GTK_CONTAINER(scrollbox), vbox);
1054 #else
1055 	gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrollbox), vbox);
1056 #endif
1057 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollbox),
1058 		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
1059 
1060 #define WIDGET_FRAME(description) G_STMT_START {                               \
1061     container = gtk_vbox_new(FALSE, 0);                                        \
1062     frame = gtk_frame_new(NULL);                                               \
1063     gtk_frame_set_label(GTK_FRAME(frame), description);                        \
1064     gtk_container_add(GTK_CONTAINER(frame), container);                        \
1065     gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 3);                 \
1066 } G_STMT_END
1067 
1068 #define WIDGET_CONF_BOOL(name, description, tooltip) G_STMT_START {            \
1069     widget = gtk_check_button_new_with_label(description);                     \
1070     if (tooltip) gtk_widget_set_tooltip_text(widget, tooltip);                 \
1071     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), ac_info->name);    \
1072     gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 3);           \
1073     g_object_set_data(G_OBJECT(dialog), "check_" #name, widget);               \
1074 } G_STMT_END
1075 
1076 	WIDGET_FRAME(_("Auto-close quotes and brackets"));
1077 	WIDGET_CONF_BOOL(parenthesis, _("Parenthesis ( )"),
1078 		_("Auto-close parenthesis \"(\" -> \"(|)\""));
1079 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_parenthesis_cb), dialog);
1080 	WIDGET_CONF_BOOL(cbracket, _("Curly brackets { }"),
1081 		_("Auto-close curly brackets \"{\" -> \"{|}\""));
1082 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_cbracket_cb), dialog);
1083 	WIDGET_CONF_BOOL(sbracket, _("Square brackets [ ]"),
1084 		_("Auto-close square brackets \"[\" -> \"[|]\""));
1085 	WIDGET_CONF_BOOL(abracket, _("Angular brackets < >"),
1086 		_("Auto-close angular brackets \"<\" -> \"<|>\""));
1087 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_abracket_htmlonly_cb), dialog);
1088 	WIDGET_CONF_BOOL(abracket_htmlonly, _("\tOnly for HTML"),
1089 		_("Auto-close angular brackets only in HTML documents"));
1090 	WIDGET_CONF_BOOL(dquote, _("Double quotes \" \""),
1091 		_("Auto-close double quotes \" -> \"|\""));
1092 	WIDGET_CONF_BOOL(squote, _("Single quotes \' \'"),
1093 		_("Auto-close single quotes ' -> '|'"));
1094 	WIDGET_CONF_BOOL(backquote, _("Backquote ` `"),
1095 		_("Auto-close backquote ` -> `|`"));
1096 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_backquote_bashonly_cb), dialog);
1097 	WIDGET_CONF_BOOL(backquote_bashonly, _("\tOnly for Shell-scripts (Bash)"),
1098 		_("Auto-close backquote only in Shell-scripts like Bash"));
1099 
1100 	WIDGET_FRAME(_("Improve curly brackets completion"));
1101 	WIDGET_CONF_BOOL(make_indent_for_cbracket, _("Indent when enclosing"),
1102 		_("If you select some text and press \"{\" or \"}\", plugin "
1103 		"will auto-close selected lines and make new block with indent."
1104 		"\nYou do not need to select block precisely - block enclosing "
1105 		"takes into account only lines."));
1106 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_make_indent_for_cbracket_cb), dialog);
1107 	WIDGET_CONF_BOOL(move_cursor_to_beginning, _("Move cursor to beginning"),
1108 		_("If you checked \"Indent when enclosing\", moving cursor "
1109 		"to beginning may be useful: usually you make new block "
1110 		"and need to create new statement before this block."));
1111 	WIDGET_CONF_BOOL(improved_cbracket_indent, _("Improved auto-indentation"),
1112 		_("Improved auto-indent for curly brackets: type \"{\" "
1113 		"and then press Enter - plugin will create full indented block. "
1114 		"Works without \"auto-close { }\" checkbox."));
1115 	WIDGET_CONF_BOOL(whitesmiths_style, _("\tWhitesmith's style"),
1116 		_("This style puts the brace associated with a control statement on "
1117 		"the next line, indented. Statements within the braces are indented "
1118 		"to the same level as the braces."));
1119 
1120 	container = vbox;
1121 	WIDGET_CONF_BOOL(delete_pairing_brace, _("Delete pairing character while backspacing first"),
1122 		_("Check if you want to delete pairing bracket by pressing BackSpace."));
1123 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_delete_pairing_brace_cb), dialog);
1124 	WIDGET_CONF_BOOL(suppress_doubling, _("Suppress double-completion"),
1125 		_("Check if you want to allow editor automatically fix mistypes "
1126 		"with brackets: if you type \"{}\" you will get \"{}\", not \"{}}\"."));
1127 	WIDGET_CONF_BOOL(enclose_selections, _("Enclose selections"),
1128 		_("Automatically enclose selected text by pressing just one bracket key."));
1129 	g_signal_connect(widget, "toggled", G_CALLBACK(ac_enclose_selections_cb), dialog);
1130 	WIDGET_CONF_BOOL(keep_selection, _("Keep selection when enclosing"),
1131 		_("Keep your previously selected text after enclosing."));
1132 
1133 	WIDGET_FRAME(_("Behaviour inside comments and strings"));
1134 	WIDGET_CONF_BOOL(comments_ac_enable, _("Allow auto-closing in strings and comments"),
1135 		_("Check if you want to keep auto-closing inside strings and comments too."));
1136 	WIDGET_CONF_BOOL(comments_enclose, _("Enclose selections in strings and comments"),
1137 		_("Check if you want to enclose selections inside strings and comments too."));
1138 
1139 	container = vbox;
1140 	WIDGET_CONF_BOOL(close_functions, _("Auto-complete \";\" for functions"),
1141 		_("Full function auto-closing (works only for C/C++): type \"sin(\" "
1142 		"and you will get \"sin(|);\"."));
1143 	WIDGET_CONF_BOOL(bcksp_remove_pair, _("Shift+BackSpace removes pairing brace too"),
1144 		_("Remove left and right brace while pressing Shift+BackSpace.\nTip: "
1145 		"to completely remove indented block just Shift+BackSpace first \"{\" "
1146 		"or last \"}\"."));
1147 	WIDGET_CONF_BOOL(jump_on_tab, _("Jump on Tab to enclosed char"),
1148 		_("Jump behind autoclosed items on Tab press."));
1149 
1150 #undef WIDGET_CONF_BOOL
1151 #undef WIDGET_FRAME
1152 
1153 	ac_make_indent_for_cbracket_cb(NULL, dialog);
1154 	ac_cbracket_cb(NULL, dialog);
1155 	ac_enclose_selections_cb(NULL, dialog);
1156 	ac_parenthesis_cb(NULL, dialog);
1157 	ac_abracket_htmlonly_cb(NULL, dialog);
1158 	ac_delete_pairing_brace_cb(NULL, dialog);
1159 	g_signal_connect(dialog, "response", G_CALLBACK(configure_response_cb), NULL);
1160 	gtk_widget_show_all(scrollbox);
1161 	return scrollbox;
1162 }
1163 
1164 static void
autoclose_cleanup(void)1165 autoclose_cleanup(void)
1166 {
1167 	guint i = 0;
1168 
1169 	foreach_document(i)
1170 	{
1171 		gpointer data;
1172 		ScintillaObject *sci;
1173 
1174 		sci = documents[i]->editor->sci;
1175 		data = g_object_steal_data(G_OBJECT(sci), "autoclose-userdata");
1176 		g_free(data);
1177 	}
1178 }
1179 
1180 /* Called by Geany before unloading the plugin. */
1181 static void
plugin_autoclose_cleanup(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)1182 plugin_autoclose_cleanup(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
1183 {
1184 	autoclose_cleanup();
1185 	g_free(ac_info->config_file);
1186 	g_free(ac_info);
1187 }
1188 
1189 static void
plugin_autoclose_help(G_GNUC_UNUSED GeanyPlugin * plugin,G_GNUC_UNUSED gpointer pdata)1190 plugin_autoclose_help(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata)
1191 {
1192 	utils_open_browser("http://plugins.geany.org/autoclose.html");
1193 }
1194 
1195 
1196 /* Load module */
1197 G_MODULE_EXPORT
geany_load_module(GeanyPlugin * plugin)1198 void geany_load_module(GeanyPlugin *plugin)
1199 {
1200 	/* Setup translation */
1201 	main_locale_init(LOCALEDIR, GETTEXT_PACKAGE);
1202 
1203 	/* Set metadata */
1204 	plugin->info->name = _("Auto-close");
1205 	plugin->info->description = _("Auto-close braces and brackets with lot of features");
1206 	plugin->info->version = "0.3";
1207 	plugin->info->author = "Pavel Roschin <rpg89(at)post(dot)ru>";
1208 
1209 	/* Set functions */
1210 	plugin->funcs->init = plugin_autoclose_init;
1211 	plugin->funcs->cleanup = plugin_autoclose_cleanup;
1212 	plugin->funcs->help = plugin_autoclose_help;
1213 	plugin->funcs->callbacks = plugin_autoclose_callbacks;
1214 	plugin->funcs->configure = plugin_autoclose_configure;
1215 
1216 	/* Register! */
1217 	GEANY_PLUGIN_REGISTER(plugin, 226);
1218 }
1219