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