Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * autoclose.c | |
| * | |
| * Copyright 2013 Pavel Roschin <rpg89(at)post(dot)ru> | |
| * | |
| * This program is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU General Public License as published by | |
| * the Free Software Foundation; either version 2 of the License, or | |
| * (at your option) any later version. | |
| * | |
| * This program is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU General Public License for more details. | |
| * You should have received a copy of the GNU General Public License | |
| * along with this program; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
| * MA 02110-1301, USA. | |
| */ | |
| #ifdef HAVE_CONFIG_H | |
| #include "config.h" /* for the gettext domain */ | |
| #endif | |
| #include <string.h> | |
| #ifdef HAVE_LOCALE_H | |
| #include <locale.h> | |
| #endif | |
| #include <gdk/gdkkeysyms.h> | |
| #include <geanyplugin.h> | |
| #include <geany.h> | |
| #include "Scintilla.h" | |
| #include "SciLexer.h" | |
| #define AC_STOP_ACTION TRUE | |
| #define AC_CONTINUE_ACTION FALSE | |
| #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l) | |
| GeanyPlugin *geany_plugin; | |
| GeanyData *geany_data; | |
| PLUGIN_VERSION_CHECK(224) | |
| PLUGIN_SET_TRANSLATABLE_INFO( | |
| LOCALEDIR, | |
| GETTEXT_PACKAGE, | |
| _("Auto-close"), | |
| _("Auto-close braces and brackets with lot of features"), | |
| "0.3", | |
| "Pavel Roschin <rpg89(at)post(dot)ru>") | |
| typedef struct { | |
| /* close chars */ | |
| gboolean parenthesis; | |
| gboolean abracket; | |
| gboolean abracket_htmlonly; | |
| gboolean cbracket; | |
| gboolean sbracket; | |
| gboolean dquote; | |
| gboolean squote; | |
| gboolean backquote; | |
| gboolean backquote_bashonly; | |
| /* settings */ | |
| gboolean delete_pairing_brace; | |
| gboolean suppress_doubling; | |
| gboolean enclose_selections; | |
| gboolean comments_ac_enable; | |
| gboolean comments_enclose; | |
| gboolean keep_selection; | |
| gboolean make_indent_for_cbracket; | |
| gboolean move_cursor_to_beginning; | |
| gboolean improved_cbracket_indent; | |
| gboolean whitesmiths_style; | |
| gboolean close_functions; | |
| gboolean bcksp_remove_pair; | |
| gboolean jump_on_tab; | |
| /* others */ | |
| gchar *config_file; | |
| } AutocloseInfo; | |
| static AutocloseInfo *ac_info = NULL; | |
| typedef struct { | |
| /* used to place the caret after autoclosed items on tab (similar to eclipse) */ | |
| gint jump_on_tab; | |
| /* used to reset jump_on_tab when needed */ | |
| gint last_caret; | |
| /* used to reset jump_on_tab when needed */ | |
| gint last_line; | |
| struct GeanyDocument *doc; | |
| } AutocloseUserData; | |
| static gint | |
| get_indent(ScintillaObject *sci, gint line) | |
| { | |
| return (gint) SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t) line, 0); | |
| } | |
| static gchar | |
| char_at(ScintillaObject *sci, gint pos) | |
| { | |
| return sci_get_char_at(sci, pos); | |
| } | |
| static const gchar * | |
| get_char_range(ScintillaObject *sci, gint start, gint length) | |
| { | |
| return (const gchar *) SSM(sci, SCI_GETRANGEPOINTER, start, length); | |
| } | |
| static gboolean | |
| blank_line(ScintillaObject *sci, gint line) | |
| { | |
| return get_indent(sci, line) == | |
| sci_get_line_end_position(sci, line); | |
| } | |
| static void | |
| unindent_line(ScintillaObject *sci, gint line, gint indent_width) | |
| { | |
| gint indent = sci_get_line_indentation(sci, line); | |
| sci_set_line_indentation(sci, line, indent > 0 ? indent - indent_width : 0); | |
| } | |
| static void | |
| delete_line(ScintillaObject *sci, gint line) | |
| { | |
| gint start = sci_get_position_from_line(sci, line); | |
| gint len = sci_get_line_length(sci, line); | |
| SSM(sci, SCI_DELETERANGE, start, len); | |
| } | |
| static gint | |
| get_lines_selected(ScintillaObject *sci) | |
| { | |
| gint start = (gint) SSM(sci, SCI_GETSELECTIONSTART, 0, 0); | |
| gint end = (gint) SSM(sci, SCI_GETSELECTIONEND, 0, 0); | |
| gint line_start; | |
| gint line_end; | |
| if (start == end) | |
| return 0; /* no selection */ | |
| line_start = (gint) SSM(sci, SCI_LINEFROMPOSITION, (uptr_t) start, 0); | |
| line_end = (gint) SSM(sci, SCI_LINEFROMPOSITION, (uptr_t) end, 0); | |
| return line_end - line_start + 1; | |
| } | |
| static void | |
| insert_text(ScintillaObject *sci, gint pos, const gchar *text) | |
| { | |
| SSM(sci, SCI_INSERTTEXT, pos, (sptr_t) text); | |
| } | |
| static gint | |
| get_selections(ScintillaObject *sci) | |
| { | |
| return (gint) SSM(sci, SCI_GETSELECTIONS, 0, 0); | |
| } | |
| static gint | |
| get_caret_pos(ScintillaObject *sci, gint selection) | |
| { | |
| return (gint) SSM(sci, SCI_GETSELECTIONNCARET, selection, 0); | |
| } | |
| static gint | |
| get_ancor_pos(ScintillaObject *sci, gint selection) | |
| { | |
| return (gint) SSM(sci, SCI_GETSELECTIONNANCHOR, selection, 0); | |
| } | |
| static gboolean | |
| char_is_quote(gchar ch) | |
| { | |
| return '\'' == ch || '"' == ch; | |
| } | |
| static gboolean | |
| char_is_curly_bracket(gchar ch) | |
| { | |
| return '{' == ch || '}' == ch; | |
| } | |
| static gboolean | |
| isspace_no_newline(gchar ch) | |
| { | |
| return g_ascii_isspace(ch) && ch != '\n' && ch != '\r'; | |
| } | |
| /** | |
| * This function is based on Geany's source but has different meaning: check | |
| * ability to enclose selection. Calls only for selected text so using | |
| * sci_get_selection_start/end is ok here. | |
| * */ | |
| static gboolean | |
| lexer_has_braces(ScintillaObject *sci, gint lexer) | |
| { | |
| gint sel_start; | |
| switch (lexer) | |
| { | |
| case SCLEX_CPP: | |
| case SCLEX_D: | |
| case SCLEX_PASCAL: | |
| case SCLEX_TCL: | |
| case SCLEX_CSS: | |
| return TRUE; | |
| case SCLEX_HTML: /* for PHP & JS */ | |
| case SCLEX_PERL: | |
| case SCLEX_BASH: | |
| /* PHP, Perl, bash has vars like ${var} */ | |
| if (get_lines_selected(sci) > 1) | |
| return TRUE; | |
| sel_start = sci_get_selection_start(sci); | |
| if ('$' == char_at(sci, sel_start - 1)) | |
| return FALSE; | |
| return TRUE; | |
| default: | |
| return FALSE; | |
| } | |
| } | |
| static gboolean | |
| lexer_cpp_like(gint lexer, gint style) | |
| { | |
| if (lexer == SCLEX_CPP && style == SCE_C_IDENTIFIER) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| static gboolean | |
| filetype_c_or_cpp(gint type) | |
| { | |
| return type == GEANY_FILETYPES_C || type == GEANY_FILETYPES_CPP; | |
| } | |
| static gboolean | |
| filetype_cpp(gint type) | |
| { | |
| return type == GEANY_FILETYPES_CPP; | |
| } | |
| static gint | |
| get_end_pos(ScintillaObject *sci, gint line) | |
| { | |
| gint end; | |
| gchar ch; | |
| end = sci_get_line_end_position(sci, line); | |
| ch = char_at(sci, end - 1); | |
| /* ignore spaces and "}" */ | |
| while (isspace_no_newline(ch) || '}' == ch) | |
| { | |
| end--; | |
| ch = char_at(sci, end - 1); | |
| } | |
| return end; | |
| } | |
| static gboolean | |
| check_chars( | |
| ScintillaObject *sci, | |
| gint ch, | |
| gchar *chars_left, | |
| gchar *chars_right) | |
| { | |
| switch (ch) | |
| { | |
| case '(': | |
| case ')': | |
| if (!ac_info->parenthesis) | |
| return FALSE; | |
| *chars_left = '('; | |
| *chars_right = ')'; | |
| break; | |
| case ';': | |
| if (!ac_info->close_functions) | |
| return FALSE; | |
| break; | |
| case '{': | |
| case '}': | |
| if (!ac_info->cbracket) | |
| return FALSE; | |
| *chars_left = '{'; | |
| *chars_right = '}'; | |
| break; | |
| case '[': | |
| case ']': | |
| if (!ac_info->sbracket) | |
| return FALSE; | |
| *chars_left = '['; | |
| *chars_right = ']'; | |
| break; | |
| case '<': | |
| case '>': | |
| if (!ac_info->abracket) | |
| return FALSE; | |
| if (ac_info->abracket_htmlonly && | |
| sci_get_lexer(sci) != SCLEX_HTML) | |
| return FALSE; | |
| *chars_left = '<'; | |
| *chars_right = '>'; | |
| break; | |
| case '\'': | |
| if (!ac_info->squote) | |
| return FALSE; | |
| *chars_left = *chars_right = ch; | |
| break; | |
| case '"': | |
| if (!ac_info->dquote) | |
| return FALSE; | |
| *chars_left = *chars_right = ch; | |
| break; | |
| case '`': | |
| if (!ac_info->backquote) | |
| return FALSE; | |
| if (ac_info->backquote_bashonly && | |
| sci_get_lexer(sci) != SCLEX_BASH) | |
| return FALSE; | |
| *chars_left = *chars_right = ch; | |
| break; | |
| default: | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| static gboolean | |
| improve_indent( | |
| ScintillaObject *sci, | |
| GeanyEditor *editor, | |
| gint pos) | |
| { | |
| gint ch, ch_next; | |
| gint line; | |
| gint indent, indent_width; | |
| gint end_pos; | |
| if (!ac_info->improved_cbracket_indent) | |
| return AC_CONTINUE_ACTION; | |
| ch = char_at(sci, pos - 1); | |
| if (ch != '{') | |
| return AC_CONTINUE_ACTION; | |
| /* if curly bracket completion is enabled - just make indents | |
| * but ensure that second "}" exists. If disabled - make indent | |
| * and complete second curly bracket */ | |
| ch_next = char_at(sci, pos); | |
| if (ac_info->cbracket && ch_next != '}') | |
| return AC_CONTINUE_ACTION; | |
| line = sci_get_line_from_position(sci, pos); | |
| indent = sci_get_line_indentation(sci, line); | |
| indent_width = editor_get_indent_prefs(editor)->width; | |
| sci_start_undo_action(sci); | |
| if (ac_info->cbracket) | |
| SSM(sci, SCI_ADDTEXT, 2, (sptr_t)"\n\n"); | |
| else | |
| SSM(sci, SCI_ADDTEXT, 3, (sptr_t)"\n\n}"); | |
| if (ac_info->whitesmiths_style) | |
| { | |
| sci_set_line_indentation(sci, line, indent); | |
| sci_set_line_indentation(sci, line + 1, indent); | |
| sci_set_line_indentation(sci, line + 2, indent); | |
| } | |
| else | |
| { | |
| sci_set_line_indentation(sci, line + 1, indent + indent_width); | |
| sci_set_line_indentation(sci, line + 2, indent); | |
| } | |
| /* move to the end of added line */ | |
| end_pos = sci_get_line_end_position(sci, line + 1); | |
| sci_set_current_position(sci, end_pos, TRUE); | |
| sci_end_undo_action(sci); | |
| /* do not alow internal auto-indenter to do the work */ | |
| return AC_STOP_ACTION; | |
| } | |
| static gboolean | |
| handle_backspace( | |
| AutocloseUserData *data, | |
| ScintillaObject *sci, | |
| gchar ch, | |
| gchar *ch_left, | |
| gchar *ch_right, | |
| GdkEventKey *event, | |
| gint indent_width) | |
| { | |
| gint pos = sci_get_current_position(sci); | |
| gint end_pos; | |
| gint line_start, line_end, line; | |
| gint i; | |
| if (!ac_info->delete_pairing_brace) | |
| return AC_CONTINUE_ACTION; | |
| ch = char_at(sci, pos - 1); | |
| if (!check_chars(sci, ch, ch_left, ch_right)) | |
| return AC_CONTINUE_ACTION; | |
| if (event->state & GDK_SHIFT_MASK) | |
| { | |
| if ((ch_left[0] == ch || ch_right[0] == ch) && | |
| ac_info->bcksp_remove_pair) | |
| { | |
| end_pos = sci_find_matching_brace(sci, pos - 1); | |
| if (-1 == end_pos) | |
| return AC_CONTINUE_ACTION; | |
| sci_start_undo_action(sci); | |
| line_start = sci_get_line_from_position(sci, pos); | |
| line_end = sci_get_line_from_position(sci, end_pos); | |
| SSM(sci, SCI_DELETERANGE, end_pos, 1); | |
| if (end_pos < pos) | |
| pos--; | |
| SSM(sci, SCI_DELETERANGE, pos - 1, 1); | |
| /* remove indentation magick */ | |
| if (char_is_curly_bracket(ch)) | |
| { | |
| if (line_start == line_end) | |
| goto final; | |
| if (line_start > line_end) | |
| { | |
| line = line_end; | |
| line_end = line_start; | |
| line_start = line; | |
| } | |
| if (blank_line(sci, line_start)) | |
| { | |
| delete_line(sci, line_start); | |
| line_end--; | |
| } | |
| else | |
| line_start++; | |
| if (blank_line(sci, line_end)) | |
| delete_line(sci, line_end); | |
| line_end--; | |
| /* unindent */ | |
| for (i = line_start; i <= line_end; i++) | |
| { | |
| unindent_line(sci, i, indent_width); | |
| } | |
| } | |
| final: | |
| sci_end_undo_action(sci); | |
| return AC_STOP_ACTION; | |
| } | |
| } | |
| /* handle \'|' situation */ | |
| if (char_is_quote(ch) && char_at(sci, pos - 2) == '\\') | |
| return AC_CONTINUE_ACTION; | |
| if (ch_left[0] == ch && ch_right[0] == char_at(sci, pos)) | |
| { | |
| SSM(sci, SCI_DELETERANGE, pos, 1); | |
| data->jump_on_tab = 0; | |
| return AC_CONTINUE_ACTION; | |
| } | |
| return AC_CONTINUE_ACTION; | |
| } | |
| static gboolean | |
| enclose_selection( | |
| AutocloseUserData *data, | |
| ScintillaObject *sci, | |
| gchar ch, | |
| gint lexer, | |
| gint style, | |
| gchar *chars_left, | |
| gchar *chars_right, | |
| GeanyEditor *editor) | |
| { | |
| gint i; | |
| gint start, end; | |
| gboolean in_comment; | |
| gint start_line, start_pos, end_line, text_end_pos; | |
| gint start_indent, indent_width, current_indent; | |
| start = sci_get_selection_start(sci); | |
| end = sci_get_selection_end(sci); | |
| /* case if selection covers mixed style */ | |
| if (highlighting_is_code_style(lexer, sci_get_style_at(sci, start)) != | |
| highlighting_is_code_style(lexer, sci_get_style_at(sci, end))) | |
| in_comment = FALSE; | |
| else | |
| in_comment = !highlighting_is_code_style(lexer, style); | |
| if (!ac_info->comments_enclose && in_comment) | |
| return AC_CONTINUE_ACTION; | |
| sci_start_undo_action(sci); | |
| /* Insert {} block - special case: make indents, move cursor to beginning */ | |
| if (char_is_curly_bracket(ch) && lexer_has_braces(sci, lexer) && | |
| ac_info->make_indent_for_cbracket && !in_comment) | |
| { | |
| start_line = sci_get_line_from_position(sci, start); | |
| start_pos = SSM(sci, SCI_GETLINEINDENTPOSITION, (uptr_t)start_line, 0); | |
| insert_text(sci, start_pos, "{\n"); | |
| end_line = sci_get_line_from_position(sci, end); | |
| start_indent = sci_get_line_indentation(sci, start_line); | |
| indent_width = editor_get_indent_prefs(editor)->width; | |
| sci_set_line_indentation(sci, start_line, start_indent); | |
| if (!ac_info->whitesmiths_style) | |
| sci_set_line_indentation(sci, start_line + 1, start_indent + indent_width); | |
| else | |
| sci_set_line_indentation(sci, start_line + 1, start_indent); | |
| for (i = start_line + 2; i <= end_line; i++) | |
| { | |
| current_indent = sci_get_line_indentation(sci, i); | |
| if (!ac_info->whitesmiths_style) | |
| sci_set_line_indentation(sci, i, current_indent + indent_width); | |
| else | |
| sci_set_line_indentation(sci, i, current_indent); | |
| } | |
| text_end_pos = sci_get_line_end_position(sci, i - 1); | |
| sci_set_current_position(sci, text_end_pos, FALSE); | |
| SSM(sci, SCI_ADDTEXT, 2, (sptr_t)"\n}"); | |
| sci_set_line_indentation(sci, i, start_indent); | |
| if (ac_info->move_cursor_to_beginning) | |
| sci_set_current_position(sci, start_pos, TRUE); | |
| } | |
| else | |
| { | |
| gint selections = get_selections(sci); | |
| /* specially handle rectangular selection */ | |
| if (selections > 1) | |
| { | |
| gint *sels_left = g_malloc(selections * sizeof(gint)); | |
| gint *sels_right = g_malloc(selections * sizeof(gint)); | |
| gint caret = get_caret_pos(sci, 0); | |
| gint anchor = get_ancor_pos(sci, 0); | |
| gboolean caret_is_left = caret < anchor; | |
| gint pos_first = get_caret_pos(sci, 0); | |
| gint pos_second = get_caret_pos(sci, 1); | |
| gboolean selection_is_up_down = pos_first < pos_second; | |
| gint line; | |
| /* looks like a forward loop but actually lines processed in reverse order */ | |
| for (i = 0; i < selections; i++) | |
| { | |
| if (selection_is_up_down) | |
| line = selections - i - 1; | |
| else | |
| line = i; | |
| if (caret_is_left) | |
| { | |
| sels_left[i] = get_caret_pos(sci, line); | |
| sels_right[i] = get_ancor_pos(sci, line) + 1; | |
| } | |
| else | |
| { | |
| sels_right[i] = get_caret_pos(sci, line) + 1; | |
| sels_left[i] = get_ancor_pos(sci, line); | |
| } | |
| } | |
| for (i = 0; i < selections; i++) | |
| { | |
| insert_text(sci, sels_left[i], chars_left); | |
| insert_text(sci, sels_right[i], chars_right); | |
| sels_left[i] += (selections - i - 1) * 2 + 1; | |
| sels_right[i] += (selections - i - 1) * 2 + 1; | |
| } | |
| if (ac_info->keep_selection) | |
| { | |
| i = 0; | |
| SSM(sci, SCI_SETSELECTION, sels_left[i], sels_right[i] - 1); | |
| for (i = 1; i < selections; i++) | |
| SSM(sci, SCI_ADDSELECTION, sels_left[i], sels_right[i] - 1); | |
| } | |
| g_free(sels_left); | |
| g_free(sels_right); | |
| } | |
| else /* normal selection */ | |
| { | |
| insert_text(sci, start, chars_left); | |
| insert_text(sci, end + 1, chars_right); | |
| sci_set_current_position(sci, end + 1, TRUE); | |
| data->jump_on_tab += strlen(chars_right); | |
| data->last_caret = end + 1; | |
| data->last_line = sci_get_current_line(sci); | |
| if (ac_info->keep_selection) | |
| { | |
| sci_set_selection_start(sci, start + 1); | |
| sci_set_selection_end(sci, end + 1); | |
| } | |
| } | |
| } | |
| sci_end_undo_action(sci); | |
| return AC_STOP_ACTION; | |
| } | |
| static gboolean | |
| check_struct( | |
| ScintillaObject *sci, | |
| gint pos, | |
| const gchar *str) | |
| { | |
| gchar ch; | |
| gint line, len; | |
| ch = char_at(sci, pos - 1); | |
| while (g_ascii_isspace(ch)) | |
| { | |
| pos--; | |
| ch = char_at(sci, pos - 1); | |
| } | |
| line = sci_get_line_from_position(sci, pos); | |
| len = strlen(str); | |
| const gchar *sci_buf = get_char_range(sci, get_indent(sci, line), len); | |
| g_return_val_if_fail(sci_buf, FALSE); | |
| if (strncmp(sci_buf, str, len) == 0) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| static void | |
| struct_semicolon( | |
| ScintillaObject *sci, | |
| gint pos, | |
| gchar *chars_right, | |
| gint filetype) | |
| { | |
| if (filetype_c_or_cpp(filetype) && | |
| (check_struct(sci, pos, "struct") || check_struct(sci, pos, "typedef struct"))) | |
| { | |
| chars_right[1] = ';'; | |
| return; | |
| } | |
| if (filetype_cpp(filetype) && check_struct(sci, pos, "class")) | |
| { | |
| chars_right[1] = ';'; | |
| return; | |
| } | |
| } | |
| static gboolean | |
| check_define( | |
| ScintillaObject *sci, | |
| gint line) | |
| { | |
| const gchar* sci_buf = get_char_range(sci, get_indent(sci, line), 7); | |
| g_return_val_if_fail(sci_buf, FALSE); | |
| if (strncmp(sci_buf, "#define", 7) == 0) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| static gboolean | |
| auto_close_chars( | |
| AutocloseUserData *data, | |
| GdkEventKey *event) | |
| { | |
| ScintillaObject *sci; | |
| GeanyEditor *editor; | |
| GeanyDocument *doc; | |
| gint ch, ch_next, ch_buf; | |
| gchar chars_left[2] = {0, 0}; | |
| gchar chars_right[3] = {0, 0, 0}; | |
| gint lexer, style; | |
| gint pos, line, lex_offset; | |
| gboolean has_sel; | |
| gint filetype = 0; | |
| g_return_val_if_fail(data, AC_CONTINUE_ACTION); | |
| doc = data->doc; | |
| g_return_val_if_fail(DOC_VALID(doc), AC_CONTINUE_ACTION); | |
| editor = doc->editor; | |
| g_return_val_if_fail(editor, AC_CONTINUE_ACTION); | |
| sci = editor->sci; | |
| g_return_val_if_fail(sci, AC_CONTINUE_ACTION); | |
| if (doc->file_type) | |
| filetype = doc->file_type->id; | |
| pos = sci_get_current_position(sci); | |
| line = sci_get_current_line(sci); | |
| ch = event->keyval; | |
| if (ch == GDK_BackSpace) | |
| { | |
| return handle_backspace(data, sci, ch, chars_left, chars_right, | |
| event, editor_get_indent_prefs(editor)->width); | |
| } | |
| else if (ch == GDK_Return) | |
| { | |
| return improve_indent(sci, editor, pos); | |
| } | |
| else if (ch == GDK_Tab && ac_info->jump_on_tab) | |
| { | |
| /* jump behind inserted "); */ | |
| if (data->jump_on_tab == 0) | |
| return AC_CONTINUE_ACTION; | |
| sci_set_current_position(sci, pos + data->jump_on_tab, FALSE); | |
| data->jump_on_tab = 0; | |
| return AC_STOP_ACTION; | |
| } | |
| /* set up completion chars */ | |
| if (!check_chars(sci, ch, chars_left, chars_right)) | |
| return AC_CONTINUE_ACTION; | |
| has_sel = sci_has_selection(sci); | |
| /* do not suppress/complete in case: '\|' */ | |
| if (char_is_quote(ch) && char_at(sci, pos - 1) == '\\' && !has_sel) | |
| return AC_CONTINUE_ACTION; | |
| lexer = sci_get_lexer(sci); | |
| /* in C-like languages - complete functions with ; */ | |
| lex_offset = -1; | |
| ch_buf = char_at(sci, pos + lex_offset); | |
| while (g_ascii_isspace(ch_buf)) | |
| { | |
| --lex_offset; | |
| ch_buf = char_at(sci, pos + lex_offset); | |
| } | |
| style = sci_get_style_at(sci, pos + lex_offset); | |
| /* add ; after functions */ | |
| if ( | |
| !has_sel && | |
| ac_info->close_functions && | |
| chars_left[0] == '(' && | |
| lexer_cpp_like(lexer, style) && | |
| pos == get_end_pos(sci, line) && | |
| sci_get_line_indentation(sci, line) != 0 && | |
| !check_define(sci, line) | |
| ) | |
| chars_right[1] = ';'; | |
| style = sci_get_style_at(sci, pos); | |
| /* suppress double completion symbols */ | |
| ch_next = char_at(sci, pos); | |
| if (ch == ch_next && !has_sel && ac_info->suppress_doubling && | |
| !(chars_left[0] != chars_right[0] && ch == chars_left[0])) | |
| { | |
| /* jump_on_data may be 2 (due to autoclosing ");"). Need to decrement if ")" is pressed */ | |
| if (data->jump_on_tab > 0) | |
| data->jump_on_tab -= 1; | |
| if ((!ac_info->comments_ac_enable && !highlighting_is_code_style(lexer, style)) && | |
| ch != '"' && ch != '\'') | |
| return AC_CONTINUE_ACTION; | |
| /* suppress ; only at end of line */ | |
| if (ch == ';' && pos + 1 != get_end_pos(sci, line)) | |
| return AC_CONTINUE_ACTION; | |
| SSM(sci, SCI_DELETERANGE, pos, 1); | |
| return AC_CONTINUE_ACTION; | |
| } | |
| if (ch == ';') | |
| return AC_CONTINUE_ACTION; | |
| /* If we have selected text */ | |
| if (has_sel && ac_info->enclose_selections) | |
| return enclose_selection(data, sci, ch, lexer, style, chars_left, chars_right, editor); | |
| /* disable autocompletion inside comments and strings */ | |
| if (!ac_info->comments_ac_enable && !highlighting_is_code_style(lexer, style)) | |
| return AC_CONTINUE_ACTION; | |
| if (ch == chars_right[0] && chars_left[0] != chars_right[0]) | |
| return AC_CONTINUE_ACTION; | |
| /* add ; after struct */ | |
| struct_semicolon(sci, pos, chars_right, filetype); | |
| /* just close char */ | |
| SSM(sci, SCI_INSERTTEXT, pos, (sptr_t)chars_right); | |
| sci_set_current_position(sci, pos, TRUE); | |
| data->jump_on_tab += strlen(chars_right); | |
| data->last_caret = pos; | |
| data->last_line = sci_get_current_line(sci); | |
| return AC_CONTINUE_ACTION; | |
| } | |
| static gboolean | |
| on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data) | |
| { | |
| AutocloseUserData *data = user_data; | |
| g_return_val_if_fail(data && DOC_VALID(data->doc), AC_CONTINUE_ACTION); | |
| return auto_close_chars(data, event); | |
| } | |
| static void | |
| on_sci_notify(ScintillaObject *sci, gint scn, SCNotification *nt, gpointer user_data) | |
| { | |
| AutocloseUserData *data = user_data; | |
| if (!ac_info->jump_on_tab) | |
| return; | |
| g_return_if_fail(data); | |
| /* reset jump_on_tab state when user clicked away */ | |
| gboolean updated_sel = nt->updated & SC_UPDATE_SELECTION; | |
| gboolean updated_text = nt->updated & SC_UPDATE_CONTENT; | |
| gint new_caret = sci_get_current_position(sci); | |
| gint new_line = sci_get_current_line(sci); | |
| if (updated_sel && !updated_text) | |
| { | |
| gint delta = data->last_caret - new_caret; | |
| gint delta_l = data->last_line - new_line; | |
| if (delta_l == 0 && data->jump_on_tab) | |
| data->jump_on_tab += delta; | |
| else | |
| data->jump_on_tab = 0; | |
| } | |
| data->last_caret = new_caret; | |
| data->last_line = new_line; | |
| } | |
| static void | |
| on_document_open(GObject *obj, GeanyDocument *doc, gpointer user_data) | |
| { | |
| AutocloseUserData *data; | |
| ScintillaObject *sci; | |
| g_return_if_fail(DOC_VALID(doc)); | |
| sci = doc->editor->sci; | |
| data = g_new0(AutocloseUserData, 1); | |
| data->doc = doc; | |
| plugin_signal_connect(geany_plugin, G_OBJECT(sci), "sci-notify", | |
| FALSE, G_CALLBACK(on_sci_notify), data); | |
| plugin_signal_connect(geany_plugin, G_OBJECT(sci), "key-press-event", | |
| FALSE, G_CALLBACK(on_key_press), data); | |
| /* This will free the data when the sci is destroyed */ | |
| g_object_set_data_full(G_OBJECT(sci), "autoclose-userdata", data, g_free); | |
| } | |
| PluginCallback plugin_callbacks[] = | |
| { | |
| { "document-open", (GCallback) &on_document_open, FALSE, NULL }, | |
| { "document-new", (GCallback) &on_document_open, FALSE, NULL }, | |
| { NULL, NULL, FALSE, NULL } | |
| }; | |
| static void | |
| configure_response_cb(GtkDialog *dialog, gint response, gpointer user_data) | |
| { | |
| if (response != GTK_RESPONSE_OK && response != GTK_RESPONSE_APPLY) | |
| return; | |
| GKeyFile *config = g_key_file_new(); | |
| gchar *config_dir = g_path_get_dirname(ac_info->config_file); | |
| g_key_file_load_from_file(config, ac_info->config_file, G_KEY_FILE_NONE, NULL); | |
| #define SAVE_CONF_BOOL(name) G_STMT_START { \ | |
| ac_info->name = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON( \ | |
| g_object_get_data(G_OBJECT(dialog), "check_" #name))); \ | |
| g_key_file_set_boolean(config, "autoclose", #name, ac_info->name); \ | |
| } G_STMT_END | |
| SAVE_CONF_BOOL(parenthesis); | |
| SAVE_CONF_BOOL(abracket); | |
| SAVE_CONF_BOOL(abracket_htmlonly); | |
| SAVE_CONF_BOOL(cbracket); | |
| SAVE_CONF_BOOL(sbracket); | |
| SAVE_CONF_BOOL(dquote); | |
| SAVE_CONF_BOOL(squote); | |
| SAVE_CONF_BOOL(backquote); | |
| SAVE_CONF_BOOL(backquote_bashonly); | |
| SAVE_CONF_BOOL(comments_ac_enable); | |
| SAVE_CONF_BOOL(delete_pairing_brace); | |
| SAVE_CONF_BOOL(suppress_doubling); | |
| SAVE_CONF_BOOL(enclose_selections); | |
| SAVE_CONF_BOOL(comments_enclose); | |
| SAVE_CONF_BOOL(keep_selection); | |
| SAVE_CONF_BOOL(make_indent_for_cbracket); | |
| SAVE_CONF_BOOL(move_cursor_to_beginning); | |
| SAVE_CONF_BOOL(improved_cbracket_indent); | |
| SAVE_CONF_BOOL(whitesmiths_style); | |
| SAVE_CONF_BOOL(close_functions); | |
| SAVE_CONF_BOOL(bcksp_remove_pair); | |
| SAVE_CONF_BOOL(jump_on_tab); | |
| #undef SAVE_CONF_BOOL | |
| if (!g_file_test(config_dir, G_FILE_TEST_IS_DIR) && utils_mkdir(config_dir, TRUE) != 0) | |
| { | |
| dialogs_show_msgbox(GTK_MESSAGE_ERROR, | |
| _("Plugin configuration directory could not be created.")); | |
| } | |
| else | |
| { | |
| /* write config to file */ | |
| gchar *data; | |
| data = g_key_file_to_data(config, NULL, NULL); | |
| utils_write_file(ac_info->config_file, data); | |
| g_free(data); | |
| } | |
| g_free(config_dir); | |
| g_key_file_free(config); | |
| } | |
| /* Called by Geany to initialize the plugin */ | |
| void | |
| plugin_init(G_GNUC_UNUSED GeanyData *data) | |
| { | |
| guint i = 0; | |
| foreach_document(i) | |
| { | |
| on_document_open(NULL, documents[i], NULL); | |
| } | |
| GKeyFile *config = g_key_file_new(); | |
| ac_info = g_new0(AutocloseInfo, 1); | |
| ac_info->config_file = g_strconcat(geany->app->configdir, G_DIR_SEPARATOR_S, | |
| "plugins", G_DIR_SEPARATOR_S, "autoclose", G_DIR_SEPARATOR_S, "autoclose.conf", NULL); | |
| g_key_file_load_from_file(config, ac_info->config_file, G_KEY_FILE_NONE, NULL); | |
| #define GET_CONF_BOOL(name, def) ac_info->name = utils_get_setting_boolean(config, "autoclose", #name, def) | |
| GET_CONF_BOOL(parenthesis, TRUE); | |
| /* Angular bracket conflicts with conditional statements, enable only for HTML by default */ | |
| GET_CONF_BOOL(abracket, TRUE); | |
| GET_CONF_BOOL(abracket_htmlonly, TRUE); | |
| GET_CONF_BOOL(cbracket, TRUE); | |
| GET_CONF_BOOL(sbracket, TRUE); | |
| GET_CONF_BOOL(dquote, TRUE); | |
| GET_CONF_BOOL(squote, TRUE); | |
| GET_CONF_BOOL(backquote, TRUE); | |
| GET_CONF_BOOL(backquote_bashonly, TRUE); | |
| GET_CONF_BOOL(comments_ac_enable, FALSE); | |
| GET_CONF_BOOL(delete_pairing_brace, TRUE); | |
| GET_CONF_BOOL(suppress_doubling, TRUE); | |
| GET_CONF_BOOL(enclose_selections, TRUE); | |
| GET_CONF_BOOL(comments_enclose, FALSE); | |
| GET_CONF_BOOL(keep_selection, TRUE); | |
| GET_CONF_BOOL(make_indent_for_cbracket, TRUE); | |
| GET_CONF_BOOL(move_cursor_to_beginning, TRUE); | |
| GET_CONF_BOOL(improved_cbracket_indent, TRUE); | |
| GET_CONF_BOOL(whitesmiths_style, FALSE); | |
| GET_CONF_BOOL(close_functions, TRUE); | |
| GET_CONF_BOOL(bcksp_remove_pair, FALSE); | |
| GET_CONF_BOOL(jump_on_tab, TRUE); | |
| #undef GET_CONF_BOOL | |
| g_key_file_free(config); | |
| } | |
| #define GET_CHECKBOX_ACTIVE(name) gboolean sens = gtk_toggle_button_get_active(\ | |
| GTK_TOGGLE_BUTTON(g_object_get_data(G_OBJECT(data), "check_" #name))) | |
| #define SET_SENS(name) gtk_widget_set_sensitive( \ | |
| g_object_get_data(G_OBJECT(data), "check_" #name), sens) | |
| static void | |
| ac_make_indent_for_cbracket_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(make_indent_for_cbracket); | |
| SET_SENS(move_cursor_to_beginning); | |
| } | |
| static void | |
| ac_parenthesis_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(parenthesis); | |
| SET_SENS(close_functions); | |
| } | |
| static void | |
| ac_cbracket_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(cbracket); | |
| SET_SENS(make_indent_for_cbracket); | |
| SET_SENS(move_cursor_to_beginning); | |
| } | |
| static void | |
| ac_abracket_htmlonly_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(abracket); | |
| SET_SENS(abracket_htmlonly); | |
| } | |
| static void | |
| ac_backquote_bashonly_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(backquote); | |
| SET_SENS(backquote_bashonly); | |
| } | |
| static void | |
| ac_enclose_selections_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(enclose_selections); | |
| SET_SENS(keep_selection); | |
| SET_SENS(comments_enclose); | |
| } | |
| static void | |
| ac_delete_pairing_brace_cb(GtkToggleButton *togglebutton, gpointer data) | |
| { | |
| GET_CHECKBOX_ACTIVE(delete_pairing_brace); | |
| SET_SENS(bcksp_remove_pair); | |
| } | |
| GtkWidget * | |
| plugin_configure(GtkDialog *dialog) | |
| { | |
| GtkWidget *widget, *vbox, *frame, *container, *scrollbox; | |
| vbox = gtk_vbox_new(FALSE, 0); | |
| scrollbox = gtk_scrolled_window_new(NULL, NULL); | |
| gtk_widget_set_size_request(GTK_WIDGET(scrollbox), -1, 400); | |
| gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrollbox), vbox); | |
| gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollbox), | |
| GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); | |
| #define WIDGET_FRAME(description) G_STMT_START { \ | |
| container = gtk_vbox_new(FALSE, 0); \ | |
| frame = gtk_frame_new(NULL); \ | |
| gtk_frame_set_label(GTK_FRAME(frame), description); \ | |
| gtk_container_add(GTK_CONTAINER(frame), container); \ | |
| gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 3); \ | |
| } G_STMT_END | |
| #define WIDGET_CONF_BOOL(name, description, tooltip) G_STMT_START { \ | |
| widget = gtk_check_button_new_with_label(description); \ | |
| if (tooltip) gtk_widget_set_tooltip_text(widget, tooltip); \ | |
| gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), ac_info->name); \ | |
| gtk_box_pack_start(GTK_BOX(container), widget, FALSE, FALSE, 3); \ | |
| g_object_set_data(G_OBJECT(dialog), "check_" #name, widget); \ | |
| } G_STMT_END | |
| WIDGET_FRAME(_("Auto-close quotes and brackets")); | |
| WIDGET_CONF_BOOL(parenthesis, _("Parenthesis ( )"), | |
| _("Auto-close parenthesis \"(\" -> \"(|)\"")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_parenthesis_cb), dialog); | |
| WIDGET_CONF_BOOL(cbracket, _("Curly brackets { }"), | |
| _("Auto-close curly brackets \"{\" -> \"{|}\"")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_cbracket_cb), dialog); | |
| WIDGET_CONF_BOOL(sbracket, _("Square brackets [ ]"), | |
| _("Auto-close square brackets \"[\" -> \"[|]\"")); | |
| WIDGET_CONF_BOOL(abracket, _("Angular brackets < >"), | |
| _("Auto-close angular brackets \"<\" -> \"<|>\"")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_abracket_htmlonly_cb), dialog); | |
| WIDGET_CONF_BOOL(abracket_htmlonly, _("\tOnly for HTML"), | |
| _("Auto-close angular brackets only in HTML documents")); | |
| WIDGET_CONF_BOOL(dquote, _("Double quotes \" \""), | |
| _("Auto-close double quotes \" -> \"|\"")); | |
| WIDGET_CONF_BOOL(squote, _("Single quotes \' \'"), | |
| _("Auto-close single quotes ' -> '|'")); | |
| WIDGET_CONF_BOOL(backquote, _("Backquote ` `"), | |
| _("Auto-close backquote ` -> `|`")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_backquote_bashonly_cb), dialog); | |
| WIDGET_CONF_BOOL(backquote_bashonly, _("\tOnly for Shell-scripts (Bash)"), | |
| _("Auto-close backquote only in Shell-scripts like Bash")); | |
| WIDGET_FRAME(_("Improve curly brackets completion")); | |
| WIDGET_CONF_BOOL(make_indent_for_cbracket, _("Indent when enclosing"), | |
| _("If you select some text and press \"{\" or \"}\", plugin " | |
| "will auto-close selected lines and make new block with indent." | |
| "\nYou do not need to select block precisely - block enclosing " | |
| "takes into account only lines.")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_make_indent_for_cbracket_cb), dialog); | |
| WIDGET_CONF_BOOL(move_cursor_to_beginning, _("Move cursor to beginning"), | |
| _("If you checked \"Indent when enclosing\", moving cursor " | |
| "to beginning may be useful: usually you make new block " | |
| "and need to create new statement before this block.")); | |
| WIDGET_CONF_BOOL(improved_cbracket_indent, _("Improved auto-indentation"), | |
| _("Improved auto-indent for curly brackets: type \"{\" " | |
| "and then press Enter - plugin will create full indented block. " | |
| "Works without \"auto-close { }\" checkbox.")); | |
| WIDGET_CONF_BOOL(whitesmiths_style, _("\tWhitesmith's style"), | |
| _("This style puts the brace associated with a control statement on " | |
| "the next line, indented. Statements within the braces are indented " | |
| "to the same level as the braces.")); | |
| container = vbox; | |
| WIDGET_CONF_BOOL(delete_pairing_brace, _("Delete pairing character while backspacing first"), | |
| _("Check if you want to delete pairing bracket by pressing BackSpace.")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_delete_pairing_brace_cb), dialog); | |
| WIDGET_CONF_BOOL(suppress_doubling, _("Suppress double-completion"), | |
| _("Check if you want to allow editor automatically fix mistypes " | |
| "with brackets: if you type \"{}\" you will get \"{}\", not \"{}}\".")); | |
| WIDGET_CONF_BOOL(enclose_selections, _("Enclose selections"), | |
| _("Automatically enclose selected text by pressing just one bracket key.")); | |
| g_signal_connect(widget, "toggled", G_CALLBACK(ac_enclose_selections_cb), dialog); | |
| WIDGET_CONF_BOOL(keep_selection, _("Keep selection when enclosing"), | |
| _("Keep your previously selected text after enclosing.")); | |
| WIDGET_FRAME(_("Behaviour inside comments and strings")); | |
| WIDGET_CONF_BOOL(comments_ac_enable, _("Allow auto-closing in strings and comments"), | |
| _("Check if you want to keep auto-closing inside strings and comments too.")); | |
| WIDGET_CONF_BOOL(comments_enclose, _("Enclose selections in strings and comments"), | |
| _("Check if you want to enclose selections inside strings and comments too.")); | |
| container = vbox; | |
| WIDGET_CONF_BOOL(close_functions, _("Auto-complete \";\" for functions"), | |
| _("Full function auto-closing (works only for C/C++): type \"sin(\" " | |
| "and you will get \"sin(|);\".")); | |
| WIDGET_CONF_BOOL(bcksp_remove_pair, _("Shift+BackSpace removes pairing brace too"), | |
| _("Remove left and right brace while pressing Shift+BackSpace.\nTip: " | |
| "to completely remove indented block just Shift+BackSpace first \"{\" " | |
| "or last \"}\".")); | |
| WIDGET_CONF_BOOL(jump_on_tab, _("Jump on Tab to enclosed char"), | |
| _("Jump behind autoclosed items on Tab press.")); | |
| #undef WIDGET_CONF_BOOL | |
| #undef WIDGET_FRAME | |
| ac_make_indent_for_cbracket_cb(NULL, dialog); | |
| ac_cbracket_cb(NULL, dialog); | |
| ac_enclose_selections_cb(NULL, dialog); | |
| ac_parenthesis_cb(NULL, dialog); | |
| ac_abracket_htmlonly_cb(NULL, dialog); | |
| ac_delete_pairing_brace_cb(NULL, dialog); | |
| g_signal_connect(dialog, "response", G_CALLBACK(configure_response_cb), NULL); | |
| gtk_widget_show_all(scrollbox); | |
| return scrollbox; | |
| } | |
| static void | |
| autoclose_cleanup(void) | |
| { | |
| guint i = 0; | |
| foreach_document(i) | |
| { | |
| gpointer data; | |
| ScintillaObject *sci; | |
| sci = documents[i]->editor->sci; | |
| data = g_object_steal_data(G_OBJECT(sci), "autoclose-userdata"); | |
| g_free(data); | |
| } | |
| } | |
| /* Called by Geany before unloading the plugin. */ | |
| void | |
| plugin_cleanup(void) | |
| { | |
| autoclose_cleanup(); | |
| g_free(ac_info->config_file); | |
| g_free(ac_info); | |
| } | |
| void | |
| plugin_help(void) | |
| { | |
| utils_open_browser("http://plugins.geany.org/autoclose.html"); | |
| } |