/* * autoclose.c * * Copyright 2013 Pavel Roschin * * 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 #ifdef HAVE_LOCALE_H #include #endif #include #include #include #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; 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; } /** Find previous string/last occurence from a position * * The function finds the last occurence of @a text before the given * position @a pos. It will only search the range of @a max before * @a pos. @a length gives the length of the string at @a text. @a text * does not need to be NULL terminated. * * @param sci The ScintillaObject * @param pos The position to search backwards from (excluding pos) * @param max Maximum number of chars to search through (the function * will search the range from pos to pos-max) * @param length Length of the search text * @param text Text to search for * @returns The position where the text was found or -1 if not found * **/ static gint ao_sci_find_prev_string(ScintillaObject *sci, gint pos, gint max, gint length, gchar *text) { gint start = 0; gint found; if (pos == 0) { return -1; } if (pos >= max) { start = pos - max; } scintilla_send_message(sci, SCI_SETTARGETRANGE, pos-1, start); found = scintilla_send_message(sci, SCI_SEARCHINTARGET, length, (sptr_t)text); return found; } 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) { if (ch_left[0] != ch_right[0]) { end_pos = sci_find_matching_brace(sci, pos - 1); } else { end_pos = ao_sci_find_prev_string(sci, pos, pos, 1, ch_left); } 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); } static PluginCallback plugin_autoclose_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 */ static gboolean plugin_autoclose_init(GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata) { guint i = 0; geany_plugin = plugin; geany_data = plugin->geany_data; 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); return TRUE; } #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); } static GtkWidget * plugin_autoclose_configure(G_GNUC_UNUSED GeanyPlugin *plugin, GtkDialog *dialog, G_GNUC_UNUSED gpointer pdata) { 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); #if GTK_CHECK_VERSION(3, 8, 0) gtk_container_add(GTK_CONTAINER(scrollbox), vbox); #else gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrollbox), vbox); #endif 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. */ static void plugin_autoclose_cleanup(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata) { autoclose_cleanup(); g_free(ac_info->config_file); g_free(ac_info); } static void plugin_autoclose_help(G_GNUC_UNUSED GeanyPlugin *plugin, G_GNUC_UNUSED gpointer pdata) { utils_open_browser("http://plugins.geany.org/autoclose.html"); } /* Load module */ G_MODULE_EXPORT void geany_load_module(GeanyPlugin *plugin) { /* Setup translation */ main_locale_init(LOCALEDIR, GETTEXT_PACKAGE); /* Set metadata */ plugin->info->name = _("Auto-close"); plugin->info->description = _("Auto-close braces and brackets with lot of features"); plugin->info->version = "0.3"; plugin->info->author = "Pavel Roschin "; /* Set functions */ plugin->funcs->init = plugin_autoclose_init; plugin->funcs->cleanup = plugin_autoclose_cleanup; plugin->funcs->help = plugin_autoclose_help; plugin->funcs->callbacks = plugin_autoclose_callbacks; plugin->funcs->configure = plugin_autoclose_configure; /* Register! */ GEANY_PLUGIN_REGISTER(plugin, 226); }