"Fossies" - the Fresh Open Source Software Archive

Member "geany-1.36/src/editor.c" (28 Sep 2019, 149232 Bytes) of package /linux/misc/geany-1.36.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "editor.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.35_vs_1.36.

    1 /*
    2  *      editor.c - this file is part of Geany, a fast and lightweight IDE
    3  *
    4  *      Copyright 2005 The Geany contributors
    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  *
   16  *      You should have received a copy of the GNU General Public License along
   17  *      with this program; if not, write to the Free Software Foundation, Inc.,
   18  *      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
   19  */
   20 
   21 /**
   22  * @file editor.h
   23  * Editor-related functions for @ref GeanyEditor.
   24  * Geany uses the Scintilla editing widget, and this file is mostly built around
   25  * Scintilla's functionality.
   26  * @see sciwrappers.h.
   27  */
   28 /* Callbacks for the Scintilla widget (ScintillaObject).
   29  * Most important is the sci-notify callback, handled in on_editor_notification().
   30  * This includes auto-indentation, comments, auto-completion, calltips, etc.
   31  * Also some general Scintilla-related functions.
   32  */
   33 
   34 #ifdef HAVE_CONFIG_H
   35 # include "config.h"
   36 #endif
   37 
   38 #include "editor.h"
   39 
   40 #include "app.h"
   41 #include "callbacks.h"
   42 #include "dialogs.h"
   43 #include "documentprivate.h"
   44 #include "filetypesprivate.h"
   45 #include "geanyobject.h"
   46 #include "highlighting.h"
   47 #include "keybindings.h"
   48 #include "main.h"
   49 #include "prefs.h"
   50 #include "projectprivate.h"
   51 #include "sciwrappers.h"
   52 #include "support.h"
   53 #include "symbols.h"
   54 #include "templates.h"
   55 #include "ui_utils.h"
   56 #include "utils.h"
   57 
   58 #include "SciLexer.h"
   59 
   60 #include "gtkcompat.h"
   61 
   62 #include <ctype.h>
   63 #include <string.h>
   64 
   65 #include <gdk/gdkkeysyms.h>
   66 
   67 
   68 static GHashTable *snippet_hash = NULL;
   69 static GtkAccelGroup *snippet_accel_group = NULL;
   70 static gboolean autocomplete_scope_shown = FALSE;
   71 
   72 static const gchar geany_cursor_marker[] = "__GEANY_CURSOR_MARKER__";
   73 
   74 /* holds word under the mouse or keyboard cursor */
   75 static gchar current_word[GEANY_MAX_WORD_LENGTH];
   76 
   77 /* Initialised in keyfile.c. */
   78 GeanyEditorPrefs editor_prefs;
   79 
   80 EditorInfo editor_info = {current_word, -1};
   81 
   82 static struct
   83 {
   84     gchar *text;
   85     gboolean set;
   86     gchar *last_word;
   87     guint tag_index;
   88     gint pos;
   89     ScintillaObject *sci;
   90 } calltip = {NULL, FALSE, NULL, 0, 0, NULL};
   91 
   92 static gchar indent[100];
   93 
   94 
   95 static void on_new_line_added(GeanyEditor *editor);
   96 static gboolean handle_xml(GeanyEditor *editor, gint pos, gchar ch);
   97 static void insert_indent_after_line(GeanyEditor *editor, gint line);
   98 static void auto_multiline(GeanyEditor *editor, gint pos);
   99 static void auto_close_chars(ScintillaObject *sci, gint pos, gchar c);
  100 static void close_block(GeanyEditor *editor, gint pos);
  101 static void editor_highlight_braces(GeanyEditor *editor, gint cur_pos);
  102 static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, gsize wordlen,
  103         const gchar *wc, gboolean stem);
  104 static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent);
  105 static const gchar *snippets_find_completion_by_name(const gchar *type, const gchar *name);
  106 static void snippets_make_replacements(GeanyEditor *editor, GString *pattern);
  107 static GeanyFiletype *editor_get_filetype_at_line(GeanyEditor *editor, gint line);
  108 static gboolean sci_is_blank_line(ScintillaObject *sci, gint line);
  109 
  110 
  111 void editor_snippets_free(void)
  112 {
  113     g_hash_table_destroy(snippet_hash);
  114     gtk_window_remove_accel_group(GTK_WINDOW(main_widgets.window), snippet_accel_group);
  115 }
  116 
  117 
  118 static void snippets_load(GKeyFile *sysconfig, GKeyFile *userconfig)
  119 {
  120     gsize i, j, len = 0, len_keys = 0;
  121     gchar **groups_user, **groups_sys;
  122     gchar **keys_user, **keys_sys;
  123     gchar *value;
  124     GHashTable *tmp;
  125 
  126     /* keys are strings, values are GHashTables, so use g_free and g_hash_table_destroy */
  127     snippet_hash =
  128         g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
  129 
  130     /* first read all globally defined auto completions */
  131     groups_sys = g_key_file_get_groups(sysconfig, &len);
  132     for (i = 0; i < len; i++)
  133     {
  134         if (strcmp(groups_sys[i], "Keybindings") == 0)
  135             continue;
  136         keys_sys = g_key_file_get_keys(sysconfig, groups_sys[i], &len_keys, NULL);
  137         /* create new hash table for the read section (=> filetype) */
  138         tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
  139         g_hash_table_insert(snippet_hash, g_strdup(groups_sys[i]), tmp);
  140 
  141         for (j = 0; j < len_keys; j++)
  142         {
  143             g_hash_table_insert(tmp, g_strdup(keys_sys[j]),
  144                         utils_get_setting_string(sysconfig, groups_sys[i], keys_sys[j], ""));
  145         }
  146         g_strfreev(keys_sys);
  147     }
  148     g_strfreev(groups_sys);
  149 
  150     /* now read defined completions in user's configuration directory and add / replace them */
  151     groups_user = g_key_file_get_groups(userconfig, &len);
  152     for (i = 0; i < len; i++)
  153     {
  154         if (strcmp(groups_user[i], "Keybindings") == 0)
  155             continue;
  156         keys_user = g_key_file_get_keys(userconfig, groups_user[i], &len_keys, NULL);
  157 
  158         tmp = g_hash_table_lookup(snippet_hash, groups_user[i]);
  159         if (tmp == NULL)
  160         {   /* new key found, create hash table */
  161             tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
  162             g_hash_table_insert(snippet_hash, g_strdup(groups_user[i]), tmp);
  163         }
  164         for (j = 0; j < len_keys; j++)
  165         {
  166             value = g_hash_table_lookup(tmp, keys_user[j]);
  167             if (value == NULL)
  168             {   /* value = NULL means the key doesn't yet exist, so insert */
  169                 g_hash_table_insert(tmp, g_strdup(keys_user[j]),
  170                         utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
  171             }
  172             else
  173             {   /* old key and value will be freed by destroy function (g_free) */
  174                 g_hash_table_replace(tmp, g_strdup(keys_user[j]),
  175                         utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
  176             }
  177         }
  178         g_strfreev(keys_user);
  179     }
  180     g_strfreev(groups_user);
  181 }
  182 
  183 
  184 static gboolean on_snippet_keybinding_activate(gchar *key)
  185 {
  186     GeanyDocument *doc = document_get_current();
  187     const gchar *s;
  188 
  189     if (!doc || !gtk_widget_has_focus(GTK_WIDGET(doc->editor->sci)))
  190         return FALSE;
  191 
  192     s = snippets_find_completion_by_name(doc->file_type->name, key);
  193     if (!s) /* allow user to specify keybindings for "special" snippets */
  194     {
  195         GHashTable *specials = g_hash_table_lookup(snippet_hash, "Special");
  196 
  197         if (G_LIKELY(specials != NULL))
  198             s = g_hash_table_lookup(specials, key);
  199     }
  200     if (!s)
  201     {
  202         utils_beep();
  203         return FALSE;
  204     }
  205 
  206     editor_insert_snippet(doc->editor, sci_get_current_position(doc->editor->sci), s);
  207     sci_scroll_caret(doc->editor->sci);
  208 
  209     return TRUE;
  210 }
  211 
  212 
  213 static void add_kb(GKeyFile *keyfile, const gchar *group, gchar **keys)
  214 {
  215     gsize i;
  216 
  217     if (!keys)
  218         return;
  219     for (i = 0; i < g_strv_length(keys); i++)
  220     {
  221         guint key;
  222         GdkModifierType mods;
  223         gchar *accel_string = g_key_file_get_value(keyfile, group, keys[i], NULL);
  224 
  225         gtk_accelerator_parse(accel_string, &key, &mods);
  226         g_free(accel_string);
  227 
  228         if (key == 0 && mods == 0)
  229         {
  230             g_warning("Can not parse accelerator \"%s\" from user snippets.conf", accel_string);
  231             continue;
  232         }
  233         gtk_accel_group_connect(snippet_accel_group, key, mods, 0,
  234             g_cclosure_new_swap((GCallback)on_snippet_keybinding_activate,
  235                 g_strdup(keys[i]), (GClosureNotify)g_free));
  236     }
  237 }
  238 
  239 
  240 static void load_kb(GKeyFile *sysconfig, GKeyFile *userconfig)
  241 {
  242     const gchar kb_group[] = "Keybindings";
  243     gchar **keys = g_key_file_get_keys(userconfig, kb_group, NULL, NULL);
  244     gchar **ptr;
  245 
  246     /* remove overridden keys from system keyfile */
  247     foreach_strv(ptr, keys)
  248         g_key_file_remove_key(sysconfig, kb_group, *ptr, NULL);
  249 
  250     add_kb(userconfig, kb_group, keys);
  251     g_strfreev(keys);
  252 
  253     keys = g_key_file_get_keys(sysconfig, kb_group, NULL, NULL);
  254     add_kb(sysconfig, kb_group, keys);
  255     g_strfreev(keys);
  256 }
  257 
  258 
  259 void editor_snippets_init(void)
  260 {
  261     gchar *sysconfigfile, *userconfigfile;
  262     GKeyFile *sysconfig = g_key_file_new();
  263     GKeyFile *userconfig = g_key_file_new();
  264 
  265     sysconfigfile = g_build_filename(app->datadir, "snippets.conf", NULL);
  266     userconfigfile = g_build_filename(app->configdir, "snippets.conf", NULL);
  267 
  268     /* check for old autocomplete.conf files (backwards compatibility) */
  269     if (! g_file_test(userconfigfile, G_FILE_TEST_IS_REGULAR))
  270         SETPTR(userconfigfile, g_build_filename(app->configdir, "autocomplete.conf", NULL));
  271 
  272     /* load the actual config files */
  273     g_key_file_load_from_file(sysconfig, sysconfigfile, G_KEY_FILE_NONE, NULL);
  274     g_key_file_load_from_file(userconfig, userconfigfile, G_KEY_FILE_NONE, NULL);
  275 
  276     snippets_load(sysconfig, userconfig);
  277 
  278     /* setup snippet keybindings */
  279     snippet_accel_group = gtk_accel_group_new();
  280     gtk_window_add_accel_group(GTK_WINDOW(main_widgets.window), snippet_accel_group);
  281     load_kb(sysconfig, userconfig);
  282 
  283     g_free(sysconfigfile);
  284     g_free(userconfigfile);
  285     g_key_file_free(sysconfig);
  286     g_key_file_free(userconfig);
  287 }
  288 
  289 
  290 static gboolean on_editor_button_press_event(GtkWidget *widget, GdkEventButton *event,
  291                                              gpointer data)
  292 {
  293     GeanyEditor *editor = data;
  294     GeanyDocument *doc = editor->document;
  295 
  296     /* it's very unlikely we got a 'real' click even on 0, 0, so assume it is a
  297      * fake event to show the editor menu triggered by a key event where we want to use the
  298      * text cursor position. */
  299     if (event->x > 0.0 && event->y > 0.0)
  300         editor_info.click_pos = sci_get_position_from_xy(editor->sci,
  301             (gint)event->x, (gint)event->y, FALSE);
  302     else
  303         editor_info.click_pos = sci_get_current_position(editor->sci);
  304 
  305     if (event->button == 1)
  306     {
  307         guint state = keybindings_get_modifiers(event->state);
  308 
  309         if (event->type == GDK_BUTTON_PRESS && editor_prefs.disable_dnd)
  310         {
  311             gint ss = sci_get_selection_start(editor->sci);
  312             sci_set_selection_end(editor->sci, ss);
  313         }
  314         if (event->type == GDK_BUTTON_PRESS && state == GEANY_PRIMARY_MOD_MASK)
  315         {
  316             sci_set_current_position(editor->sci, editor_info.click_pos, FALSE);
  317 
  318             editor_find_current_word(editor, editor_info.click_pos,
  319                 current_word, sizeof current_word, NULL);
  320             if (*current_word)
  321                 return symbols_goto_tag(current_word, TRUE);
  322             else
  323                 keybindings_send_command(GEANY_KEY_GROUP_GOTO, GEANY_KEYS_GOTO_MATCHINGBRACE);
  324             return TRUE;
  325         }
  326         return document_check_disk_status(doc, FALSE);
  327     }
  328 
  329     /* calls the edit popup menu in the editor */
  330     if (event->button == 3)
  331     {
  332         gboolean can_goto;
  333 
  334         /* ensure the editor widget has the focus after this operation */
  335         gtk_widget_grab_focus(widget);
  336 
  337         editor_find_current_word(editor, editor_info.click_pos,
  338             current_word, sizeof current_word, NULL);
  339 
  340         can_goto = sci_has_selection(editor->sci) || current_word[0] != '\0';
  341         ui_update_popup_goto_items(can_goto);
  342         ui_update_popup_copy_items(doc);
  343         ui_update_insert_include_item(doc, 0);
  344 
  345         g_signal_emit_by_name(geany_object, "update-editor-menu",
  346             current_word, editor_info.click_pos, doc);
  347 
  348         gtk_menu_popup(GTK_MENU(main_widgets.editor_menu),
  349             NULL, NULL, NULL, NULL, event->button, event->time);
  350 
  351         return TRUE;
  352     }
  353     return FALSE;
  354 }
  355 
  356 
  357 static gboolean is_style_php(gint style)
  358 {
  359     if ((style >= SCE_HPHP_DEFAULT && style <= SCE_HPHP_OPERATOR) ||
  360         style == SCE_HPHP_COMPLEX_VARIABLE)
  361     {
  362         return TRUE;
  363     }
  364 
  365     return FALSE;
  366 }
  367 
  368 
  369 static gint editor_get_long_line_type(void)
  370 {
  371     if (app->project)
  372         switch (app->project->priv->long_line_behaviour)
  373         {
  374             case 0: /* marker disabled */
  375                 return 2;
  376             case 1: /* use global settings */
  377                 break;
  378             case 2: /* custom (enabled) */
  379                 return editor_prefs.long_line_type;
  380         }
  381 
  382     if (!editor_prefs.long_line_enabled)
  383         return 2;
  384     else
  385         return editor_prefs.long_line_type;
  386 }
  387 
  388 
  389 static gint editor_get_long_line_column(void)
  390 {
  391     if (app->project && app->project->priv->long_line_behaviour != 1 /* use global settings */)
  392         return app->project->priv->long_line_column;
  393     else
  394         return editor_prefs.long_line_column;
  395 }
  396 
  397 
  398 #define get_project_pref(id)\
  399     (app->project ? app->project->priv->id : editor_prefs.id)
  400 
  401 static const GeanyEditorPrefs *
  402 get_default_prefs(void)
  403 {
  404     static GeanyEditorPrefs eprefs;
  405 
  406     eprefs = editor_prefs;
  407 
  408     /* project overrides */
  409     eprefs.indentation = (GeanyIndentPrefs*)editor_get_indent_prefs(NULL);
  410     eprefs.long_line_type = editor_get_long_line_type();
  411     eprefs.long_line_column = editor_get_long_line_column();
  412     eprefs.line_wrapping = get_project_pref(line_wrapping);
  413     eprefs.line_break_column = get_project_pref(line_break_column);
  414     eprefs.auto_continue_multiline = get_project_pref(auto_continue_multiline);
  415     return &eprefs;
  416 }
  417 
  418 
  419 /* Gets the prefs for the editor.
  420  * Prefs can be different according to project or document.
  421  * @warning Always get a fresh result instead of keeping a pointer to it if the editor/project
  422  * settings may have changed, or if this function has been called for a different editor.
  423  * @param editor The editor, or @c NULL to get the default prefs.
  424  * @return The prefs. */
  425 const GeanyEditorPrefs *editor_get_prefs(GeanyEditor *editor)
  426 {
  427     static GeanyEditorPrefs eprefs;
  428     const GeanyEditorPrefs *dprefs = get_default_prefs();
  429 
  430     /* Return the address of the default prefs to allow returning default and editor
  431      * pref pointers without invalidating the contents of either. */
  432     if (editor == NULL)
  433         return dprefs;
  434 
  435     eprefs = *dprefs;
  436     eprefs.indentation = (GeanyIndentPrefs*)editor_get_indent_prefs(editor);
  437     /* add other editor & document overrides as needed */
  438     return &eprefs;
  439 }
  440 
  441 
  442 void editor_toggle_fold(GeanyEditor *editor, gint line, gint modifiers)
  443 {
  444     ScintillaObject *sci;
  445     gint header;
  446 
  447     g_return_if_fail(editor != NULL);
  448 
  449     sci = editor->sci;
  450     /* When collapsing a fold range whose starting line is offscreen,
  451      * scroll the starting line to display at the top of the view.
  452      * Otherwise it can be confusing when the document scrolls down to hide
  453      * the folded lines. */
  454     if ((sci_get_fold_level(sci, line) & SC_FOLDLEVELNUMBERMASK) > SC_FOLDLEVELBASE &&
  455         !(sci_get_fold_level(sci, line) & SC_FOLDLEVELHEADERFLAG))
  456     {
  457         gint parent = sci_get_fold_parent(sci, line);
  458         gint first = sci_get_first_visible_line(sci);
  459 
  460         parent = SSM(sci, SCI_VISIBLEFROMDOCLINE, parent, 0);
  461         if (first > parent)
  462             SSM(sci, SCI_SETFIRSTVISIBLELINE, parent, 0);
  463     }
  464 
  465     /* find the fold header of the given line in case the one clicked isn't a fold point */
  466     if (sci_get_fold_level(sci, line) & SC_FOLDLEVELHEADERFLAG)
  467         header = line;
  468     else
  469         header = sci_get_fold_parent(sci, line);
  470 
  471     if ((editor_prefs.unfold_all_children && ! (modifiers & SCMOD_SHIFT)) ||
  472         (! editor_prefs.unfold_all_children && (modifiers & SCMOD_SHIFT)))
  473     {
  474         SSM(sci, SCI_FOLDCHILDREN, header, SC_FOLDACTION_TOGGLE);
  475     }
  476     else
  477     {
  478         SSM(sci, SCI_FOLDLINE, header, SC_FOLDACTION_TOGGLE);
  479     }
  480 }
  481 
  482 
  483 static void on_margin_click(GeanyEditor *editor, SCNotification *nt)
  484 {
  485     /* left click to marker margin marks the line */
  486     if (nt->margin == 1)
  487     {
  488         gint line = sci_get_line_from_position(editor->sci, nt->position);
  489 
  490         /*sci_marker_delete_all(editor->sci, 1);*/
  491         sci_toggle_marker_at_line(editor->sci, line, 1);    /* toggle the marker */
  492     }
  493     /* left click on the folding margin to toggle folding state of current line */
  494     else if (nt->margin == 2 && editor_prefs.folding)
  495     {
  496         gint line = sci_get_line_from_position(editor->sci, nt->position);
  497         editor_toggle_fold(editor, line, nt->modifiers);
  498     }
  499 }
  500 
  501 
  502 static void on_update_ui(GeanyEditor *editor, G_GNUC_UNUSED SCNotification *nt)
  503 {
  504     ScintillaObject *sci = editor->sci;
  505     gint pos = sci_get_current_position(sci);
  506 
  507     /* since Scintilla 2.24, SCN_UPDATEUI is also sent on scrolling though we don't need to handle
  508      * this and so ignore every SCN_UPDATEUI events except for content and selection changes */
  509     if (! (nt->updated & SC_UPDATE_CONTENT) && ! (nt->updated & SC_UPDATE_SELECTION))
  510         return;
  511 
  512     /* undo / redo menu update */
  513     ui_update_popup_reundo_items(editor->document);
  514 
  515     /* brace highlighting */
  516     editor_highlight_braces(editor, pos);
  517 
  518     ui_update_statusbar(editor->document, pos);
  519 
  520 #if 0
  521     /** experimental code for inverting selections */
  522     {
  523     gint i;
  524     for (i = SSM(sci, SCI_GETSELECTIONSTART, 0, 0); i < SSM(sci, SCI_GETSELECTIONEND, 0, 0); i++)
  525     {
  526         /* need to get colour from getstyleat(), but how? */
  527         SSM(sci, SCI_STYLESETFORE, STYLE_DEFAULT, 0);
  528         SSM(sci, SCI_STYLESETBACK, STYLE_DEFAULT, 0);
  529     }
  530 
  531     sci_get_style_at(sci, pos);
  532     }
  533 #endif
  534 }
  535 
  536 
  537 static void check_line_breaking(GeanyEditor *editor, gint pos)
  538 {
  539     ScintillaObject *sci = editor->sci;
  540     gint line, lstart, col;
  541     gchar c;
  542 
  543     if (!editor->line_breaking || sci_get_selection_mode(editor->sci) != SC_SEL_STREAM)
  544         return;
  545 
  546     col = sci_get_col_from_position(sci, pos);
  547 
  548     line = sci_get_current_line(sci);
  549 
  550     lstart = sci_get_position_from_line(sci, line);
  551 
  552     /* use column instead of position which might be different with multibyte characters */
  553     if (col < get_project_pref(line_break_column))
  554         return;
  555 
  556     /* look for the last space before line_break_column */
  557     pos = sci_get_position_from_col(sci, line, get_project_pref(line_break_column));
  558 
  559     while (pos > lstart)
  560     {
  561         c = sci_get_char_at(sci, --pos);
  562         if (c == ' ')
  563         {
  564             gint diff, last_pos, last_col;
  565 
  566             /* remember the distance between the current column and the last column on the line
  567              * (we use column position in case the previous line gets altered, such as removing
  568              * trailing spaces or in case it contains multibyte characters) */
  569             last_pos = sci_get_line_end_position(sci, line);
  570             last_col = sci_get_col_from_position(sci, last_pos);
  571             diff = last_col - col;
  572 
  573             /* break the line after the space */
  574             sci_set_current_position(sci, pos + 1, FALSE);
  575             sci_cancel(sci);    /* don't select from completion list */
  576             sci_send_command(sci, SCI_NEWLINE);
  577             line++;
  578 
  579             /* correct cursor position (might not be at line end) */
  580             last_pos = sci_get_line_end_position(sci, line);
  581             last_col = sci_get_col_from_position(sci, last_pos); /* get last column on line */
  582             /* last column - distance is the desired column, then retrieve its document position */
  583             pos = sci_get_position_from_col(sci, line, last_col - diff);
  584             sci_set_current_position(sci, pos, FALSE);
  585             sci_scroll_caret(sci);
  586             return;
  587         }
  588     }
  589 }
  590 
  591 
  592 static void show_autocomplete(ScintillaObject *sci, gsize rootlen, GString *words)
  593 {
  594     /* hide autocompletion if only option is already typed */
  595     if (rootlen >= words->len ||
  596         (words->str[rootlen] == '?' && rootlen >= words->len - 2))
  597     {
  598         sci_send_command(sci, SCI_AUTOCCANCEL);
  599         return;
  600     }
  601     /* store whether a calltip is showing, so we can reshow it after autocompletion */
  602     calltip.set = (gboolean) SSM(sci, SCI_CALLTIPACTIVE, 0, 0);
  603     SSM(sci, SCI_AUTOCSHOW, rootlen, (sptr_t) words->str);
  604 }
  605 
  606 
  607 static void show_tags_list(GeanyEditor *editor, const GPtrArray *tags, gsize rootlen)
  608 {
  609     ScintillaObject *sci = editor->sci;
  610 
  611     g_return_if_fail(tags);
  612 
  613     if (tags->len > 0)
  614     {
  615         GString *words = g_string_sized_new(150);
  616         guint j;
  617 
  618         for (j = 0; j < tags->len; ++j)
  619         {
  620             TMTag *tag = tags->pdata[j];
  621 
  622             if (j > 0)
  623                 g_string_append_c(words, '\n');
  624 
  625             if (j == editor_prefs.autocompletion_max_entries)
  626             {
  627                 g_string_append(words, "...");
  628                 break;
  629             }
  630             g_string_append(words, tag->name);
  631 
  632             /* for now, tag types don't all follow C, so just look at arglist */
  633             if (!EMPTY(tag->arglist))
  634                 g_string_append(words, "?2");
  635             else
  636                 g_string_append(words, "?1");
  637         }
  638         show_autocomplete(sci, rootlen, words);
  639         g_string_free(words, TRUE);
  640     }
  641 }
  642 
  643 
  644 /* do not use with long strings */
  645 static gboolean match_last_chars(ScintillaObject *sci, gint pos, const gchar *str)
  646 {
  647     gsize len = strlen(str);
  648     gchar *buf;
  649 
  650     g_return_val_if_fail(len < 100, FALSE);
  651 
  652     if ((gint)len > pos)
  653         return FALSE;
  654 
  655     buf = g_alloca(len + 1);
  656     sci_get_text_range(sci, pos - len, pos, buf);
  657     return strcmp(str, buf) == 0;
  658 }
  659 
  660 
  661 static gboolean reshow_calltip(gpointer data)
  662 {
  663     GeanyDocument *doc;
  664 
  665     g_return_val_if_fail(calltip.sci != NULL, FALSE);
  666 
  667     SSM(calltip.sci, SCI_CALLTIPCANCEL, 0, 0);
  668     doc = document_get_current();
  669 
  670     if (doc && doc->editor->sci == calltip.sci)
  671     {
  672         /* we use the position where the calltip was previously started as SCI_GETCURRENTPOS
  673          * may be completely wrong in case the user cancelled the auto completion with the mouse */
  674         SSM(calltip.sci, SCI_CALLTIPSHOW, calltip.pos, (sptr_t) calltip.text);
  675     }
  676     return FALSE;
  677 }
  678 
  679 
  680 static void request_reshowing_calltip(SCNotification *nt)
  681 {
  682     if (calltip.set)
  683     {
  684         /* delay the reshow of the calltip window to make sure it is actually displayed,
  685          * without it might be not visible on SCN_AUTOCCANCEL. the priority is set to
  686          * low to hopefully make Scintilla's events happen before reshowing since they
  687          * seem to re-cancel the calltip on autoc menu hiding too */
  688         g_idle_add_full(G_PRIORITY_LOW, reshow_calltip, NULL, NULL);
  689     }
  690 }
  691 
  692 
  693 static gboolean autocomplete_scope(GeanyEditor *editor, const gchar *root, gsize rootlen)
  694 {
  695     ScintillaObject *sci = editor->sci;
  696     gint pos = sci_get_current_position(editor->sci);
  697     gchar typed = sci_get_char_at(sci, pos - 1);
  698     gchar brace_char;
  699     gchar *name;
  700     GeanyFiletype *ft = editor->document->file_type;
  701     GPtrArray *tags;
  702     gboolean function = FALSE;
  703     gboolean member;
  704     gboolean scope_sep_typed = FALSE;
  705     gboolean ret = FALSE;
  706     const gchar *current_scope;
  707     const gchar *context_sep = tm_parser_context_separator(ft->lang);
  708 
  709     if (autocomplete_scope_shown)
  710     {
  711         /* move at the operator position */
  712         pos -= rootlen;
  713 
  714         /* allow for a space between word and operator */
  715         while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
  716             pos--;
  717 
  718         if (pos > 0)
  719             typed = sci_get_char_at(sci, pos - 1);
  720     }
  721 
  722     /* make sure to keep in sync with similar checks below */
  723     if (match_last_chars(sci, pos, context_sep))
  724     {
  725         pos -= strlen(context_sep);
  726         scope_sep_typed = TRUE;
  727     }
  728     else if (typed == '.')
  729         pos -= 1;
  730     else if ((ft->id == GEANY_FILETYPES_C || ft->id == GEANY_FILETYPES_CPP) &&
  731             match_last_chars(sci, pos, "->"))
  732         pos -= 2;
  733     else if (ft->id == GEANY_FILETYPES_CPP && match_last_chars(sci, pos, "->*"))
  734         pos -= 3;
  735     else
  736         return FALSE;
  737 
  738     /* allow for a space between word and operator */
  739     while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
  740         pos--;
  741 
  742     /* if function or array index, skip to matching brace */
  743     brace_char = sci_get_char_at(sci, pos - 1);
  744     if (pos > 0 && (brace_char == ')' || brace_char == ']'))
  745     {
  746         gint brace_pos = sci_find_matching_brace(sci, pos - 1);
  747 
  748         if (brace_pos != -1)
  749         {
  750             pos = brace_pos;
  751             function = brace_char == ')';
  752         }
  753 
  754         /* allow for a space between opening brace and name */
  755         while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
  756             pos--;
  757     }
  758 
  759     name = editor_get_word_at_pos(editor, pos, NULL);
  760     if (!name)
  761         return FALSE;
  762 
  763     /* check if invoked on member */
  764     pos -= strlen(name);
  765     while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
  766         pos--;
  767     /* make sure to keep in sync with similar checks above */
  768     member = match_last_chars(sci, pos, ".") || match_last_chars(sci, pos, context_sep) ||
  769              match_last_chars(sci, pos, "->") || match_last_chars(sci, pos, "->*");
  770 
  771     if (symbols_get_current_scope(editor->document, &current_scope) == -1)
  772         current_scope = "";
  773     tags = tm_workspace_find_scope_members(editor->document->tm_file, name, function,
  774                 member, current_scope, scope_sep_typed);
  775     if (tags)
  776     {
  777         GPtrArray *filtered = g_ptr_array_new();
  778         TMTag *tag;
  779         guint i;
  780 
  781         foreach_ptr_array(tag, i, tags)
  782         {
  783             if (g_str_has_prefix(tag->name, root))
  784                 g_ptr_array_add(filtered, tag);
  785         }
  786 
  787         if (filtered->len > 0)
  788         {
  789             show_tags_list(editor, filtered, rootlen);
  790             ret = TRUE;
  791         }
  792 
  793         g_ptr_array_free(tags, TRUE);
  794         g_ptr_array_free(filtered, TRUE);
  795     }
  796 
  797     g_free(name);
  798     return ret;
  799 }
  800 
  801 
  802 static void on_char_added(GeanyEditor *editor, SCNotification *nt)
  803 {
  804     ScintillaObject *sci = editor->sci;
  805     gint pos = sci_get_current_position(sci);
  806 
  807     switch (nt->ch)
  808     {
  809         case '\r':
  810         {   /* simple indentation (only for CR format) */
  811             if (sci_get_eol_mode(sci) == SC_EOL_CR)
  812                 on_new_line_added(editor);
  813             break;
  814         }
  815         case '\n':
  816         {   /* simple indentation (for CR/LF and LF format) */
  817             on_new_line_added(editor);
  818             break;
  819         }
  820         case '>':
  821             editor_start_auto_complete(editor, pos, FALSE); /* C/C++ ptr-> scope completion */
  822             /* fall through */
  823         case '/':
  824         {   /* close xml-tags */
  825             handle_xml(editor, pos, nt->ch);
  826             break;
  827         }
  828         case '(':
  829         {
  830             auto_close_chars(sci, pos, nt->ch);
  831             /* show calltips */
  832             editor_show_calltip(editor, --pos);
  833             break;
  834         }
  835         case ')':
  836         {   /* hide calltips */
  837             if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0))
  838             {
  839                 SSM(sci, SCI_CALLTIPCANCEL, 0, 0);
  840             }
  841             g_free(calltip.text);
  842             calltip.text = NULL;
  843             calltip.pos = 0;
  844             calltip.sci = NULL;
  845             calltip.set = FALSE;
  846             break;
  847         }
  848         case '{':
  849         case '[':
  850         case '"':
  851         case '\'':
  852         {
  853             auto_close_chars(sci, pos, nt->ch);
  854             break;
  855         }
  856         case '}':
  857         {   /* closing bracket handling */
  858             if (editor->auto_indent)
  859                 close_block(editor, pos - 1);
  860             break;
  861         }
  862         /* scope autocompletion */
  863         case '.':
  864         case ':':   /* C/C++ class:: syntax */
  865         /* tag autocompletion */
  866         default:
  867 #if 0
  868             if (! editor_start_auto_complete(editor, pos, FALSE))
  869                 request_reshowing_calltip(nt);
  870 #else
  871             editor_start_auto_complete(editor, pos, FALSE);
  872 #endif
  873     }
  874     check_line_breaking(editor, pos);
  875 }
  876 
  877 
  878 /* expand() and fold_changed() are copied from SciTE (thanks) to fix #1923350. */
  879 static void expand(ScintillaObject *sci, gint *line, gboolean doExpand,
  880         gboolean force, gint visLevels, gint level)
  881 {
  882     gint lineMaxSubord = SSM(sci, SCI_GETLASTCHILD, *line, level & SC_FOLDLEVELNUMBERMASK);
  883     gint levelLine = level;
  884     (*line)++;
  885     while (*line <= lineMaxSubord)
  886     {
  887         if (force)
  888         {
  889             if (visLevels > 0)
  890                 SSM(sci, SCI_SHOWLINES, *line, *line);
  891             else
  892                 SSM(sci, SCI_HIDELINES, *line, *line);
  893         }
  894         else
  895         {
  896             if (doExpand)
  897                 SSM(sci, SCI_SHOWLINES, *line, *line);
  898         }
  899         if (levelLine == -1)
  900             levelLine = SSM(sci, SCI_GETFOLDLEVEL, *line, 0);
  901         if (levelLine & SC_FOLDLEVELHEADERFLAG)
  902         {
  903             if (force)
  904             {
  905                 if (visLevels > 1)
  906                     SSM(sci, SCI_SETFOLDEXPANDED, *line, 1);
  907                 else
  908                     SSM(sci, SCI_SETFOLDEXPANDED, *line, 0);
  909                 expand(sci, line, doExpand, force, visLevels - 1, -1);
  910             }
  911             else
  912             {
  913                 if (doExpand)
  914                 {
  915                     if (!sci_get_fold_expanded(sci, *line))
  916                         SSM(sci, SCI_SETFOLDEXPANDED, *line, 1);
  917                     expand(sci, line, TRUE, force, visLevels - 1, -1);
  918                 }
  919                 else
  920                 {
  921                     expand(sci, line, FALSE, force, visLevels - 1, -1);
  922                 }
  923             }
  924         }
  925         else
  926         {
  927             (*line)++;
  928         }
  929     }
  930 }
  931 
  932 
  933 static void fold_changed(ScintillaObject *sci, gint line, gint levelNow, gint levelPrev)
  934 {
  935     if (levelNow & SC_FOLDLEVELHEADERFLAG)
  936     {
  937         if (! (levelPrev & SC_FOLDLEVELHEADERFLAG))
  938         {
  939             /* Adding a fold point */
  940             SSM(sci, SCI_SETFOLDEXPANDED, line, 1);
  941             if (!SSM(sci, SCI_GETALLLINESVISIBLE, 0, 0))
  942                 expand(sci, &line, TRUE, FALSE, 0, levelPrev);
  943         }
  944     }
  945     else if (levelPrev & SC_FOLDLEVELHEADERFLAG)
  946     {
  947         if (! sci_get_fold_expanded(sci, line))
  948         {   /* Removing the fold from one that has been contracted so should expand
  949              * otherwise lines are left invisible with no way to make them visible */
  950             SSM(sci, SCI_SETFOLDEXPANDED, line, 1);
  951             if (!SSM(sci, SCI_GETALLLINESVISIBLE, 0, 0))
  952                 expand(sci, &line, TRUE, FALSE, 0, levelPrev);
  953         }
  954     }
  955     if (! (levelNow & SC_FOLDLEVELWHITEFLAG) &&
  956             ((levelPrev & SC_FOLDLEVELNUMBERMASK) > (levelNow & SC_FOLDLEVELNUMBERMASK)))
  957     {
  958         if (!SSM(sci, SCI_GETALLLINESVISIBLE, 0, 0)) {
  959             /* See if should still be hidden */
  960             gint parentLine = sci_get_fold_parent(sci, line);
  961             if (parentLine < 0)
  962             {
  963                 SSM(sci, SCI_SHOWLINES, line, line);
  964             }
  965             else if (sci_get_fold_expanded(sci, parentLine) &&
  966                     sci_get_line_is_visible(sci, parentLine))
  967             {
  968                 SSM(sci, SCI_SHOWLINES, line, line);
  969             }
  970         }
  971     }
  972 }
  973 
  974 
  975 static void ensure_range_visible(ScintillaObject *sci, gint posStart, gint posEnd,
  976         gboolean enforcePolicy)
  977 {
  978     gint lineStart = sci_get_line_from_position(sci, MIN(posStart, posEnd));
  979     gint lineEnd = sci_get_line_from_position(sci, MAX(posStart, posEnd));
  980     gint line;
  981 
  982     for (line = lineStart; line <= lineEnd; line++)
  983     {
  984         SSM(sci, enforcePolicy ? SCI_ENSUREVISIBLEENFORCEPOLICY : SCI_ENSUREVISIBLE, line, 0);
  985     }
  986 }
  987 
  988 
  989 static void auto_update_margin_width(GeanyEditor *editor)
  990 {
  991     gint next_linecount = 1;
  992     gint linecount = sci_get_line_count(editor->sci);
  993     GeanyDocument *doc = editor->document;
  994 
  995     while (next_linecount <= linecount)
  996         next_linecount *= 10;
  997 
  998     if (editor->document->priv->line_count != next_linecount)
  999     {
 1000         doc->priv->line_count = next_linecount;
 1001         sci_set_line_numbers(editor->sci, TRUE);
 1002     }
 1003 }
 1004 
 1005 
 1006 static void partial_complete(ScintillaObject *sci, const gchar *text)
 1007 {
 1008     gint pos = sci_get_current_position(sci);
 1009 
 1010     sci_insert_text(sci, pos, text);
 1011     sci_set_current_position(sci, pos + strlen(text), TRUE);
 1012 }
 1013 
 1014 
 1015 /* Complete the next word part from @a entry */
 1016 static gboolean check_partial_completion(GeanyEditor *editor, const gchar *entry)
 1017 {
 1018     gchar *stem, *ptr, *text = utils_strdupa(entry);
 1019 
 1020     read_current_word(editor, -1, current_word, sizeof current_word, NULL, TRUE);
 1021     stem = current_word;
 1022     if (strstr(text, stem) != text)
 1023         return FALSE;   /* shouldn't happen */
 1024     if (strlen(text) <= strlen(stem))
 1025         return FALSE;
 1026 
 1027     text += strlen(stem); /* skip stem */
 1028     ptr = strstr(text + 1, "_");
 1029     if (ptr)
 1030     {
 1031         ptr[1] = '\0';
 1032         partial_complete(editor->sci, text);
 1033         return TRUE;
 1034     }
 1035     else
 1036     {
 1037         /* CamelCase */
 1038         foreach_str(ptr, text + 1)
 1039         {
 1040             if (!ptr[0])
 1041                 break;
 1042             if (g_ascii_isupper(*ptr) && g_ascii_islower(ptr[1]))
 1043             {
 1044                 ptr[0] = '\0';
 1045                 partial_complete(editor->sci, text);
 1046                 return TRUE;
 1047             }
 1048         }
 1049     }
 1050     return FALSE;
 1051 }
 1052 
 1053 
 1054 /* Callback for the "sci-notify" signal to emit a "editor-notify" signal.
 1055  * Plugins can connect to the "editor-notify" signal. */
 1056 void editor_sci_notify_cb(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gint scn,
 1057                           gpointer scnt, gpointer data)
 1058 {
 1059     GeanyEditor *editor = data;
 1060     gboolean retval;
 1061 
 1062     g_return_if_fail(editor != NULL);
 1063 
 1064     g_signal_emit_by_name(geany_object, "editor-notify", editor, scnt, &retval);
 1065 }
 1066 
 1067 
 1068 static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *editor,
 1069                                  SCNotification *nt, G_GNUC_UNUSED gpointer data)
 1070 {
 1071     ScintillaObject *sci = editor->sci;
 1072     GeanyDocument *doc = editor->document;
 1073 
 1074     switch (nt->nmhdr.code)
 1075     {
 1076         case SCN_SAVEPOINTLEFT:
 1077             document_set_text_changed(doc, TRUE);
 1078             break;
 1079 
 1080         case SCN_SAVEPOINTREACHED:
 1081             document_set_text_changed(doc, FALSE);
 1082             break;
 1083 
 1084         case SCN_MODIFYATTEMPTRO:
 1085             utils_beep();
 1086             break;
 1087 
 1088         case SCN_MARGINCLICK:
 1089             on_margin_click(editor, nt);
 1090             break;
 1091 
 1092         case SCN_UPDATEUI:
 1093             on_update_ui(editor, nt);
 1094             break;
 1095 
 1096         case SCN_PAINTED:
 1097             /* Visible lines are only laid out accurately just before painting,
 1098              * so we need to only call editor_scroll_to_line here, because the document
 1099              * may have line wrapping and folding enabled.
 1100              * http://scintilla.sourceforge.net/ScintillaDoc.html#LineWrapping
 1101              * This is important e.g. when loading a session and switching pages
 1102              * and having the cursor scroll in view. */
 1103              /* FIXME: Really we want to do this just before painting, not after it
 1104               * as it will cause repainting. */
 1105             if (editor->scroll_percent > 0.0F)
 1106             {
 1107                 editor_scroll_to_line(editor, -1, editor->scroll_percent);
 1108                 /* disable further scrolling */
 1109                 editor->scroll_percent = -1.0F;
 1110             }
 1111             break;
 1112 
 1113         case SCN_MODIFIED:
 1114             if (editor_prefs.show_linenumber_margin && (nt->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) && nt->linesAdded)
 1115             {
 1116                 /* automatically adjust Scintilla's line numbers margin width */
 1117                 auto_update_margin_width(editor);
 1118             }
 1119             if (nt->modificationType & SC_STARTACTION && ! ignore_callback)
 1120             {
 1121                 /* get notified about undo changes */
 1122                 document_undo_add(doc, UNDO_SCINTILLA, NULL);
 1123             }
 1124             if (editor_prefs.folding && (nt->modificationType & SC_MOD_CHANGEFOLD) != 0)
 1125             {
 1126                 /* handle special fold cases, e.g. #1923350 */
 1127                 fold_changed(sci, nt->line, nt->foldLevelNow, nt->foldLevelPrev);
 1128             }
 1129             if (nt->modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT))
 1130             {
 1131                 document_update_tag_list_in_idle(doc);
 1132             }
 1133             break;
 1134 
 1135         case SCN_CHARADDED:
 1136             on_char_added(editor, nt);
 1137             break;
 1138 
 1139         case SCN_USERLISTSELECTION:
 1140             if (nt->listType == 1)
 1141             {
 1142                 sci_add_text(sci, nt->text);
 1143             }
 1144             break;
 1145 
 1146         case SCN_AUTOCSELECTION:
 1147             if (g_str_equal(nt->text, "..."))
 1148             {
 1149                 sci_cancel(sci);
 1150                 utils_beep();
 1151                 break;
 1152             }
 1153             /* fall through */
 1154         case SCN_AUTOCCANCELLED:
 1155             /* now that autocomplete is finishing or was cancelled, reshow calltips
 1156              * if they were showing */
 1157             autocomplete_scope_shown = FALSE;
 1158             request_reshowing_calltip(nt);
 1159             break;
 1160         case SCN_NEEDSHOWN:
 1161             ensure_range_visible(sci, nt->position, nt->position + nt->length, FALSE);
 1162             break;
 1163 
 1164         case SCN_URIDROPPED:
 1165             if (nt->text != NULL)
 1166             {
 1167                 document_open_file_list(nt->text, strlen(nt->text));
 1168             }
 1169             break;
 1170 
 1171         case SCN_CALLTIPCLICK:
 1172             if (nt->position > 0)
 1173             {
 1174                 switch (nt->position)
 1175                 {
 1176                     case 1: /* up arrow */
 1177                         if (calltip.tag_index > 0)
 1178                             calltip.tag_index--;
 1179                         break;
 1180 
 1181                     case 2: calltip.tag_index++; break; /* down arrow */
 1182                 }
 1183                 editor_show_calltip(editor, -1);
 1184             }
 1185             break;
 1186 
 1187         case SCN_ZOOM:
 1188             /* recalculate line margin width */
 1189             sci_set_line_numbers(sci, editor_prefs.show_linenumber_margin);
 1190             break;
 1191     }
 1192     /* we always return FALSE here to let plugins handle the event too */
 1193     return FALSE;
 1194 }
 1195 
 1196 
 1197 /* Note: this is the same as sci_get_tab_width(), but is still useful when you don't have
 1198  * a scintilla pointer. */
 1199 static gint get_tab_width(const GeanyIndentPrefs *indent_prefs)
 1200 {
 1201     if (indent_prefs->type == GEANY_INDENT_TYPE_BOTH)
 1202         return indent_prefs->hard_tab_width;
 1203 
 1204     return indent_prefs->width; /* tab width = indent width */
 1205 }
 1206 
 1207 
 1208 /* Returns a string containing width chars of whitespace, filled with simple space
 1209  * characters or with the right number of tab characters, according to the indent prefs.
 1210  * (Result is filled with tabs *and* spaces if width isn't a multiple of
 1211  * the tab width). */
 1212 static gchar *
 1213 get_whitespace(const GeanyIndentPrefs *iprefs, gint width)
 1214 {
 1215     g_return_val_if_fail(width >= 0, NULL);
 1216 
 1217     if (width == 0)
 1218         return g_strdup("");
 1219 
 1220     if (iprefs->type == GEANY_INDENT_TYPE_SPACES)
 1221     {
 1222         return g_strnfill(width, ' ');
 1223     }
 1224     else
 1225     {   /* first fill text with tabs and fill the rest with spaces */
 1226         const gint tab_width = get_tab_width(iprefs);
 1227         gint tabs = width / tab_width;
 1228         gint spaces = width % tab_width;
 1229         gint len = tabs + spaces;
 1230         gchar *str;
 1231 
 1232         str = g_malloc(len + 1);
 1233 
 1234         memset(str, '\t', tabs);
 1235         memset(str + tabs, ' ', spaces);
 1236         str[len] = '\0';
 1237         return str;
 1238     }
 1239 }
 1240 
 1241 
 1242 static const GeanyIndentPrefs *
 1243 get_default_indent_prefs(void)
 1244 {
 1245     static GeanyIndentPrefs iprefs;
 1246 
 1247     iprefs = app->project ? *app->project->priv->indentation : *editor_prefs.indentation;
 1248     return &iprefs;
 1249 }
 1250 
 1251 
 1252 /** Gets the indentation prefs for the editor.
 1253  * Prefs can be different according to project or document.
 1254  * @warning Always get a fresh result instead of keeping a pointer to it if the editor/project
 1255  * settings may have changed, or if this function has been called for a different editor.
 1256  * @param editor @nullable The editor, or @c NULL to get the default indent prefs.
 1257  * @return The indent prefs. */
 1258 GEANY_API_SYMBOL
 1259 const GeanyIndentPrefs *
 1260 editor_get_indent_prefs(GeanyEditor *editor)
 1261 {
 1262     static GeanyIndentPrefs iprefs;
 1263     const GeanyIndentPrefs *dprefs = get_default_indent_prefs();
 1264 
 1265     /* Return the address of the default prefs to allow returning default and editor
 1266      * pref pointers without invalidating the contents of either. */
 1267     if (editor == NULL)
 1268         return dprefs;
 1269 
 1270     iprefs = *dprefs;
 1271     iprefs.type = editor->indent_type;
 1272     iprefs.width = editor->indent_width;
 1273 
 1274     /* if per-document auto-indent is enabled, but we don't have a global mode set,
 1275      * just use basic auto-indenting */
 1276     if (editor->auto_indent && iprefs.auto_indent_mode == GEANY_AUTOINDENT_NONE)
 1277         iprefs.auto_indent_mode = GEANY_AUTOINDENT_BASIC;
 1278 
 1279     if (!editor->auto_indent)
 1280         iprefs.auto_indent_mode = GEANY_AUTOINDENT_NONE;
 1281 
 1282     return &iprefs;
 1283 }
 1284 
 1285 
 1286 static void on_new_line_added(GeanyEditor *editor)
 1287 {
 1288     ScintillaObject *sci = editor->sci;
 1289     gint line = sci_get_current_line(sci);
 1290 
 1291     /* simple indentation */
 1292     if (editor->auto_indent)
 1293     {
 1294         insert_indent_after_line(editor, line - 1);
 1295     }
 1296 
 1297     if (get_project_pref(auto_continue_multiline))
 1298     {   /* " * " auto completion in multiline C/C++/D/Java comments */
 1299         auto_multiline(editor, line);
 1300     }
 1301 
 1302     if (editor_prefs.newline_strip)
 1303     {   /* strip the trailing spaces on the previous line */
 1304         editor_strip_line_trailing_spaces(editor, line - 1);
 1305     }
 1306 }
 1307 
 1308 
 1309 static gboolean lexer_has_braces(ScintillaObject *sci)
 1310 {
 1311     gint lexer = sci_get_lexer(sci);
 1312 
 1313     switch (lexer)
 1314     {
 1315         case SCLEX_CPP:
 1316         case SCLEX_D:
 1317         case SCLEX_HTML:    /* for PHP & JS */
 1318         case SCLEX_PHPSCRIPT:
 1319         case SCLEX_PASCAL:  /* for multiline comments? */
 1320         case SCLEX_BASH:
 1321         case SCLEX_PERL:
 1322         case SCLEX_TCL:
 1323         case SCLEX_R:
 1324         case SCLEX_RUST:
 1325             return TRUE;
 1326         default:
 1327             return FALSE;
 1328     }
 1329 }
 1330 
 1331 
 1332 /* Read indent chars for the line that pos is on into indent global variable.
 1333  * Note: Use sci_get_line_indentation() and get_whitespace()/editor_insert_text_block()
 1334  * instead in any new code.  */
 1335 static void read_indent(GeanyEditor *editor, gint pos)
 1336 {
 1337     ScintillaObject *sci = editor->sci;
 1338     guint i, len, j = 0;
 1339     gint line;
 1340     gchar *linebuf;
 1341 
 1342     line = sci_get_line_from_position(sci, pos);
 1343 
 1344     len = sci_get_line_length(sci, line);
 1345     linebuf = sci_get_line(sci, line);
 1346 
 1347     for (i = 0; i < len && j <= (sizeof(indent) - 1); i++)
 1348     {
 1349         if (linebuf[i] == ' ' || linebuf[i] == '\t')    /* simple indentation */
 1350             indent[j++] = linebuf[i];
 1351         else
 1352             break;
 1353     }
 1354     indent[j] = '\0';
 1355     g_free(linebuf);
 1356 }
 1357 
 1358 
 1359 static gint get_brace_indent(ScintillaObject *sci, gint line)
 1360 {
 1361     gint start = sci_get_position_from_line(sci, line);
 1362     gint end = sci_get_line_end_position(sci, line) - 1;
 1363     gint lexer = sci_get_lexer(sci);
 1364     gint count = 0;
 1365     gint pos;
 1366 
 1367     for (pos = end; pos >= start && count < 1; pos--)
 1368     {
 1369         if (highlighting_is_code_style(lexer, sci_get_style_at(sci, pos)))
 1370         {
 1371             gchar c = sci_get_char_at(sci, pos);
 1372 
 1373             if (c == '{')
 1374                 count ++;
 1375             else if (c == '}')
 1376                 count --;
 1377         }
 1378     }
 1379 
 1380     return count > 0 ? 1 : 0;
 1381 }
 1382 
 1383 
 1384 /* gets the last code position on a line
 1385  * warning: if there is no code position on the line, returns the start position */
 1386 static gint get_sci_line_code_end_position(ScintillaObject *sci, gint line)
 1387 {
 1388     gint start = sci_get_position_from_line(sci, line);
 1389     gint lexer = sci_get_lexer(sci);
 1390     gint pos;
 1391 
 1392     for (pos = sci_get_line_end_position(sci, line) - 1; pos > start; pos--)
 1393     {
 1394         gint style = sci_get_style_at(sci, pos);
 1395 
 1396         if (highlighting_is_code_style(lexer, style) && ! isspace(sci_get_char_at(sci, pos)))
 1397             break;
 1398     }
 1399 
 1400     return pos;
 1401 }
 1402 
 1403 
 1404 static gint get_python_indent(ScintillaObject *sci, gint line)
 1405 {
 1406     gint last_char = get_sci_line_code_end_position(sci, line);
 1407 
 1408     /* add extra indentation for Python after colon */
 1409     if (sci_get_char_at(sci, last_char) == ':' &&
 1410         sci_get_style_at(sci, last_char) == SCE_P_OPERATOR)
 1411     {
 1412         return 1;
 1413     }
 1414     return 0;
 1415 }
 1416 
 1417 
 1418 static gint get_xml_indent(ScintillaObject *sci, gint line)
 1419 {
 1420     gboolean need_close = FALSE;
 1421     gint end = get_sci_line_code_end_position(sci, line);
 1422     gint pos;
 1423 
 1424     /* don't indent if there's a closing tag to the right of the cursor */
 1425     pos = sci_get_current_position(sci);
 1426     if (sci_get_char_at(sci, pos) == '<' &&
 1427         sci_get_char_at(sci, pos + 1) == '/')
 1428         return 0;
 1429 
 1430     if (sci_get_char_at(sci, end) == '>' &&
 1431         sci_get_char_at(sci, end - 1) != '/')
 1432     {
 1433         gint style = sci_get_style_at(sci, end);
 1434 
 1435         if (style == SCE_H_TAG || style == SCE_H_TAGUNKNOWN)
 1436         {
 1437             gint start = sci_get_position_from_line(sci, line);
 1438             gchar *line_contents = sci_get_contents_range(sci, start, end + 1);
 1439             gchar *opened_tag_name = utils_find_open_xml_tag(line_contents, end + 1 - start);
 1440 
 1441             if (!EMPTY(opened_tag_name))
 1442             {
 1443                 need_close = TRUE;
 1444                 if (sci_get_lexer(sci) == SCLEX_HTML && utils_is_short_html_tag(opened_tag_name))
 1445                     need_close = FALSE;
 1446             }
 1447             g_free(line_contents);
 1448             g_free(opened_tag_name);
 1449         }
 1450     }
 1451 
 1452     return need_close ? 1 : 0;
 1453 }
 1454 
 1455 
 1456 static gint get_indent_size_after_line(GeanyEditor *editor, gint line)
 1457 {
 1458     ScintillaObject *sci = editor->sci;
 1459     gint size;
 1460     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 1461 
 1462     g_return_val_if_fail(line >= 0, 0);
 1463 
 1464     size = sci_get_line_indentation(sci, line);
 1465 
 1466     if (iprefs->auto_indent_mode > GEANY_AUTOINDENT_BASIC)
 1467     {
 1468         gint additional_indent = 0;
 1469 
 1470         if (lexer_has_braces(sci))
 1471             additional_indent = iprefs->width * get_brace_indent(sci, line);
 1472         else if (sci_get_lexer(sci) == SCLEX_PYTHON) /* Python/Cython */
 1473             additional_indent = iprefs->width * get_python_indent(sci, line);
 1474 
 1475         /* HTML lexer "has braces" because of PHP and JavaScript.  If get_brace_indent() did not
 1476          * recommend us to insert additional indent, we are probably not in PHP/JavaScript chunk and
 1477          * should make the XML-related check */
 1478         if (additional_indent == 0 &&
 1479             (sci_get_lexer(sci) == SCLEX_HTML ||
 1480             sci_get_lexer(sci) == SCLEX_XML) &&
 1481             editor->document->file_type->priv->xml_indent_tags)
 1482         {
 1483             size += iprefs->width * get_xml_indent(sci, line);
 1484         }
 1485 
 1486         size += additional_indent;
 1487     }
 1488     return size;
 1489 }
 1490 
 1491 
 1492 static void insert_indent_after_line(GeanyEditor *editor, gint line)
 1493 {
 1494     ScintillaObject *sci = editor->sci;
 1495     gint line_indent = sci_get_line_indentation(sci, line);
 1496     gint size = get_indent_size_after_line(editor, line);
 1497     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 1498     gchar *text;
 1499 
 1500     if (size == 0)
 1501         return;
 1502 
 1503     if (iprefs->type == GEANY_INDENT_TYPE_TABS && size == line_indent)
 1504     {
 1505         /* support tab indents, space aligns style - copy last line 'indent' exactly */
 1506         gint start = sci_get_position_from_line(sci, line);
 1507         gint end = sci_get_line_indent_position(sci, line);
 1508 
 1509         text = sci_get_contents_range(sci, start, end);
 1510     }
 1511     else
 1512     {
 1513         text = get_whitespace(iprefs, size);
 1514     }
 1515     sci_add_text(sci, text);
 1516     g_free(text);
 1517 }
 1518 
 1519 
 1520 static void auto_close_chars(ScintillaObject *sci, gint pos, gchar c)
 1521 {
 1522     const gchar *closing_char = NULL;
 1523     gint end_pos = -1;
 1524 
 1525     if (utils_isbrace(c, 0))
 1526         end_pos = sci_find_matching_brace(sci, pos - 1);
 1527 
 1528     switch (c)
 1529     {
 1530         case '(':
 1531             if ((editor_prefs.autoclose_chars & GEANY_AC_PARENTHESIS) && end_pos == -1)
 1532                 closing_char = ")";
 1533             break;
 1534         case '{':
 1535             if ((editor_prefs.autoclose_chars & GEANY_AC_CBRACKET) && end_pos == -1)
 1536                 closing_char = "}";
 1537             break;
 1538         case '[':
 1539             if ((editor_prefs.autoclose_chars & GEANY_AC_SBRACKET) && end_pos == -1)
 1540                 closing_char = "]";
 1541             break;
 1542         case '\'':
 1543             if (editor_prefs.autoclose_chars & GEANY_AC_SQUOTE)
 1544                 closing_char = "'";
 1545             break;
 1546         case '"':
 1547             if (editor_prefs.autoclose_chars & GEANY_AC_DQUOTE)
 1548                 closing_char = "\"";
 1549             break;
 1550     }
 1551 
 1552     if (closing_char != NULL)
 1553     {
 1554         sci_add_text(sci, closing_char);
 1555         sci_set_current_position(sci, pos, TRUE);
 1556     }
 1557 }
 1558 
 1559 
 1560 /* Finds a corresponding matching brace to the given pos
 1561  * (this is taken from Scintilla Editor.cxx,
 1562  * fit to work with close_block) */
 1563 static gint brace_match(ScintillaObject *sci, gint pos)
 1564 {
 1565     gchar chBrace = sci_get_char_at(sci, pos);
 1566     gchar chSeek = utils_brace_opposite(chBrace);
 1567     gchar chAtPos;
 1568     gint direction = -1;
 1569     gint styBrace;
 1570     gint depth = 1;
 1571     gint styAtPos;
 1572 
 1573     /* Hack: we need the style at @p pos but it isn't computed yet, so force styling
 1574      * of this very position */
 1575     sci_colourise(sci, pos, pos + 1);
 1576 
 1577     styBrace = sci_get_style_at(sci, pos);
 1578 
 1579     if (utils_is_opening_brace(chBrace, editor_prefs.brace_match_ltgt))
 1580         direction = 1;
 1581 
 1582     pos += direction;
 1583     while ((pos >= 0) && (pos < sci_get_length(sci)))
 1584     {
 1585         chAtPos = sci_get_char_at(sci, pos);
 1586         styAtPos = sci_get_style_at(sci, pos);
 1587 
 1588         if ((pos > sci_get_end_styled(sci)) || (styAtPos == styBrace))
 1589         {
 1590             if (chAtPos == chBrace)
 1591                 depth++;
 1592             if (chAtPos == chSeek)
 1593                 depth--;
 1594             if (depth == 0)
 1595                 return pos;
 1596         }
 1597         pos += direction;
 1598     }
 1599     return -1;
 1600 }
 1601 
 1602 
 1603 /* Called after typing '}'. */
 1604 static void close_block(GeanyEditor *editor, gint pos)
 1605 {
 1606     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 1607     gint x = 0, cnt = 0;
 1608     gint line, line_len;
 1609     gchar *line_buf;
 1610     ScintillaObject *sci;
 1611     gint line_indent, last_indent;
 1612 
 1613     if (iprefs->auto_indent_mode < GEANY_AUTOINDENT_CURRENTCHARS)
 1614         return;
 1615     g_return_if_fail(editor != NULL && editor->document->file_type != NULL);
 1616 
 1617     sci = editor->sci;
 1618 
 1619     if (! lexer_has_braces(sci))
 1620         return;
 1621 
 1622     line = sci_get_line_from_position(sci, pos);
 1623     line_len = sci_get_line_end_position(sci, line) - sci_get_position_from_line(sci, line);
 1624 
 1625     /* check that the line is empty, to not kill text in the line */
 1626     line_buf = sci_get_line(sci, line);
 1627     line_buf[line_len] = '\0';
 1628     while (x < line_len)
 1629     {
 1630         if (isspace(line_buf[x]))
 1631             cnt++;
 1632         x++;
 1633     }
 1634     g_free(line_buf);
 1635 
 1636     if ((line_len - 1) != cnt)
 1637         return;
 1638 
 1639     if (iprefs->auto_indent_mode == GEANY_AUTOINDENT_MATCHBRACES)
 1640     {
 1641         gint start_brace = brace_match(sci, pos);
 1642 
 1643         if (start_brace >= 0)
 1644         {
 1645             gint line_start;
 1646             gint brace_line = sci_get_line_from_position(sci, start_brace);
 1647             gint size = sci_get_line_indentation(sci, brace_line);
 1648             gchar *ind = get_whitespace(iprefs, size);
 1649             gchar *text = g_strconcat(ind, "}", NULL);
 1650 
 1651             line_start = sci_get_position_from_line(sci, line);
 1652             sci_set_anchor(sci, line_start);
 1653             sci_replace_sel(sci, text);
 1654             g_free(text);
 1655             g_free(ind);
 1656             return;
 1657         }
 1658         /* fall through - unmatched brace (possibly because of TCL, PHP lexer bugs) */
 1659     }
 1660 
 1661     /* GEANY_AUTOINDENT_CURRENTCHARS */
 1662     line_indent = sci_get_line_indentation(sci, line);
 1663     last_indent = sci_get_line_indentation(sci, line - 1);
 1664 
 1665     if (line_indent < last_indent)
 1666         return;
 1667     line_indent -= iprefs->width;
 1668     line_indent = MAX(0, line_indent);
 1669     sci_set_line_indentation(sci, line, line_indent);
 1670 }
 1671 
 1672 
 1673 /* checks whether @p c is an ASCII character (e.g. < 0x80) */
 1674 #define IS_ASCII(c) (((unsigned char)(c)) < 0x80)
 1675 
 1676 
 1677 /* Reads the word at given cursor position and writes it into the given buffer. The buffer will be
 1678  * NULL terminated in any case, even when the word is truncated because wordlen is too small.
 1679  * position can be -1, then the current position is used.
 1680  * wc are the wordchars to use, if NULL, GEANY_WORDCHARS will be used */
 1681 static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, gsize wordlen,
 1682         const gchar *wc, gboolean stem)
 1683 {
 1684     gint line, line_start, startword, endword;
 1685     gchar *chunk;
 1686     ScintillaObject *sci;
 1687 
 1688     g_return_if_fail(editor != NULL);
 1689     sci = editor->sci;
 1690 
 1691     if (pos == -1)
 1692         pos = sci_get_current_position(sci);
 1693 
 1694     line = sci_get_line_from_position(sci, pos);
 1695     line_start = sci_get_position_from_line(sci, line);
 1696     startword = pos - line_start;
 1697     endword = pos - line_start;
 1698 
 1699     word[0] = '\0';
 1700     chunk = sci_get_line(sci, line);
 1701 
 1702     if (wc == NULL)
 1703         wc = GEANY_WORDCHARS;
 1704 
 1705     /* the checks for "c < 0" are to allow any Unicode character which should make the code
 1706      * a little bit more Unicode safe, anyway, this allows also any Unicode punctuation,
 1707      * TODO: improve this code */
 1708     while (startword > 0 && (strchr(wc, chunk[startword - 1]) || ! IS_ASCII(chunk[startword - 1])))
 1709         startword--;
 1710     if (!stem)
 1711     {
 1712         while (chunk[endword] != 0 && (strchr(wc, chunk[endword]) || ! IS_ASCII(chunk[endword])))
 1713             endword++;
 1714     }
 1715 
 1716     if (startword != endword)
 1717     {
 1718         chunk[endword] = '\0';
 1719 
 1720         g_strlcpy(word, chunk + startword, wordlen); /* ensure null terminated */
 1721     }
 1722     else
 1723         g_strlcpy(word, "", wordlen);
 1724 
 1725     g_free(chunk);
 1726 }
 1727 
 1728 
 1729 /* Reads the word at given cursor position and writes it into the given buffer. The buffer will be
 1730  * NULL terminated in any case, even when the word is truncated because wordlen is too small.
 1731  * position can be -1, then the current position is used.
 1732  * wc are the wordchars to use, if NULL, GEANY_WORDCHARS will be used */
 1733 void editor_find_current_word(GeanyEditor *editor, gint pos, gchar *word, gsize wordlen,
 1734                               const gchar *wc)
 1735 {
 1736     read_current_word(editor, pos, word, wordlen, wc, FALSE);
 1737 }
 1738 
 1739 
 1740 /* Same as editor_find_current_word() but uses editor's word boundaries to decide what the word
 1741  * is.  This should be used e.g. to get the word to search for */
 1742 void editor_find_current_word_sciwc(GeanyEditor *editor, gint pos, gchar *word, gsize wordlen)
 1743 {
 1744     gint start;
 1745     gint end;
 1746 
 1747     g_return_if_fail(editor != NULL);
 1748 
 1749     if (pos == -1)
 1750         pos = sci_get_current_position(editor->sci);
 1751 
 1752     start = sci_word_start_position(editor->sci, pos, TRUE);
 1753     end = sci_word_end_position(editor->sci, pos, TRUE);
 1754 
 1755     if (start == end) /* caret in whitespaces sequence */
 1756         *word = 0;
 1757     else
 1758     {
 1759         if ((guint)(end - start) >= wordlen)
 1760             end = start + (wordlen - 1);
 1761         sci_get_text_range(editor->sci, start, end, word);
 1762     }
 1763 }
 1764 
 1765 
 1766 /**
 1767  *  Finds the word at the position specified by @a pos. If any word is found, it is returned.
 1768  *  Otherwise NULL is returned.
 1769  *  Additional wordchars can be specified to define what to consider as a word.
 1770  *
 1771  *  @param editor The editor to operate on.
 1772  *  @param pos The position where the word should be read from.
 1773  *             May be @c -1 to use the current position.
 1774  *  @param wordchars The wordchars to separate words. wordchars mean all characters to count
 1775  *                   as part of a word. May be @c NULL to use the default wordchars,
 1776  *                   see @ref GEANY_WORDCHARS.
 1777  *
 1778  *  @return @nullable A newly-allocated string containing the word at the given @a pos or @c NULL.
 1779  *          Should be freed when no longer needed.
 1780  *
 1781  *  @since 0.16
 1782  */
 1783 GEANY_API_SYMBOL
 1784 gchar *editor_get_word_at_pos(GeanyEditor *editor, gint pos, const gchar *wordchars)
 1785 {
 1786     static gchar cword[GEANY_MAX_WORD_LENGTH];
 1787 
 1788     g_return_val_if_fail(editor != NULL, FALSE);
 1789 
 1790     read_current_word(editor, pos, cword, sizeof(cword), wordchars, FALSE);
 1791 
 1792     return (*cword == '\0') ? NULL : g_strdup(cword);
 1793 }
 1794 
 1795 
 1796 /* Read the word up to position @a pos. */
 1797 static const gchar *
 1798 editor_read_word_stem(GeanyEditor *editor, gint pos, const gchar *wordchars)
 1799 {
 1800     static gchar word[GEANY_MAX_WORD_LENGTH];
 1801 
 1802     read_current_word(editor, pos, word, sizeof word, wordchars, TRUE);
 1803 
 1804     return (*word) ? word : NULL;
 1805 }
 1806 
 1807 
 1808 static gint find_previous_brace(ScintillaObject *sci, gint pos)
 1809 {
 1810     gint orig_pos = pos;
 1811 
 1812     while (pos >= 0 && pos > orig_pos - 300)
 1813     {
 1814         gchar c = sci_get_char_at(sci, pos);
 1815         if (utils_is_opening_brace(c, editor_prefs.brace_match_ltgt))
 1816             return pos;
 1817         pos--;
 1818     }
 1819     return -1;
 1820 }
 1821 
 1822 
 1823 static gint find_start_bracket(ScintillaObject *sci, gint pos)
 1824 {
 1825     gint brackets = 0;
 1826     gint orig_pos = pos;
 1827 
 1828     while (pos > 0 && pos > orig_pos - 300)
 1829     {
 1830         gchar c = sci_get_char_at(sci, pos);
 1831 
 1832         if (c == ')') brackets++;
 1833         else if (c == '(') brackets--;
 1834         if (brackets < 0) return pos;   /* found start bracket */
 1835         pos--;
 1836     }
 1837     return -1;
 1838 }
 1839 
 1840 
 1841 static gboolean append_calltip(GString *str, const TMTag *tag, GeanyFiletypeID ft_id)
 1842 {
 1843     if (! tag->arglist)
 1844         return FALSE;
 1845 
 1846     if (ft_id != GEANY_FILETYPES_PASCAL && ft_id != GEANY_FILETYPES_GO)
 1847     {   /* usual calltips: "retval tagname (arglist)" */
 1848         if (tag->var_type)
 1849         {
 1850             guint i;
 1851 
 1852             g_string_append(str, tag->var_type);
 1853             for (i = 0; i < tag->pointerOrder; i++)
 1854             {
 1855                 g_string_append_c(str, '*');
 1856             }
 1857             g_string_append_c(str, ' ');
 1858         }
 1859         if (tag->scope)
 1860         {
 1861             const gchar *cosep = symbols_get_context_separator(ft_id);
 1862 
 1863             g_string_append(str, tag->scope);
 1864             g_string_append(str, cosep);
 1865         }
 1866         g_string_append(str, tag->name);
 1867         g_string_append_c(str, ' ');
 1868         g_string_append(str, tag->arglist);
 1869     }
 1870     else
 1871     {   /* special case Pascal/Go calltips: "tagname (arglist) : retval"
 1872          * (with ':' omitted for Go) */
 1873         g_string_append(str, tag->name);
 1874         g_string_append_c(str, ' ');
 1875         g_string_append(str, tag->arglist);
 1876 
 1877         if (!EMPTY(tag->var_type))
 1878         {
 1879             g_string_append(str, ft_id == GEANY_FILETYPES_PASCAL ? " : " : " ");
 1880             g_string_append(str, tag->var_type);
 1881         }
 1882     }
 1883 
 1884     return TRUE;
 1885 }
 1886 
 1887 
 1888 static gchar *find_calltip(const gchar *word, GeanyFiletype *ft)
 1889 {
 1890     GPtrArray *tags;
 1891     const TMTagType arg_types = tm_tag_function_t | tm_tag_prototype_t |
 1892         tm_tag_method_t | tm_tag_macro_with_arg_t;
 1893     TMTag *tag;
 1894     GString *str = NULL;
 1895     guint i;
 1896 
 1897     g_return_val_if_fail(ft && word && *word, NULL);
 1898 
 1899     /* use all types in case language uses wrong tag type e.g. python "members" instead of "methods" */
 1900     tags = tm_workspace_find(word, NULL, tm_tag_max_t, NULL, ft->lang);
 1901     if (tags->len == 0)
 1902     {
 1903         g_ptr_array_free(tags, TRUE);
 1904         return NULL;
 1905     }
 1906 
 1907     tag = TM_TAG(tags->pdata[0]);
 1908 
 1909     if (ft->id == GEANY_FILETYPES_D &&
 1910         (tag->type == tm_tag_class_t || tag->type == tm_tag_struct_t))
 1911     {
 1912         g_ptr_array_free(tags, TRUE);
 1913         /* user typed e.g. 'new Classname(' so lookup D constructor Classname::this() */
 1914         tags = tm_workspace_find("this", tag->name, arg_types, NULL, ft->lang);
 1915         if (tags->len == 0)
 1916         {
 1917             g_ptr_array_free(tags, TRUE);
 1918             return NULL;
 1919         }
 1920     }
 1921 
 1922     /* remove tags with no argument list */
 1923     for (i = 0; i < tags->len; i++)
 1924     {
 1925         tag = TM_TAG(tags->pdata[i]);
 1926 
 1927         if (! tag->arglist)
 1928             tags->pdata[i] = NULL;
 1929     }
 1930     tm_tags_prune((GPtrArray *) tags);
 1931     if (tags->len == 0)
 1932     {
 1933         g_ptr_array_free(tags, TRUE);
 1934         return NULL;
 1935     }
 1936     else
 1937     {   /* remove duplicate calltips */
 1938         TMTagAttrType sort_attr[] = {tm_tag_attr_name_t, tm_tag_attr_scope_t,
 1939             tm_tag_attr_arglist_t, 0};
 1940 
 1941         tm_tags_sort((GPtrArray *) tags, sort_attr, TRUE, FALSE);
 1942     }
 1943 
 1944     /* if the current word has changed since last time, start with the first tag match */
 1945     if (! utils_str_equal(word, calltip.last_word))
 1946         calltip.tag_index = 0;
 1947     /* cache the current word for next time */
 1948     g_free(calltip.last_word);
 1949     calltip.last_word = g_strdup(word);
 1950     calltip.tag_index = MIN(calltip.tag_index, tags->len - 1);  /* ensure tag_index is in range */
 1951 
 1952     for (i = calltip.tag_index; i < tags->len; i++)
 1953     {
 1954         tag = TM_TAG(tags->pdata[i]);
 1955 
 1956         if (str == NULL)
 1957         {
 1958             str = g_string_new(NULL);
 1959             if (calltip.tag_index > 0)
 1960                 g_string_prepend(str, "\001 "); /* up arrow */
 1961             append_calltip(str, tag, FILETYPE_ID(ft));
 1962         }
 1963         else /* add a down arrow */
 1964         {
 1965             if (calltip.tag_index > 0)  /* already have an up arrow */
 1966                 g_string_insert_c(str, 1, '\002');
 1967             else
 1968                 g_string_prepend(str, "\002 ");
 1969             break;
 1970         }
 1971     }
 1972 
 1973     g_ptr_array_free(tags, TRUE);
 1974 
 1975     if (str)
 1976     {
 1977         gchar *result = str->str;
 1978 
 1979         g_string_free(str, FALSE);
 1980         return result;
 1981     }
 1982     return NULL;
 1983 }
 1984 
 1985 
 1986 /* use pos = -1 to search for the previous unmatched open bracket. */
 1987 gboolean editor_show_calltip(GeanyEditor *editor, gint pos)
 1988 {
 1989     gint orig_pos = pos; /* the position for the calltip */
 1990     gint lexer;
 1991     gint style;
 1992     gchar word[GEANY_MAX_WORD_LENGTH];
 1993     gchar *str;
 1994     ScintillaObject *sci;
 1995 
 1996     g_return_val_if_fail(editor != NULL, FALSE);
 1997     g_return_val_if_fail(editor->document->file_type != NULL, FALSE);
 1998 
 1999     sci = editor->sci;
 2000 
 2001     lexer = sci_get_lexer(sci);
 2002 
 2003     if (pos == -1)
 2004     {
 2005         /* position of '(' is unknown, so go backwards from current position to find it */
 2006         pos = sci_get_current_position(sci);
 2007         pos--;
 2008         orig_pos = pos;
 2009         pos = (lexer == SCLEX_LATEX) ? find_previous_brace(sci, pos) :
 2010             find_start_bracket(sci, pos);
 2011         if (pos == -1)
 2012             return FALSE;
 2013     }
 2014 
 2015     /* the style 1 before the brace (which may be highlighted) */
 2016     style = sci_get_style_at(sci, pos - 1);
 2017     if (! highlighting_is_code_style(lexer, style))
 2018         return FALSE;
 2019 
 2020     while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
 2021         pos--;
 2022 
 2023     /* skip possible generic/template specification, like foo<int>() */
 2024     if (sci_get_char_at(sci, pos - 1) == '>')
 2025     {
 2026         pos = sci_find_matching_brace(sci, pos - 1);
 2027         if (pos == -1)
 2028             return FALSE;
 2029 
 2030         while (pos > 0 && isspace(sci_get_char_at(sci, pos - 1)))
 2031             pos--;
 2032     }
 2033 
 2034     word[0] = '\0';
 2035     editor_find_current_word(editor, pos - 1, word, sizeof word, NULL);
 2036     if (word[0] == '\0')
 2037         return FALSE;
 2038 
 2039     str = find_calltip(word, editor->document->file_type);
 2040     if (str)
 2041     {
 2042         g_free(calltip.text);   /* free the old calltip */
 2043         calltip.text = str;
 2044         calltip.pos = orig_pos;
 2045         calltip.sci = sci;
 2046         calltip.set = TRUE;
 2047         utils_wrap_string(calltip.text, -1);
 2048         SSM(sci, SCI_CALLTIPSHOW, orig_pos, (sptr_t) calltip.text);
 2049         return TRUE;
 2050     }
 2051     return FALSE;
 2052 }
 2053 
 2054 
 2055 gchar *editor_get_calltip_text(GeanyEditor *editor, const TMTag *tag)
 2056 {
 2057     GString *str;
 2058 
 2059     g_return_val_if_fail(editor != NULL, NULL);
 2060 
 2061     str = g_string_new(NULL);
 2062     if (append_calltip(str, tag, editor->document->file_type->id))
 2063         return g_string_free(str, FALSE);
 2064     else
 2065         return g_string_free(str, TRUE);
 2066 }
 2067 
 2068 
 2069 /* Current document & global tags autocompletion */
 2070 static gboolean
 2071 autocomplete_tags(GeanyEditor *editor, GeanyFiletype *ft, const gchar *root, gsize rootlen)
 2072 {
 2073     GPtrArray *tags;
 2074     gboolean found;
 2075 
 2076     g_return_val_if_fail(editor, FALSE);
 2077 
 2078     tags = tm_workspace_find_prefix(root, ft->lang, editor_prefs.autocompletion_max_entries);
 2079     found = tags->len > 0;
 2080     if (found)
 2081         show_tags_list(editor, tags, rootlen);
 2082     g_ptr_array_free(tags, TRUE);
 2083 
 2084     return found;
 2085 }
 2086 
 2087 
 2088 static gboolean autocomplete_check_html(GeanyEditor *editor, gint style, gint pos)
 2089 {
 2090     GeanyFiletype *ft = editor->document->file_type;
 2091     gboolean try = FALSE;
 2092 
 2093     /* use entity completion when style is not JavaScript, ASP, Python, PHP, ...
 2094      * (everything after SCE_HJ_START is for embedded scripting languages) */
 2095     if (ft->id == GEANY_FILETYPES_HTML && style < SCE_HJ_START)
 2096         try = TRUE;
 2097     else if (sci_get_lexer(editor->sci) == SCLEX_XML && style < SCE_HJ_START)
 2098         try = TRUE;
 2099     else if (ft->id == GEANY_FILETYPES_PHP)
 2100     {
 2101         /* use entity completion when style is outside of PHP styles */
 2102         if (! is_style_php(style))
 2103             try = TRUE;
 2104     }
 2105     if (try)
 2106     {
 2107         gchar root[GEANY_MAX_WORD_LENGTH];
 2108         gchar *tmp;
 2109 
 2110         read_current_word(editor, pos, root, sizeof(root), GEANY_WORDCHARS"&", TRUE);
 2111 
 2112         /* Allow something like "&quot;some text&quot;".
 2113          * for entity completion we want to have completion for '&' within words. */
 2114         tmp = strchr(root, '&');
 2115         if (tmp != NULL)
 2116         {
 2117             return autocomplete_tags(editor, filetypes_index(GEANY_FILETYPES_HTML), tmp, strlen(tmp));
 2118         }
 2119     }
 2120     return FALSE;
 2121 }
 2122 
 2123 
 2124 /* Algorithm based on based on Scite's StartAutoCompleteWord()
 2125  * @returns a sorted list of words matching @p root */
 2126 static GSList *get_doc_words(ScintillaObject *sci, gchar *root, gsize rootlen)
 2127 {
 2128     gchar *word;
 2129     gint len, current, word_end;
 2130     gint pos_find, flags;
 2131     guint word_length;
 2132     gsize nmatches = 0;
 2133     GSList *words = NULL;
 2134     struct Sci_TextToFind ttf;
 2135 
 2136     len = sci_get_length(sci);
 2137     current = sci_get_current_position(sci) - rootlen;
 2138 
 2139     ttf.lpstrText = root;
 2140     ttf.chrg.cpMin = 0;
 2141     ttf.chrg.cpMax = len;
 2142     ttf.chrgText.cpMin = 0;
 2143     ttf.chrgText.cpMax = 0;
 2144     flags = SCFIND_WORDSTART | SCFIND_MATCHCASE;
 2145 
 2146     /* search the whole document for the word root and collect results */
 2147     pos_find = SSM(sci, SCI_FINDTEXT, flags, (uptr_t) &ttf);
 2148     while (pos_find >= 0 && pos_find < len)
 2149     {
 2150         word_end = pos_find + rootlen;
 2151         if (pos_find != current)
 2152         {
 2153             word_end = sci_word_end_position(sci, word_end, TRUE);
 2154 
 2155             word_length = word_end - pos_find;
 2156             if (word_length > rootlen)
 2157             {
 2158                 word = sci_get_contents_range(sci, pos_find, word_end);
 2159                 /* search whether we already have the word in, otherwise add it */
 2160                 if (g_slist_find_custom(words, word, (GCompareFunc)strcmp) != NULL)
 2161                     g_free(word);
 2162                 else
 2163                 {
 2164                     words = g_slist_prepend(words, word);
 2165                     nmatches++;
 2166                 }
 2167 
 2168                 if (nmatches == editor_prefs.autocompletion_max_entries)
 2169                     break;
 2170             }
 2171         }
 2172         ttf.chrg.cpMin = word_end;
 2173         pos_find = SSM(sci, SCI_FINDTEXT, flags, (uptr_t) &ttf);
 2174     }
 2175 
 2176     return g_slist_sort(words, (GCompareFunc)utils_str_casecmp);
 2177 }
 2178 
 2179 
 2180 static gboolean autocomplete_doc_word(GeanyEditor *editor, gchar *root, gsize rootlen)
 2181 {
 2182     ScintillaObject *sci = editor->sci;
 2183     GSList *words, *node;
 2184     GString *str;
 2185     guint n_words = 0;
 2186 
 2187     words = get_doc_words(sci, root, rootlen);
 2188     if (!words)
 2189     {
 2190         SSM(sci, SCI_AUTOCCANCEL, 0, 0);
 2191         return FALSE;
 2192     }
 2193 
 2194     str = g_string_sized_new(rootlen * 2 * 10);
 2195     foreach_slist(node, words)
 2196     {
 2197         g_string_append(str, node->data);
 2198         g_free(node->data);
 2199         if (node->next)
 2200             g_string_append_c(str, '\n');
 2201         n_words++;
 2202     }
 2203     if (n_words >= editor_prefs.autocompletion_max_entries)
 2204         g_string_append(str, "\n...");
 2205 
 2206     g_slist_free(words);
 2207 
 2208     show_autocomplete(sci, rootlen, str);
 2209     g_string_free(str, TRUE);
 2210     return TRUE;
 2211 }
 2212 
 2213 
 2214 gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean force)
 2215 {
 2216     gint rootlen, lexer, style;
 2217     gchar *root;
 2218     gchar cword[GEANY_MAX_WORD_LENGTH];
 2219     ScintillaObject *sci;
 2220     gboolean ret = FALSE;
 2221     const gchar *wordchars;
 2222     GeanyFiletype *ft;
 2223 
 2224     g_return_val_if_fail(editor != NULL, FALSE);
 2225 
 2226     if (! editor_prefs.auto_complete_symbols && ! force)
 2227         return FALSE;
 2228 
 2229     /* If we are at the beginning of the document, we skip autocompletion as we can't determine the
 2230      * necessary styling information */
 2231     if (G_UNLIKELY(pos < 2))
 2232         return FALSE;
 2233 
 2234     sci = editor->sci;
 2235     ft = editor->document->file_type;
 2236 
 2237     lexer = sci_get_lexer(sci);
 2238     style = sci_get_style_at(sci, pos - 2);
 2239 
 2240     /* don't autocomplete in comments and strings */
 2241     if (!force && !highlighting_is_code_style(lexer, style))
 2242         return FALSE;
 2243 
 2244     ret = autocomplete_check_html(editor, style, pos);
 2245 
 2246     if (ft->id == GEANY_FILETYPES_LATEX)
 2247         wordchars = GEANY_WORDCHARS"\\"; /* add \ to word chars if we are in a LaTeX file */
 2248     else if (ft->id == GEANY_FILETYPES_CSS)
 2249         wordchars = GEANY_WORDCHARS"-"; /* add - because they are part of property names */
 2250     else
 2251         wordchars = GEANY_WORDCHARS;
 2252 
 2253     read_current_word(editor, pos, cword, sizeof(cword), wordchars, TRUE);
 2254     root = cword;
 2255     rootlen = strlen(root);
 2256 
 2257     if (ret || force)
 2258     {
 2259         if (autocomplete_scope_shown)
 2260         {
 2261             autocomplete_scope_shown = FALSE;
 2262             if (!ret)
 2263                 sci_send_command(sci, SCI_AUTOCCANCEL);
 2264         }
 2265     }
 2266     else
 2267     {
 2268         ret = autocomplete_scope(editor, root, rootlen);
 2269         if (!ret && autocomplete_scope_shown)
 2270             sci_send_command(sci, SCI_AUTOCCANCEL);
 2271         autocomplete_scope_shown = ret;
 2272     }
 2273 
 2274     if (!ret && rootlen > 0)
 2275     {
 2276         if (ft->id == GEANY_FILETYPES_PHP && style == SCE_HPHP_DEFAULT &&
 2277             rootlen == 3 && strcmp(root, "php") == 0 && pos >= 5 &&
 2278             sci_get_char_at(sci, pos - 5) == '<' &&
 2279             sci_get_char_at(sci, pos - 4) == '?')
 2280         {
 2281             /* nothing, don't complete PHP open tags */
 2282         }
 2283         else
 2284         {
 2285             /* force is set when called by keyboard shortcut, otherwise start at the
 2286              * editor_prefs.symbolcompletion_min_chars'th char */
 2287             if (force || rootlen >= editor_prefs.symbolcompletion_min_chars)
 2288             {
 2289                 /* complete tags, except if forcing when completion is already visible */
 2290                 if (!(force && SSM(sci, SCI_AUTOCACTIVE, 0, 0)))
 2291                     ret = autocomplete_tags(editor, editor->document->file_type, root, rootlen);
 2292 
 2293                 /* If forcing and there's nothing else to show, complete from words in document */
 2294                 if (!ret && (force || editor_prefs.autocomplete_doc_words))
 2295                     ret = autocomplete_doc_word(editor, root, rootlen);
 2296             }
 2297         }
 2298     }
 2299     if (!ret && force)
 2300         utils_beep();
 2301 
 2302     return ret;
 2303 }
 2304 
 2305 
 2306 static const gchar *snippets_find_completion_by_name(const gchar *type, const gchar *name)
 2307 {
 2308     gchar *result = NULL;
 2309     GHashTable *tmp;
 2310 
 2311     g_return_val_if_fail(type != NULL && name != NULL, NULL);
 2312 
 2313     tmp = g_hash_table_lookup(snippet_hash, type);
 2314     if (tmp != NULL)
 2315     {
 2316         result = g_hash_table_lookup(tmp, name);
 2317     }
 2318     /* whether nothing is set for the current filetype(tmp is NULL) or
 2319      * the particular completion for this filetype is not set (result is NULL) */
 2320     if (tmp == NULL || result == NULL)
 2321     {
 2322         tmp = g_hash_table_lookup(snippet_hash, "Default");
 2323         if (tmp != NULL)
 2324         {
 2325             result = g_hash_table_lookup(tmp, name);
 2326         }
 2327     }
 2328     /* if result is still NULL here, no completion could be found */
 2329 
 2330     /* result is owned by the hash table and will be freed when the table will destroyed */
 2331     return result;
 2332 }
 2333 
 2334 
 2335 static void snippets_replace_specials(gpointer key, gpointer value, gpointer user_data)
 2336 {
 2337     gchar *needle;
 2338     GString *pattern = user_data;
 2339 
 2340     g_return_if_fail(key != NULL);
 2341     g_return_if_fail(value != NULL);
 2342 
 2343     needle = g_strconcat("%", (gchar*) key, "%", NULL);
 2344 
 2345     utils_string_replace_all(pattern, needle, (gchar*) value);
 2346     g_free(needle);
 2347 }
 2348 
 2349 
 2350 static void fix_indentation(GeanyEditor *editor, GString *buf)
 2351 {
 2352     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 2353     gchar *whitespace;
 2354     GRegex *regex;
 2355     gint cflags = G_REGEX_MULTILINE;
 2356 
 2357     /* transform leading tabs into indent widths (in spaces) */
 2358     whitespace = g_strnfill(iprefs->width, ' ');
 2359     regex = g_regex_new("^ *(\t)", cflags, 0, NULL);
 2360     while (utils_string_regex_replace_all(buf, regex, 1, whitespace, TRUE));
 2361     g_regex_unref(regex);
 2362 
 2363     /* remaining tabs are for alignment */
 2364     if (iprefs->type != GEANY_INDENT_TYPE_TABS)
 2365         utils_string_replace_all(buf, "\t", whitespace);
 2366 
 2367     /* use leading tabs */
 2368     if (iprefs->type != GEANY_INDENT_TYPE_SPACES)
 2369     {
 2370         gchar *str;
 2371 
 2372         /* for tabs+spaces mode we want the real tab width, not indent width */
 2373         SETPTR(whitespace, g_strnfill(sci_get_tab_width(editor->sci), ' '));
 2374         str = g_strdup_printf("^\t*(%s)", whitespace);
 2375 
 2376         regex = g_regex_new(str, cflags, 0, NULL);
 2377         while (utils_string_regex_replace_all(buf, regex, 1, "\t", TRUE));
 2378         g_regex_unref(regex);
 2379         g_free(str);
 2380     }
 2381     g_free(whitespace);
 2382 }
 2383 
 2384 
 2385 typedef struct
 2386 {
 2387     Sci_Position start, len;
 2388 } SelectionRange;
 2389 
 2390 
 2391 #define CURSOR_PLACEHOLDER "_" /* Would rather use … but not all docs are unicode */
 2392 
 2393 
 2394 /* Replaces the internal cursor markers with the placeholder suitable for
 2395  * display. Except for the first cursor if indicator_for_first is FALSE,
 2396  * which is simply deleted.
 2397  *
 2398  * Returns insertion points as SelectionRange list, so that the caller
 2399  * can use the positions (currently for indicators). */
 2400 static GSList *replace_cursor_markers(GeanyEditor *editor, GString *template,
 2401                                       gboolean indicator_for_first)
 2402 {
 2403     gint i = 0;
 2404     GSList *temp_list = NULL;
 2405     gint cursor_steps = 0;
 2406     SelectionRange *sel;
 2407 
 2408     while (TRUE)
 2409     {
 2410         cursor_steps = utils_string_find(template, cursor_steps, -1, geany_cursor_marker);
 2411         if (cursor_steps == -1)
 2412             break;
 2413 
 2414         sel = g_new0(SelectionRange, 1);
 2415         sel->start = cursor_steps;
 2416         g_string_erase(template, cursor_steps, strlen(geany_cursor_marker));
 2417         if (i > 0 || indicator_for_first)
 2418         {
 2419             g_string_insert(template, cursor_steps, CURSOR_PLACEHOLDER);
 2420             sel->len = sizeof(CURSOR_PLACEHOLDER) - 1;
 2421         }
 2422         i += 1;
 2423         temp_list = g_slist_append(temp_list, sel);
 2424     }
 2425 
 2426     return temp_list;
 2427 }
 2428 
 2429 
 2430 /** Inserts text, replacing \\t tab chars (@c 0x9) and \\n newline chars (@c 0xA)
 2431  * accordingly for the document.
 2432  * - Leading tabs are replaced with the correct indentation.
 2433  * - Non-leading tabs are replaced with spaces (except when using 'Tabs' indent type).
 2434  * - Newline chars are replaced with the correct line ending string.
 2435  * This is very useful for inserting code without having to handle the indent
 2436  * type yourself (Tabs & Spaces mode can be tricky).
 2437  * @param editor Editor.
 2438  * @param text Intended as e.g. @c "if (foo)\n\tbar();".
 2439  * @param insert_pos Document position to insert text at.
 2440  * @param cursor_index If >= 0, the index into @a text to place the cursor.
 2441  * @param newline_indent_size Indentation size (in spaces) to insert for each newline; use
 2442  * -1 to read the indent size from the line with @a insert_pos on it.
 2443  * @param replace_newlines Whether to replace newlines. If
 2444  * newlines have been replaced already, this should be false, to avoid errors e.g. on Windows.
 2445  * @warning Make sure all \\t tab chars in @a text are intended as indent widths or alignment,
 2446  * not hard tabs, as those won't be preserved.
 2447  * @note This doesn't scroll the cursor in view afterwards. **/
 2448 GEANY_API_SYMBOL
 2449 void editor_insert_text_block(GeanyEditor *editor, const gchar *text, gint insert_pos,
 2450         gint cursor_index, gint newline_indent_size, gboolean replace_newlines)
 2451 {
 2452     ScintillaObject *sci = editor->sci;
 2453     gint line_start = sci_get_line_from_position(sci, insert_pos);
 2454     GString *buf;
 2455     const gchar *eol = editor_get_eol_char(editor);
 2456     GSList *jump_locs, *item;
 2457 
 2458     g_return_if_fail(text);
 2459     g_return_if_fail(editor != NULL);
 2460     g_return_if_fail(insert_pos >= 0);
 2461 
 2462     buf = g_string_new(text);
 2463 
 2464     if (cursor_index >= 0)
 2465         g_string_insert(buf, cursor_index, geany_cursor_marker);    /* remember cursor pos */
 2466 
 2467     if (newline_indent_size == -1)
 2468     {
 2469         /* count indent size up to insert_pos instead of asking sci
 2470          * because there may be spaces after it */
 2471         gchar *tmp = sci_get_line(sci, line_start);
 2472         gint idx;
 2473 
 2474         idx = insert_pos - sci_get_position_from_line(sci, line_start);
 2475         tmp[idx] = '\0';
 2476         newline_indent_size = count_indent_size(editor, tmp);
 2477         g_free(tmp);
 2478     }
 2479 
 2480     /* Add line indents (in spaces) */
 2481     if (newline_indent_size > 0)
 2482     {
 2483         const gchar *nl = replace_newlines ? "\n" : eol;
 2484         gchar *whitespace;
 2485 
 2486         whitespace = g_strnfill(newline_indent_size, ' ');
 2487         SETPTR(whitespace, g_strconcat(nl, whitespace, NULL));
 2488         utils_string_replace_all(buf, nl, whitespace);
 2489         g_free(whitespace);
 2490     }
 2491 
 2492     /* transform line endings */
 2493     if (replace_newlines)
 2494         utils_string_replace_all(buf, "\n", eol);
 2495 
 2496     fix_indentation(editor, buf);
 2497 
 2498     jump_locs = replace_cursor_markers(editor, buf, cursor_index < 0);
 2499     sci_insert_text(sci, insert_pos, buf->str);
 2500 
 2501     foreach_list(item, jump_locs)
 2502     {
 2503         SelectionRange *sel = item->data;
 2504         gint start = insert_pos + sel->start;
 2505         gint end = start + sel->len;
 2506         editor_indicator_set_on_range(editor, GEANY_INDICATOR_SNIPPET, start, end);
 2507         /* jump to first cursor position initially */
 2508         if (item == jump_locs)
 2509             sci_set_selection(sci, start, end);
 2510     }
 2511 
 2512     /* Set cursor to the requested index, or by default to after the snippet */
 2513     if (cursor_index >= 0)
 2514         sci_set_current_position(sci, insert_pos + cursor_index, FALSE);
 2515     else if (jump_locs == NULL)
 2516         sci_set_current_position(sci, insert_pos + buf->len, FALSE);
 2517 
 2518     g_slist_free_full(jump_locs, g_free);
 2519     g_string_free(buf, TRUE);
 2520 }
 2521 
 2522 
 2523 static gboolean find_next_snippet_indicator(GeanyEditor *editor, SelectionRange *sel)
 2524 {
 2525     ScintillaObject *sci = editor->sci;
 2526     gint pos = sci_get_current_position(sci);
 2527 
 2528     if (pos == sci_get_length(sci))
 2529         return FALSE; /* EOF */
 2530 
 2531     /* Rewind the cursor a bit if we're in the middle (or start) of an indicator,
 2532      * and treat that as the next indicator. */
 2533     while (SSM(sci, SCI_INDICATORVALUEAT, GEANY_INDICATOR_SNIPPET, pos) && pos > 0)
 2534         pos -= 1;
 2535 
 2536     /* Be careful at the beginning of the file */
 2537     if (SSM(sci, SCI_INDICATORVALUEAT, GEANY_INDICATOR_SNIPPET, pos))
 2538         sel->start = pos;
 2539     else
 2540         sel->start = SSM(sci, SCI_INDICATOREND, GEANY_INDICATOR_SNIPPET, pos);
 2541     sel->len = SSM(sci, SCI_INDICATOREND, GEANY_INDICATOR_SNIPPET, sel->start) - sel->start;
 2542 
 2543     /* 0 if there is no remaining cursor */
 2544     return sel->len > 0;
 2545 }
 2546 
 2547 
 2548 /* Move the cursor to the next specified cursor position in an inserted snippet.
 2549  * Can, and should, be optimized to give better results */
 2550 gboolean editor_goto_next_snippet_cursor(GeanyEditor *editor)
 2551 {
 2552     ScintillaObject *sci = editor->sci;
 2553     SelectionRange sel;
 2554 
 2555     if (find_next_snippet_indicator(editor, &sel))
 2556     {
 2557         sci_indicator_set(sci, GEANY_INDICATOR_SNIPPET);
 2558         sci_set_selection(sci, sel.start, sel.start + sel.len);
 2559         return TRUE;
 2560     }
 2561     else
 2562     {
 2563         return FALSE;
 2564     }
 2565 }
 2566 
 2567 
 2568 static void snippets_make_replacements(GeanyEditor *editor, GString *pattern)
 2569 {
 2570     GHashTable *specials;
 2571 
 2572     /* replace 'special' completions */
 2573     specials = g_hash_table_lookup(snippet_hash, "Special");
 2574     if (G_LIKELY(specials != NULL))
 2575     {
 2576         g_hash_table_foreach(specials, snippets_replace_specials, pattern);
 2577     }
 2578 
 2579     /* now transform other wildcards */
 2580     utils_string_replace_all(pattern, "%newline%", "\n");
 2581     utils_string_replace_all(pattern, "%ws%", "\t");
 2582 
 2583     /* replace %cursor% by a very unlikely string marker */
 2584     utils_string_replace_all(pattern, "%cursor%", geany_cursor_marker);
 2585 
 2586     /* unescape '%' after all %wildcards% */
 2587     templates_replace_valist(pattern, "{pc}", "%", NULL);
 2588 
 2589     /* replace any template {foo} wildcards */
 2590     templates_replace_common(pattern, editor->document->file_name, editor->document->file_type, NULL);
 2591 }
 2592 
 2593 
 2594 static gboolean snippets_complete_constructs(GeanyEditor *editor, gint pos, const gchar *word)
 2595 {
 2596     ScintillaObject *sci = editor->sci;
 2597     gchar *str;
 2598     const gchar *completion;
 2599     gint str_len;
 2600     gint ft_id = editor->document->file_type->id;
 2601 
 2602     str = g_strdup(word);
 2603     g_strstrip(str);
 2604 
 2605     completion = snippets_find_completion_by_name(filetypes[ft_id]->name, str);
 2606     if (completion == NULL)
 2607     {
 2608         g_free(str);
 2609         return FALSE;
 2610     }
 2611 
 2612     /* remove the typed word, it will be added again by the used auto completion
 2613      * (not really necessary but this makes the auto completion more flexible,
 2614      *  e.g. with a completion like hi=hello, so typing "hi<TAB>" will result in "hello") */
 2615     str_len = strlen(str);
 2616     sci_set_selection_start(sci, pos - str_len);
 2617     sci_set_selection_end(sci, pos);
 2618     sci_replace_sel(sci, "");
 2619     pos -= str_len; /* pos has changed while deleting */
 2620 
 2621     editor_insert_snippet(editor, pos, completion);
 2622     sci_scroll_caret(sci);
 2623 
 2624     g_free(str);
 2625     return TRUE;
 2626 }
 2627 
 2628 
 2629 static gboolean at_eol(ScintillaObject *sci, gint pos)
 2630 {
 2631     gint line = sci_get_line_from_position(sci, pos);
 2632     gchar c;
 2633 
 2634     /* skip any trailing spaces */
 2635     while (TRUE)
 2636     {
 2637         c = sci_get_char_at(sci, pos);
 2638         if (c == ' ' || c == '\t')
 2639             pos++;
 2640         else
 2641             break;
 2642     }
 2643 
 2644     return (pos == sci_get_line_end_position(sci, line));
 2645 }
 2646 
 2647 
 2648 gboolean editor_complete_snippet(GeanyEditor *editor, gint pos)
 2649 {
 2650     gboolean result = FALSE;
 2651     const gchar *wc;
 2652     const gchar *word;
 2653     ScintillaObject *sci;
 2654 
 2655     g_return_val_if_fail(editor != NULL, FALSE);
 2656 
 2657     sci = editor->sci;
 2658     if (sci_has_selection(sci))
 2659         return FALSE;
 2660     /* return if we are editing an existing line (chars on right of cursor) */
 2661     if (keybindings_lookup_item(GEANY_KEY_GROUP_EDITOR,
 2662             GEANY_KEYS_EDITOR_COMPLETESNIPPET)->key == GDK_space &&
 2663         ! editor_prefs.complete_snippets_whilst_editing && ! at_eol(sci, pos))
 2664         return FALSE;
 2665 
 2666     wc = snippets_find_completion_by_name("Special", "wordchars");
 2667     word = editor_read_word_stem(editor, pos, wc);
 2668 
 2669     /* prevent completion of "for " */
 2670     if (!EMPTY(word) &&
 2671         ! isspace(sci_get_char_at(sci, pos - 1))) /* pos points to the line end char so use pos -1 */
 2672     {
 2673         sci_start_undo_action(sci); /* needed because we insert a space separately from construct */
 2674         result = snippets_complete_constructs(editor, pos, word);
 2675         sci_end_undo_action(sci);
 2676         if (result)
 2677             sci_cancel(sci);    /* cancel any autocompletion list, etc */
 2678     }
 2679     return result;
 2680 }
 2681 
 2682 
 2683 static void insert_closing_tag(GeanyEditor *editor, gint pos, gchar ch, const gchar *tag_name)
 2684 {
 2685     ScintillaObject *sci = editor->sci;
 2686     gchar *to_insert = NULL;
 2687 
 2688     if (ch == '/')
 2689     {
 2690         const gchar *gt = ">";
 2691         /* if there is already a '>' behind the cursor, don't add it */
 2692         if (sci_get_char_at(sci, pos) == '>')
 2693             gt = "";
 2694 
 2695         to_insert = g_strconcat(tag_name, gt, NULL);
 2696     }
 2697     else
 2698         to_insert = g_strconcat("</", tag_name, ">", NULL);
 2699 
 2700     sci_start_undo_action(sci);
 2701     sci_replace_sel(sci, to_insert);
 2702     if (ch == '>')
 2703         sci_set_selection(sci, pos, pos);
 2704     sci_end_undo_action(sci);
 2705     g_free(to_insert);
 2706 }
 2707 
 2708 
 2709 /*
 2710  * (stolen from anjuta and heavily modified)
 2711  * This routine will auto complete XML or HTML tags that are still open by closing them
 2712  * @param ch The character we are dealing with, currently only works with the '>' character
 2713  * @return True if handled, false otherwise
 2714  */
 2715 static gboolean handle_xml(GeanyEditor *editor, gint pos, gchar ch)
 2716 {
 2717     ScintillaObject *sci = editor->sci;
 2718     gint lexer = sci_get_lexer(sci);
 2719     gint min, size, style;
 2720     gchar *str_found, sel[512];
 2721     gboolean result = FALSE;
 2722 
 2723     /* If the user has turned us off, quit now.
 2724      * This may make sense only in certain languages */
 2725     if (! editor_prefs.auto_close_xml_tags || (lexer != SCLEX_HTML && lexer != SCLEX_XML))
 2726         return FALSE;
 2727 
 2728     /* return if we are inside any embedded script */
 2729     style = sci_get_style_at(sci, pos);
 2730     if (style > SCE_H_XCCOMMENT && ! highlighting_is_string_style(lexer, style))
 2731         return FALSE;
 2732 
 2733     /* if ch is /, check for </, else quit */
 2734     if (ch == '/' && sci_get_char_at(sci, pos - 2) != '<')
 2735         return FALSE;
 2736 
 2737     /* Grab the last 512 characters or so */
 2738     min = pos - (sizeof(sel) - 1);
 2739     if (min < 0) min = 0;
 2740 
 2741     if (pos - min < 3)
 2742         return FALSE; /* Smallest tag is 3 characters e.g. <p> */
 2743 
 2744     sci_get_text_range(sci, min, pos, sel);
 2745     sel[sizeof(sel) - 1] = '\0';
 2746 
 2747     if (ch == '>' && sel[pos - min - 2] == '/')
 2748         /* User typed something like "<br/>" */
 2749         return FALSE;
 2750 
 2751     size = pos - min;
 2752     if (ch == '/')
 2753         size -= 2; /* skip </ */
 2754     str_found = utils_find_open_xml_tag(sel, size);
 2755 
 2756     if (lexer == SCLEX_HTML && utils_is_short_html_tag(str_found))
 2757     {
 2758         /* ignore tag */
 2759     }
 2760     else if (!EMPTY(str_found))
 2761     {
 2762         insert_closing_tag(editor, pos, ch, str_found);
 2763         result = TRUE;
 2764     }
 2765     g_free(str_found);
 2766     return result;
 2767 }
 2768 
 2769 
 2770 /* like sci_get_line_indentation(), but for a string. */
 2771 static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent)
 2772 {
 2773     const gchar *ptr;
 2774     gsize tab_size = sci_get_tab_width(editor->sci);
 2775     gsize count = 0;
 2776 
 2777     g_return_val_if_fail(base_indent, 0);
 2778 
 2779     for (ptr = base_indent; *ptr != 0; ptr++)
 2780     {
 2781         switch (*ptr)
 2782         {
 2783             case ' ':
 2784                 count++;
 2785                 break;
 2786             case '\t':
 2787                 count += tab_size;
 2788                 break;
 2789             default:
 2790                 return count;
 2791         }
 2792     }
 2793     return count;
 2794 }
 2795 
 2796 
 2797 /* Handles special cases where HTML is embedded in another language or
 2798  * another language is embedded in HTML */
 2799 static GeanyFiletype *editor_get_filetype_at_line(GeanyEditor *editor, gint line)
 2800 {
 2801     gint style, line_start;
 2802     GeanyFiletype *current_ft;
 2803 
 2804     g_return_val_if_fail(editor != NULL, NULL);
 2805     g_return_val_if_fail(editor->document->file_type != NULL, NULL);
 2806 
 2807     current_ft = editor->document->file_type;
 2808     line_start = sci_get_position_from_line(editor->sci, line);
 2809     style = sci_get_style_at(editor->sci, line_start);
 2810 
 2811     /* Handle PHP filetype with embedded HTML */
 2812     if (current_ft->id == GEANY_FILETYPES_PHP && ! is_style_php(style))
 2813         current_ft = filetypes[GEANY_FILETYPES_HTML];
 2814 
 2815     /* Handle languages embedded in HTML */
 2816     if (current_ft->id == GEANY_FILETYPES_HTML)
 2817     {
 2818         /* Embedded JS */
 2819         if (style >= SCE_HJ_DEFAULT && style <= SCE_HJ_REGEX)
 2820             current_ft = filetypes[GEANY_FILETYPES_JS];
 2821         /* ASP JS */
 2822         else if (style >= SCE_HJA_DEFAULT && style <= SCE_HJA_REGEX)
 2823             current_ft = filetypes[GEANY_FILETYPES_JS];
 2824         /* Embedded VB */
 2825         else if (style >= SCE_HB_DEFAULT && style <= SCE_HB_STRINGEOL)
 2826             current_ft = filetypes[GEANY_FILETYPES_BASIC];
 2827         /* ASP VB */
 2828         else if (style >= SCE_HBA_DEFAULT && style <= SCE_HBA_STRINGEOL)
 2829             current_ft = filetypes[GEANY_FILETYPES_BASIC];
 2830         /* Embedded Python */
 2831         else if (style >= SCE_HP_DEFAULT && style <= SCE_HP_IDENTIFIER)
 2832             current_ft = filetypes[GEANY_FILETYPES_PYTHON];
 2833         /* ASP Python */
 2834         else if (style >= SCE_HPA_DEFAULT && style <= SCE_HPA_IDENTIFIER)
 2835             current_ft = filetypes[GEANY_FILETYPES_PYTHON];
 2836         /* Embedded PHP */
 2837         else if ((style >= SCE_HPHP_DEFAULT && style <= SCE_HPHP_OPERATOR) ||
 2838             style == SCE_HPHP_COMPLEX_VARIABLE)
 2839         {
 2840             current_ft = filetypes[GEANY_FILETYPES_PHP];
 2841         }
 2842     }
 2843 
 2844     /* Ensure the filetype's config is loaded */
 2845     filetypes_load_config(current_ft->id, FALSE);
 2846 
 2847     return current_ft;
 2848 }
 2849 
 2850 
 2851 static void real_comment_multiline(GeanyEditor *editor, gint line_start, gint last_line)
 2852 {
 2853     const gchar *eol;
 2854     gchar *str_begin, *str_end;
 2855     const gchar *co, *cc;
 2856     gint line_len;
 2857     GeanyFiletype *ft;
 2858 
 2859     g_return_if_fail(editor != NULL && editor->document->file_type != NULL);
 2860 
 2861     ft = editor_get_filetype_at_line(editor, line_start);
 2862 
 2863     eol = editor_get_eol_char(editor);
 2864     if (! filetype_get_comment_open_close(ft, FALSE, &co, &cc))
 2865         g_return_if_reached();
 2866     str_begin = g_strdup_printf("%s%s", (co != NULL) ? co : "", eol);
 2867     str_end = g_strdup_printf("%s%s", (cc != NULL) ? cc : "", eol);
 2868 
 2869     /* insert the comment strings */
 2870     sci_insert_text(editor->sci, line_start, str_begin);
 2871     line_len = sci_get_position_from_line(editor->sci, last_line + 2);
 2872     sci_insert_text(editor->sci, line_len, str_end);
 2873 
 2874     g_free(str_begin);
 2875     g_free(str_end);
 2876 }
 2877 
 2878 
 2879 /* find @p text inside the range of the current style */
 2880 static gint find_in_current_style(ScintillaObject *sci, const gchar *text, gboolean backwards)
 2881 {
 2882     gint start = sci_get_current_position(sci);
 2883     gint end = start;
 2884     gint len = sci_get_length(sci);
 2885     gint current_style = sci_get_style_at(sci, start);
 2886     struct Sci_TextToFind ttf;
 2887 
 2888     while (start > 0 && sci_get_style_at(sci, start - 1) == current_style)
 2889         start -= 1;
 2890     while (end < len && sci_get_style_at(sci, end + 1) == current_style)
 2891         end += 1;
 2892 
 2893     ttf.lpstrText = (gchar*) text;
 2894     ttf.chrg.cpMin = backwards ? end + 1 : start;
 2895     ttf.chrg.cpMax = backwards ? start : end + 1;
 2896     return sci_find_text(sci, 0, &ttf);
 2897 }
 2898 
 2899 
 2900 static void sci_delete_line(ScintillaObject *sci, gint line)
 2901 {
 2902     gint start = sci_get_position_from_line(sci, line);
 2903     gint len = sci_get_line_length(sci, line);
 2904     SSM(sci, SCI_DELETERANGE, start, len);
 2905 }
 2906 
 2907 
 2908 static gboolean real_uncomment_multiline(GeanyEditor *editor)
 2909 {
 2910     /* find the beginning of the multi line comment */
 2911     gint start, end, start_line, end_line;
 2912     GeanyFiletype *ft;
 2913     const gchar *co, *cc;
 2914 
 2915     g_return_val_if_fail(editor != NULL && editor->document->file_type != NULL, FALSE);
 2916 
 2917     ft = editor_get_filetype_at_line(editor, sci_get_current_line(editor->sci));
 2918     if (! filetype_get_comment_open_close(ft, FALSE, &co, &cc))
 2919         g_return_val_if_reached(FALSE);
 2920 
 2921     start = find_in_current_style(editor->sci, co, TRUE);
 2922     end = find_in_current_style(editor->sci, cc, FALSE);
 2923 
 2924     if (start < 0 || end < 0 || start > end /* who knows */)
 2925         return FALSE;
 2926 
 2927     start_line = sci_get_line_from_position(editor->sci, start);
 2928     end_line = sci_get_line_from_position(editor->sci, end);
 2929 
 2930     /* remove comment close chars */
 2931     SSM(editor->sci, SCI_DELETERANGE, end, strlen(cc));
 2932     if (sci_is_blank_line(editor->sci, end_line))
 2933         sci_delete_line(editor->sci, end_line);
 2934 
 2935     /* remove comment open chars (do it last since it would move the end position) */
 2936     SSM(editor->sci, SCI_DELETERANGE, start, strlen(co));
 2937     if (sci_is_blank_line(editor->sci, start_line))
 2938         sci_delete_line(editor->sci, start_line);
 2939 
 2940     return TRUE;
 2941 }
 2942 
 2943 
 2944 static gint get_multiline_comment_style(GeanyEditor *editor, gint line_start)
 2945 {
 2946     gint lexer = sci_get_lexer(editor->sci);
 2947     gint style_comment;
 2948 
 2949     /* List only those lexers which support multi line comments */
 2950     switch (lexer)
 2951     {
 2952         case SCLEX_XML:
 2953         case SCLEX_HTML:
 2954         case SCLEX_PHPSCRIPT:
 2955         {
 2956             if (is_style_php(sci_get_style_at(editor->sci, line_start)))
 2957                 style_comment = SCE_HPHP_COMMENT;
 2958             else
 2959                 style_comment = SCE_H_COMMENT;
 2960             break;
 2961         }
 2962         case SCLEX_HASKELL:
 2963         case SCLEX_LITERATEHASKELL:
 2964             style_comment = SCE_HA_COMMENTBLOCK; break;
 2965         case SCLEX_LUA: style_comment = SCE_LUA_COMMENT; break;
 2966         case SCLEX_CSS: style_comment = SCE_CSS_COMMENT; break;
 2967         case SCLEX_SQL: style_comment = SCE_SQL_COMMENT; break;
 2968         case SCLEX_CAML: style_comment = SCE_CAML_COMMENT; break;
 2969         case SCLEX_D: style_comment = SCE_D_COMMENT; break;
 2970         case SCLEX_PASCAL: style_comment = SCE_PAS_COMMENT; break;
 2971         case SCLEX_RUST: style_comment = SCE_RUST_COMMENTBLOCK; break;
 2972         default: style_comment = SCE_C_COMMENT;
 2973     }
 2974 
 2975     return style_comment;
 2976 }
 2977 
 2978 
 2979 /* set toggle to TRUE if the caller is the toggle function, FALSE otherwise
 2980  * returns the amount of uncommented single comment lines, in case of multi line uncomment
 2981  * it returns just 1 */
 2982 gint editor_do_uncomment(GeanyEditor *editor, gint line, gboolean toggle)
 2983 {
 2984     gint first_line, last_line;
 2985     gint x, i, line_start, line_len;
 2986     gint sel_start, sel_end;
 2987     gint count = 0;
 2988     gsize co_len;
 2989     gchar sel[256];
 2990     const gchar *co, *cc;
 2991     gboolean single_line = FALSE;
 2992     GeanyFiletype *ft;
 2993 
 2994     g_return_val_if_fail(editor != NULL && editor->document->file_type != NULL, 0);
 2995 
 2996     if (line < 0)
 2997     {   /* use selection or current line */
 2998         sel_start = sci_get_selection_start(editor->sci);
 2999         sel_end = sci_get_selection_end(editor->sci);
 3000 
 3001         first_line = sci_get_line_from_position(editor->sci, sel_start);
 3002         /* Find the last line with chars selected (not EOL char) */
 3003         last_line = sci_get_line_from_position(editor->sci,
 3004             sel_end - editor_get_eol_char_len(editor));
 3005         last_line = MAX(first_line, last_line);
 3006     }
 3007     else
 3008     {
 3009         first_line = last_line = line;
 3010         sel_start = sel_end = sci_get_position_from_line(editor->sci, line);
 3011     }
 3012 
 3013     ft = editor_get_filetype_at_line(editor, first_line);
 3014 
 3015     if (! filetype_get_comment_open_close(ft, TRUE, &co, &cc))
 3016         return 0;
 3017 
 3018     co_len = strlen(co);
 3019     if (co_len == 0)
 3020         return 0;
 3021 
 3022     sci_start_undo_action(editor->sci);
 3023 
 3024     for (i = first_line; i <= last_line; i++)
 3025     {
 3026         gint buf_len;
 3027 
 3028         line_start = sci_get_position_from_line(editor->sci, i);
 3029         line_len = sci_get_line_end_position(editor->sci, i) - line_start;
 3030         x = 0;
 3031 
 3032         buf_len = MIN((gint)sizeof(sel) - 1, line_len);
 3033         if (buf_len <= 0)
 3034             continue;
 3035         sci_get_text_range(editor->sci, line_start, line_start + buf_len, sel);
 3036         sel[buf_len] = '\0';
 3037 
 3038         while (isspace(sel[x])) x++;
 3039 
 3040         /* to skip blank lines */
 3041         if (x < line_len && sel[x] != '\0')
 3042         {
 3043             /* use single line comment */
 3044             if (EMPTY(cc))
 3045             {
 3046                 single_line = TRUE;
 3047 
 3048                 if (toggle)
 3049                 {
 3050                     gsize tm_len = strlen(editor_prefs.comment_toggle_mark);
 3051                     if (strncmp(sel + x, co, co_len) != 0 ||
 3052                         strncmp(sel + x + co_len, editor_prefs.comment_toggle_mark, tm_len) != 0)
 3053                         continue;
 3054 
 3055                     co_len += tm_len;
 3056                 }
 3057                 else
 3058                 {
 3059                     if (strncmp(sel + x, co, co_len) != 0)
 3060                         continue;
 3061                 }
 3062 
 3063                 sci_set_selection(editor->sci, line_start + x, line_start + x + co_len);
 3064                 sci_replace_sel(editor->sci, "");
 3065                 count++;
 3066             }
 3067             /* use multi line comment */
 3068             else
 3069             {
 3070                 gint style_comment;
 3071 
 3072                 /* skip lines which are already comments */
 3073                 style_comment = get_multiline_comment_style(editor, line_start);
 3074                 if (sci_get_style_at(editor->sci, line_start + x) == style_comment)
 3075                 {
 3076                     if (real_uncomment_multiline(editor))
 3077                         count = 1;
 3078                 }
 3079 
 3080                 /* break because we are already on the last line */
 3081                 break;
 3082             }
 3083         }
 3084     }
 3085     sci_end_undo_action(editor->sci);
 3086 
 3087     /* restore selection if there is one
 3088      * but don't touch the selection if caller is editor_do_comment_toggle */
 3089     if (! toggle && sel_start < sel_end)
 3090     {
 3091         if (single_line)
 3092         {
 3093             sci_set_selection_start(editor->sci, sel_start - co_len);
 3094             sci_set_selection_end(editor->sci, sel_end - (count * co_len));
 3095         }
 3096         else
 3097         {
 3098             gint eol_len = editor_get_eol_char_len(editor);
 3099             sci_set_selection_start(editor->sci, sel_start - co_len - eol_len);
 3100             sci_set_selection_end(editor->sci, sel_end - co_len - eol_len);
 3101         }
 3102     }
 3103 
 3104     return count;
 3105 }
 3106 
 3107 
 3108 void editor_do_comment_toggle(GeanyEditor *editor)
 3109 {
 3110     gint first_line, last_line;
 3111     gint x, i, line_start, line_len, first_line_start, last_line_start;
 3112     gint sel_start, sel_end;
 3113     gint count_commented = 0, count_uncommented = 0;
 3114     gchar sel[256];
 3115     const gchar *co, *cc;
 3116     gboolean single_line = FALSE;
 3117     gboolean first_line_was_comment = FALSE;
 3118     gboolean last_line_was_comment = FALSE;
 3119     gsize co_len;
 3120     gsize tm_len = strlen(editor_prefs.comment_toggle_mark);
 3121     GeanyFiletype *ft;
 3122 
 3123     g_return_if_fail(editor != NULL && editor->document->file_type != NULL);
 3124 
 3125     sel_start = sci_get_selection_start(editor->sci);
 3126     sel_end = sci_get_selection_end(editor->sci);
 3127 
 3128     first_line = sci_get_line_from_position(editor->sci, sel_start);
 3129     /* Find the last line with chars selected (not EOL char) */
 3130     last_line = sci_get_line_from_position(editor->sci,
 3131         sel_end - editor_get_eol_char_len(editor));
 3132     last_line = MAX(first_line, last_line);
 3133 
 3134     first_line_start = sci_get_position_from_line(editor->sci, first_line);
 3135     last_line_start = sci_get_position_from_line(editor->sci, last_line);
 3136 
 3137     ft = editor_get_filetype_at_line(editor, first_line);
 3138 
 3139     if (! filetype_get_comment_open_close(ft, TRUE, &co, &cc))
 3140         return;
 3141 
 3142     co_len = strlen(co);
 3143     if (co_len == 0)
 3144         return;
 3145 
 3146     sci_start_undo_action(editor->sci);
 3147 
 3148     for (i = first_line; i <= last_line; i++)
 3149     {
 3150         gint buf_len;
 3151 
 3152         line_start = sci_get_position_from_line(editor->sci, i);
 3153         line_len = sci_get_line_end_position(editor->sci, i) - line_start;
 3154         x = 0;
 3155 
 3156         buf_len = MIN((gint)sizeof(sel) - 1, line_len);
 3157         if (buf_len < 0)
 3158             continue;
 3159         sci_get_text_range(editor->sci, line_start, line_start + buf_len, sel);
 3160         sel[buf_len] = '\0';
 3161 
 3162         while (isspace(sel[x])) x++;
 3163 
 3164         /* use single line comment */
 3165         if (EMPTY(cc))
 3166         {
 3167             gboolean do_continue = FALSE;
 3168             single_line = TRUE;
 3169 
 3170             if (strncmp(sel + x, co, co_len) == 0 &&
 3171                 strncmp(sel + x + co_len, editor_prefs.comment_toggle_mark, tm_len) == 0)
 3172             {
 3173                 do_continue = TRUE;
 3174             }
 3175 
 3176             if (do_continue && i == first_line)
 3177                 first_line_was_comment = TRUE;
 3178             last_line_was_comment = do_continue;
 3179 
 3180             if (do_continue)
 3181             {
 3182                 count_uncommented += editor_do_uncomment(editor, i, TRUE);
 3183                 continue;
 3184             }
 3185 
 3186             /* we are still here, so the above lines were not already comments, so comment it */
 3187             count_commented += editor_do_comment(editor, i, FALSE, TRUE, TRUE);
 3188         }
 3189         /* use multi line comment */
 3190         else
 3191         {
 3192             gint style_comment;
 3193 
 3194             /* skip lines which are already comments */
 3195             style_comment = get_multiline_comment_style(editor, line_start);
 3196             if (sci_get_style_at(editor->sci, line_start + x) == style_comment)
 3197             {
 3198                 if (real_uncomment_multiline(editor))
 3199                     count_uncommented++;
 3200             }
 3201             else
 3202             {
 3203                 real_comment_multiline(editor, line_start, last_line);
 3204                 count_commented++;
 3205             }
 3206 
 3207             /* break because we are already on the last line */
 3208             break;
 3209         }
 3210     }
 3211 
 3212     sci_end_undo_action(editor->sci);
 3213 
 3214     co_len += tm_len;
 3215 
 3216     /* restore selection or caret position */
 3217     if (single_line)
 3218     {
 3219         gint a = (first_line_was_comment) ? - (gint) co_len : (gint) co_len;
 3220         gint indent_len;
 3221 
 3222         /* don't modify sel_start when the selection starts within indentation */
 3223         read_indent(editor, sel_start);
 3224         indent_len = (gint) strlen(indent);
 3225         if ((sel_start - first_line_start) <= indent_len)
 3226             a = 0;
 3227         /* if the selection start was inside the comment mark, adjust the position */
 3228         else if (first_line_was_comment &&
 3229                  sel_start >= (first_line_start + indent_len) &&
 3230                  sel_start <= (first_line_start + indent_len + (gint) co_len))
 3231         {
 3232             a = (first_line_start + indent_len) - sel_start;
 3233         }
 3234 
 3235         if (sel_start < sel_end)
 3236         {
 3237             gint b = (count_commented * (gint) co_len) - (count_uncommented * (gint) co_len);
 3238 
 3239             /* same for selection end, but here we add an offset on the offset above */
 3240             read_indent(editor, sel_end + b);
 3241             indent_len = (gint) strlen(indent);
 3242             if ((sel_end - last_line_start) < indent_len)
 3243                 b += last_line_was_comment ? (gint) co_len : -(gint) co_len;
 3244             else if (last_line_was_comment &&
 3245                      sel_end >= (last_line_start + indent_len) &&
 3246                      sel_end <= (last_line_start + indent_len + (gint) co_len))
 3247             {
 3248                 b += (gint) co_len - (sel_end - (last_line_start + indent_len));
 3249             }
 3250 
 3251             sci_set_selection_start(editor->sci, sel_start + a);
 3252             sci_set_selection_end(editor->sci, sel_end + b);
 3253         }
 3254         else
 3255             sci_set_current_position(editor->sci, sel_start + a, TRUE);
 3256     }
 3257     else
 3258     {
 3259         gint eol_len = editor_get_eol_char_len(editor);
 3260         if (count_uncommented > 0)
 3261         {
 3262             sci_set_selection_start(editor->sci, sel_start - (gint) co_len + eol_len);
 3263             sci_set_selection_end(editor->sci, sel_end - (gint) co_len + eol_len);
 3264         }
 3265         else if (count_commented > 0)
 3266         {
 3267             sci_set_selection_start(editor->sci, sel_start + (gint) co_len - eol_len);
 3268             sci_set_selection_end(editor->sci, sel_end + (gint) co_len - eol_len);
 3269         }
 3270         if (sel_start >= sel_end)
 3271             sci_scroll_caret(editor->sci);
 3272     }
 3273 }
 3274 
 3275 
 3276 /* set toggle to TRUE if the caller is the toggle function, FALSE otherwise */
 3277 gint editor_do_comment(GeanyEditor *editor, gint line, gboolean allow_empty_lines, gboolean toggle,
 3278         gboolean single_comment)
 3279 {
 3280     gint first_line, last_line;
 3281     gint x, i, line_start, line_len;
 3282     gint sel_start, sel_end, co_len;
 3283     gint count = 0;
 3284     gchar sel[256];
 3285     const gchar *co, *cc;
 3286     gboolean single_line = FALSE;
 3287     GeanyFiletype *ft;
 3288 
 3289     g_return_val_if_fail(editor != NULL && editor->document->file_type != NULL, 0);
 3290 
 3291     if (line < 0)
 3292     {   /* use selection or current line */
 3293         sel_start = sci_get_selection_start(editor->sci);
 3294         sel_end = sci_get_selection_end(editor->sci);
 3295 
 3296         first_line = sci_get_line_from_position(editor->sci, sel_start);
 3297         /* Find the last line with chars selected (not EOL char) */
 3298         last_line = sci_get_line_from_position(editor->sci,
 3299             sel_end - editor_get_eol_char_len(editor));
 3300         last_line = MAX(first_line, last_line);
 3301     }
 3302     else
 3303     {
 3304         first_line = last_line = line;
 3305         sel_start = sel_end = sci_get_position_from_line(editor->sci, line);
 3306     }
 3307 
 3308     ft = editor_get_filetype_at_line(editor, first_line);
 3309 
 3310     if (! filetype_get_comment_open_close(ft, single_comment, &co, &cc))
 3311         return 0;
 3312 
 3313     co_len = strlen(co);
 3314     if (co_len == 0)
 3315         return 0;
 3316 
 3317     sci_start_undo_action(editor->sci);
 3318 
 3319     for (i = first_line; i <= last_line; i++)
 3320     {
 3321         gint buf_len;
 3322 
 3323         line_start = sci_get_position_from_line(editor->sci, i);
 3324         line_len = sci_get_line_end_position(editor->sci, i) - line_start;
 3325         x = 0;
 3326 
 3327         buf_len = MIN((gint)sizeof(sel) - 1, line_len);
 3328         if (buf_len < 0)
 3329             continue;
 3330         sci_get_text_range(editor->sci, line_start, line_start + buf_len, sel);
 3331         sel[buf_len] = '\0';
 3332 
 3333         while (isspace(sel[x])) x++;
 3334 
 3335         /* to skip blank lines */
 3336         if (allow_empty_lines || (x < line_len && sel[x] != '\0'))
 3337         {
 3338             /* use single line comment */
 3339             if (EMPTY(cc))
 3340             {
 3341                 gint start = line_start;
 3342                 single_line = TRUE;
 3343 
 3344                 if (ft->comment_use_indent)
 3345                     start = line_start + x;
 3346 
 3347                 if (toggle)
 3348                 {
 3349                     gchar *text = g_strconcat(co, editor_prefs.comment_toggle_mark, NULL);
 3350                     sci_insert_text(editor->sci, start, text);
 3351                     g_free(text);
 3352                 }
 3353                 else
 3354                     sci_insert_text(editor->sci, start, co);
 3355                 count++;
 3356             }
 3357             /* use multi line comment */
 3358             else
 3359             {
 3360                 gint style_comment;
 3361 
 3362                 /* skip lines which are already comments */
 3363                 style_comment = get_multiline_comment_style(editor, line_start);
 3364                 if (sci_get_style_at(editor->sci, line_start + x) == style_comment)
 3365                     continue;
 3366 
 3367                 real_comment_multiline(editor, line_start, last_line);
 3368                 count = 1;
 3369 
 3370                 /* break because we are already on the last line */
 3371                 break;
 3372             }
 3373         }
 3374     }
 3375     sci_end_undo_action(editor->sci);
 3376 
 3377     /* restore selection if there is one
 3378      * but don't touch the selection if caller is editor_do_comment_toggle */
 3379     if (! toggle && sel_start < sel_end)
 3380     {
 3381         if (single_line)
 3382         {
 3383             sci_set_selection_start(editor->sci, sel_start + co_len);
 3384             sci_set_selection_end(editor->sci, sel_end + (count * co_len));
 3385         }
 3386         else
 3387         {
 3388             gint eol_len = editor_get_eol_char_len(editor);
 3389             sci_set_selection_start(editor->sci, sel_start + co_len + eol_len);
 3390             sci_set_selection_end(editor->sci, sel_end + co_len + eol_len);
 3391         }
 3392     }
 3393     return count;
 3394 }
 3395 
 3396 
 3397 static gboolean brace_timeout_active = FALSE;
 3398 
 3399 static gboolean delay_match_brace(G_GNUC_UNUSED gpointer user_data)
 3400 {
 3401     GeanyDocument *doc = document_get_current();
 3402     GeanyEditor *editor;
 3403     gint brace_pos = GPOINTER_TO_INT(user_data);
 3404     gint end_pos, cur_pos;
 3405 
 3406     brace_timeout_active = FALSE;
 3407     if (!doc)
 3408         return FALSE;
 3409 
 3410     editor = doc->editor;
 3411     cur_pos = sci_get_current_position(editor->sci) - 1;
 3412 
 3413     if (cur_pos != brace_pos)
 3414     {
 3415         cur_pos++;
 3416         if (cur_pos != brace_pos)
 3417         {
 3418             /* we have moved past the original brace_pos, but after the timeout
 3419              * we may now be on a new brace, so check again */
 3420             editor_highlight_braces(editor, cur_pos);
 3421             return FALSE;
 3422         }
 3423     }
 3424     if (!utils_isbrace(sci_get_char_at(editor->sci, brace_pos), editor_prefs.brace_match_ltgt))
 3425     {
 3426         editor_highlight_braces(editor, cur_pos);
 3427         return FALSE;
 3428     }
 3429     end_pos = sci_find_matching_brace(editor->sci, brace_pos);
 3430 
 3431     if (end_pos >= 0)
 3432     {
 3433         gint col = MIN(sci_get_col_from_position(editor->sci, brace_pos),
 3434             sci_get_col_from_position(editor->sci, end_pos));
 3435         SSM(editor->sci, SCI_SETHIGHLIGHTGUIDE, col, 0);
 3436         SSM(editor->sci, SCI_BRACEHIGHLIGHT, brace_pos, end_pos);
 3437     }
 3438     else
 3439     {
 3440         SSM(editor->sci, SCI_SETHIGHLIGHTGUIDE, 0, 0);
 3441         SSM(editor->sci, SCI_BRACEBADLIGHT, brace_pos, 0);
 3442     }
 3443     return FALSE;
 3444 }
 3445 
 3446 
 3447 static void editor_highlight_braces(GeanyEditor *editor, gint cur_pos)
 3448 {
 3449     gint brace_pos = cur_pos - 1;
 3450 
 3451     SSM(editor->sci, SCI_SETHIGHLIGHTGUIDE, 0, 0);
 3452     SSM(editor->sci, SCI_BRACEBADLIGHT, (uptr_t)-1, 0);
 3453 
 3454     if (! utils_isbrace(sci_get_char_at(editor->sci, brace_pos), editor_prefs.brace_match_ltgt))
 3455     {
 3456         brace_pos++;
 3457         if (! utils_isbrace(sci_get_char_at(editor->sci, brace_pos), editor_prefs.brace_match_ltgt))
 3458         {
 3459             return;
 3460         }
 3461     }
 3462     if (!brace_timeout_active)
 3463     {
 3464         brace_timeout_active = TRUE;
 3465         /* delaying matching makes scrolling faster e.g. holding down arrow keys */
 3466         g_timeout_add(100, delay_match_brace, GINT_TO_POINTER(brace_pos));
 3467     }
 3468 }
 3469 
 3470 
 3471 static gboolean in_block_comment(gint lexer, gint style)
 3472 {
 3473     switch (lexer)
 3474     {
 3475         case SCLEX_COBOL:
 3476         case SCLEX_CPP:
 3477             return (style == SCE_C_COMMENT ||
 3478                 style == SCE_C_COMMENTDOC);
 3479 
 3480         case SCLEX_PASCAL:
 3481             return (style == SCE_PAS_COMMENT ||
 3482                 style == SCE_PAS_COMMENT2);
 3483 
 3484         case SCLEX_D:
 3485             return (style == SCE_D_COMMENT ||
 3486                 style == SCE_D_COMMENTDOC ||
 3487                 style == SCE_D_COMMENTNESTED);
 3488 
 3489         case SCLEX_HTML:
 3490         case SCLEX_PHPSCRIPT:
 3491             return (style == SCE_HPHP_COMMENT);
 3492 
 3493         case SCLEX_CSS:
 3494             return (style == SCE_CSS_COMMENT);
 3495 
 3496         case SCLEX_RUST:
 3497             return (style == SCE_RUST_COMMENTBLOCK ||
 3498                 style == SCE_RUST_COMMENTBLOCKDOC);
 3499 
 3500         default:
 3501             return FALSE;
 3502     }
 3503 }
 3504 
 3505 
 3506 static gboolean is_comment_char(gchar c, gint lexer)
 3507 {
 3508     if ((c == '*' || c == '+') && lexer == SCLEX_D)
 3509         return TRUE;
 3510     else
 3511     if (c == '*')
 3512         return TRUE;
 3513 
 3514     return FALSE;
 3515 }
 3516 
 3517 
 3518 static void auto_multiline(GeanyEditor *editor, gint cur_line)
 3519 {
 3520     ScintillaObject *sci = editor->sci;
 3521     gint indent_pos, style;
 3522     gint lexer = sci_get_lexer(sci);
 3523 
 3524     /* Use the start of the line enter was pressed on, to avoid any doc keyword styles */
 3525     indent_pos = sci_get_line_indent_position(sci, cur_line - 1);
 3526     style = sci_get_style_at(sci, indent_pos);
 3527     if (!in_block_comment(lexer, style))
 3528         return;
 3529 
 3530     /* Check whether the comment block continues on this line */
 3531     indent_pos = sci_get_line_indent_position(sci, cur_line);
 3532     if (sci_get_style_at(sci, indent_pos) == style || indent_pos >= sci_get_length(sci))
 3533     {
 3534         gchar *previous_line = sci_get_line(sci, cur_line - 1);
 3535         /* the type of comment, '*' (C/C++/Java), '+' and the others (D) */
 3536         const gchar *continuation = "*";
 3537         const gchar *whitespace = ""; /* to hold whitespace if needed */
 3538         gchar *result;
 3539         gint len = strlen(previous_line);
 3540         gint i;
 3541 
 3542         /* find and stop at end of multi line comment */
 3543         i = len - 1;
 3544         while (i >= 0 && isspace(previous_line[i])) i--;
 3545         if (i >= 1 && is_comment_char(previous_line[i - 1], lexer) && previous_line[i] == '/')
 3546         {
 3547             gint indent_len, indent_width;
 3548 
 3549             indent_pos = sci_get_line_indent_position(sci, cur_line);
 3550             indent_len = sci_get_col_from_position(sci, indent_pos);
 3551             indent_width = editor_get_indent_prefs(editor)->width;
 3552 
 3553             /* if there is one too many spaces, delete the last space,
 3554              * to return to the indent used before the multiline comment was started. */
 3555             if (indent_len % indent_width == 1)
 3556                 SSM(sci, SCI_DELETEBACKNOTLINE, 0, 0);  /* remove whitespace indent */
 3557             g_free(previous_line);
 3558             return;
 3559         }
 3560         /* check whether we are on the second line of multi line comment */
 3561         i = 0;
 3562         while (i < len && isspace(previous_line[i])) i++; /* get to start of the line */
 3563 
 3564         if (i + 1 < len &&
 3565             previous_line[i] == '/' && is_comment_char(previous_line[i + 1], lexer))
 3566         { /* we are on the second line of a multi line comment, so we have to insert white space */
 3567             whitespace = " ";
 3568         }
 3569 
 3570         if (style == SCE_D_COMMENTNESTED)
 3571             continuation = "+"; /* for nested comments in D */
 3572 
 3573         result = g_strconcat(whitespace, continuation, " ", NULL);
 3574         sci_add_text(sci, result);
 3575         g_free(result);
 3576 
 3577         g_free(previous_line);
 3578     }
 3579 }
 3580 
 3581 
 3582 #if 0
 3583 static gboolean editor_lexer_is_c_like(gint lexer)
 3584 {
 3585     switch (lexer)
 3586     {
 3587         case SCLEX_CPP:
 3588         case SCLEX_D:
 3589         return TRUE;
 3590 
 3591         default:
 3592         return FALSE;
 3593     }
 3594 }
 3595 #endif
 3596 
 3597 
 3598 /* inserts a three-line comment at one line above current cursor position */
 3599 void editor_insert_multiline_comment(GeanyEditor *editor)
 3600 {
 3601     gchar *text;
 3602     gint text_len;
 3603     gint line;
 3604     gint pos;
 3605     gboolean have_multiline_comment = FALSE;
 3606     GeanyDocument *doc;
 3607     const gchar *co, *cc;
 3608 
 3609     g_return_if_fail(editor != NULL && editor->document->file_type != NULL);
 3610 
 3611     if (! filetype_get_comment_open_close(editor->document->file_type, FALSE, &co, &cc))
 3612         g_return_if_reached();
 3613     if (!EMPTY(cc))
 3614         have_multiline_comment = TRUE;
 3615 
 3616     sci_start_undo_action(editor->sci);
 3617 
 3618     doc = editor->document;
 3619 
 3620     /* insert three lines one line above of the current position */
 3621     line = sci_get_line_from_position(editor->sci, editor_info.click_pos);
 3622     pos = sci_get_position_from_line(editor->sci, line);
 3623 
 3624     /* use the indent on the current line but only when comment indentation is used
 3625      * and we don't have multi line comment characters */
 3626     if (editor->auto_indent &&
 3627         ! have_multiline_comment && doc->file_type->comment_use_indent)
 3628     {
 3629         read_indent(editor, editor_info.click_pos);
 3630         text = g_strdup_printf("%s\n%s\n%s\n", indent, indent, indent);
 3631         text_len = strlen(text);
 3632     }
 3633     else
 3634     {
 3635         text = g_strdup("\n\n\n");
 3636         text_len = 3;
 3637     }
 3638     sci_insert_text(editor->sci, pos, text);
 3639     g_free(text);
 3640 
 3641     /* select the inserted lines for commenting */
 3642     sci_set_selection_start(editor->sci, pos);
 3643     sci_set_selection_end(editor->sci, pos + text_len);
 3644 
 3645     editor_do_comment(editor, -1, TRUE, FALSE, FALSE);
 3646 
 3647     /* set the current position to the start of the first inserted line */
 3648     pos += strlen(co);
 3649 
 3650     /* on multi line comment jump to the next line, otherwise add the length of added indentation */
 3651     if (have_multiline_comment)
 3652         pos += 1;
 3653     else
 3654         pos += strlen(indent);
 3655 
 3656     sci_set_current_position(editor->sci, pos, TRUE);
 3657     /* reset the selection */
 3658     sci_set_anchor(editor->sci, pos);
 3659 
 3660     sci_end_undo_action(editor->sci);
 3661 }
 3662 
 3663 
 3664 /* Note: If the editor is pending a redraw, set document::scroll_percent instead.
 3665  * Scroll the view to make line appear at percent_of_view.
 3666  * line can be -1 to use the current position. */
 3667 void editor_scroll_to_line(GeanyEditor *editor, gint line, gfloat percent_of_view)
 3668 {
 3669     gint los;
 3670     GtkWidget *wid;
 3671 
 3672     g_return_if_fail(editor != NULL);
 3673 
 3674     wid = GTK_WIDGET(editor->sci);
 3675 
 3676     if (! gtk_widget_get_window(wid) || ! gdk_window_is_viewable(gtk_widget_get_window(wid)))
 3677         return; /* prevent gdk_window_scroll warning */
 3678 
 3679     if (line == -1)
 3680         line = sci_get_current_line(editor->sci);
 3681 
 3682     /* sci 'visible line' != doc line number because of folding and line wrapping */
 3683     /* calling SCI_VISIBLEFROMDOCLINE for line is more accurate than calling
 3684      * SCI_DOCLINEFROMVISIBLE for vis1. */
 3685     line = SSM(editor->sci, SCI_VISIBLEFROMDOCLINE, line, 0);
 3686     los = SSM(editor->sci, SCI_LINESONSCREEN, 0, 0);
 3687     line = line - los * percent_of_view;
 3688     SSM(editor->sci, SCI_SETFIRSTVISIBLELINE, line, 0);
 3689     sci_scroll_caret(editor->sci); /* needed for horizontal scrolling */
 3690 }
 3691 
 3692 
 3693 /* creates and inserts one tab or whitespace of the amount of the tab width */
 3694 void editor_insert_alternative_whitespace(GeanyEditor *editor)
 3695 {
 3696     gchar *text;
 3697     GeanyIndentPrefs iprefs = *editor_get_indent_prefs(editor);
 3698 
 3699     g_return_if_fail(editor != NULL);
 3700 
 3701     switch (iprefs.type)
 3702     {
 3703         case GEANY_INDENT_TYPE_TABS:
 3704             iprefs.type = GEANY_INDENT_TYPE_SPACES;
 3705             break;
 3706         case GEANY_INDENT_TYPE_SPACES:
 3707         case GEANY_INDENT_TYPE_BOTH:    /* most likely we want a tab */
 3708             iprefs.type = GEANY_INDENT_TYPE_TABS;
 3709             break;
 3710     }
 3711     text = get_whitespace(&iprefs, iprefs.width);
 3712     sci_add_text(editor->sci, text);
 3713     g_free(text);
 3714 }
 3715 
 3716 
 3717 void editor_select_word(GeanyEditor *editor)
 3718 {
 3719     gint pos;
 3720     gint start;
 3721     gint end;
 3722 
 3723     g_return_if_fail(editor != NULL);
 3724 
 3725     pos = SSM(editor->sci, SCI_GETCURRENTPOS, 0, 0);
 3726     start = sci_word_start_position(editor->sci, pos, TRUE);
 3727     end = sci_word_end_position(editor->sci, pos, TRUE);
 3728 
 3729     if (start == end) /* caret in whitespaces sequence */
 3730     {
 3731         /* look forward but reverse the selection direction,
 3732          * so the caret end up stay as near as the original position. */
 3733         end = sci_word_end_position(editor->sci, pos, FALSE);
 3734         start = sci_word_end_position(editor->sci, end, TRUE);
 3735         if (start == end)
 3736             return;
 3737     }
 3738 
 3739     sci_set_selection(editor->sci, start, end);
 3740 }
 3741 
 3742 
 3743 /* extra_line is for selecting the cursor line (or anchor line) at the bottom of a selection,
 3744  * when those lines have no selection (cursor at start of line). */
 3745 void editor_select_lines(GeanyEditor *editor, gboolean extra_line)
 3746 {
 3747     gint start, end, line;
 3748 
 3749     g_return_if_fail(editor != NULL);
 3750 
 3751     start = sci_get_selection_start(editor->sci);
 3752     end = sci_get_selection_end(editor->sci);
 3753 
 3754     /* check if whole lines are already selected */
 3755     if (! extra_line && start != end &&
 3756         sci_get_col_from_position(editor->sci, start) == 0 &&
 3757         sci_get_col_from_position(editor->sci, end) == 0)
 3758             return;
 3759 
 3760     line = sci_get_line_from_position(editor->sci, start);
 3761     start = sci_get_position_from_line(editor->sci, line);
 3762 
 3763     line = sci_get_line_from_position(editor->sci, end);
 3764     end = sci_get_position_from_line(editor->sci, line + 1);
 3765 
 3766     sci_set_selection(editor->sci, start, end);
 3767 }
 3768 
 3769 
 3770 static gboolean sci_is_blank_line(ScintillaObject *sci, gint line)
 3771 {
 3772     return sci_get_line_indent_position(sci, line) ==
 3773         sci_get_line_end_position(sci, line);
 3774 }
 3775 
 3776 
 3777 /* Returns first line of paragraph for GTK_DIR_UP, line after paragraph
 3778  * ends for GTK_DIR_DOWN or -1 if called on an empty line. */
 3779 static gint find_paragraph_stop(GeanyEditor *editor, gint line, gint direction)
 3780 {
 3781     gint step;
 3782     ScintillaObject *sci = editor->sci;
 3783 
 3784     /* first check current line and return -1 if it is empty to skip creating of a selection */
 3785     if (sci_is_blank_line(sci, line))
 3786         return -1;
 3787 
 3788     if (direction == GTK_DIR_UP)
 3789         step = -1;
 3790     else
 3791         step = 1;
 3792 
 3793     while (TRUE)
 3794     {
 3795         line += step;
 3796         if (line == -1)
 3797         {
 3798             /* start of document */
 3799             line = 0;
 3800             break;
 3801         }
 3802         if (line == sci_get_line_count(sci))
 3803             break;
 3804 
 3805         if (sci_is_blank_line(sci, line))
 3806         {
 3807             /* return line paragraph starts on */
 3808             if (direction == GTK_DIR_UP)
 3809                 line++;
 3810             break;
 3811         }
 3812     }
 3813     return line;
 3814 }
 3815 
 3816 
 3817 void editor_select_paragraph(GeanyEditor *editor)
 3818 {
 3819     gint pos_start, pos_end, line_start, line_found;
 3820 
 3821     g_return_if_fail(editor != NULL);
 3822 
 3823     line_start = sci_get_current_line(editor->sci);
 3824 
 3825     line_found = find_paragraph_stop(editor, line_start, GTK_DIR_UP);
 3826     if (line_found == -1)
 3827         return;
 3828 
 3829     pos_start = SSM(editor->sci, SCI_POSITIONFROMLINE, line_found, 0);
 3830 
 3831     line_found = find_paragraph_stop(editor, line_start, GTK_DIR_DOWN);
 3832     pos_end = SSM(editor->sci, SCI_POSITIONFROMLINE, line_found, 0);
 3833 
 3834     sci_set_selection(editor->sci, pos_start, pos_end);
 3835 }
 3836 
 3837 
 3838 /* Returns first line of block for GTK_DIR_UP, line after block
 3839  * ends for GTK_DIR_DOWN or -1 if called on an empty line. */
 3840 static gint find_block_stop(GeanyEditor *editor, gint line, gint direction)
 3841 {
 3842     gint step, ind;
 3843     ScintillaObject *sci = editor->sci;
 3844 
 3845     /* first check current line and return -1 if it is empty to skip creating of a selection */
 3846     if (sci_is_blank_line(sci, line))
 3847         return -1;
 3848 
 3849     if (direction == GTK_DIR_UP)
 3850         step = -1;
 3851     else
 3852         step = 1;
 3853 
 3854     ind = sci_get_line_indentation(sci, line);
 3855     while (TRUE)
 3856     {
 3857         line += step;
 3858         if (line == -1)
 3859         {
 3860             /* start of document */
 3861             line = 0;
 3862             break;
 3863         }
 3864         if (line == sci_get_line_count(sci))
 3865             break;
 3866 
 3867         if (sci_get_line_indentation(sci, line) != ind ||
 3868             sci_is_blank_line(sci, line))
 3869         {
 3870             /* return line block starts on */
 3871             if (direction == GTK_DIR_UP)
 3872                 line++;
 3873             break;
 3874         }
 3875     }
 3876     return line;
 3877 }
 3878 
 3879 
 3880 void editor_select_indent_block(GeanyEditor *editor)
 3881 {
 3882     gint pos_start, pos_end, line_start, line_found;
 3883 
 3884     g_return_if_fail(editor != NULL);
 3885 
 3886     line_start = sci_get_current_line(editor->sci);
 3887 
 3888     line_found = find_block_stop(editor, line_start, GTK_DIR_UP);
 3889     if (line_found == -1)
 3890         return;
 3891 
 3892     pos_start = SSM(editor->sci, SCI_POSITIONFROMLINE, line_found, 0);
 3893 
 3894     line_found = find_block_stop(editor, line_start, GTK_DIR_DOWN);
 3895     pos_end = SSM(editor->sci, SCI_POSITIONFROMLINE, line_found, 0);
 3896 
 3897     sci_set_selection(editor->sci, pos_start, pos_end);
 3898 }
 3899 
 3900 
 3901 /* simple indentation to indent the current line with the same indent as the previous one */
 3902 static void smart_line_indentation(GeanyEditor *editor, gint first_line, gint last_line)
 3903 {
 3904     gint i, sel_start = 0, sel_end = 0;
 3905 
 3906     /* get previous line and use it for read_indent to use that line
 3907      * (otherwise it would fail on a line only containing "{" in advanced indentation mode) */
 3908     read_indent(editor, sci_get_position_from_line(editor->sci, first_line - 1));
 3909 
 3910     for (i = first_line; i <= last_line; i++)
 3911     {
 3912         /* skip the first line or if the indentation of the previous and current line are equal */
 3913         if (i == 0 ||
 3914             SSM(editor->sci, SCI_GETLINEINDENTATION, i - 1, 0) ==
 3915             SSM(editor->sci, SCI_GETLINEINDENTATION, i, 0))
 3916             continue;
 3917 
 3918         sel_start = SSM(editor->sci, SCI_POSITIONFROMLINE, i, 0);
 3919         sel_end = SSM(editor->sci, SCI_GETLINEINDENTPOSITION, i, 0);
 3920         if (sel_start < sel_end)
 3921         {
 3922             sci_set_selection(editor->sci, sel_start, sel_end);
 3923             sci_replace_sel(editor->sci, "");
 3924         }
 3925         sci_insert_text(editor->sci, sel_start, indent);
 3926     }
 3927 }
 3928 
 3929 
 3930 /* simple indentation to indent the current line with the same indent as the previous one */
 3931 void editor_smart_line_indentation(GeanyEditor *editor)
 3932 {
 3933     gint first_line, last_line;
 3934     gint first_sel_start, first_sel_end;
 3935     ScintillaObject *sci;
 3936 
 3937     g_return_if_fail(editor != NULL);
 3938 
 3939     sci = editor->sci;
 3940 
 3941     first_sel_start = sci_get_selection_start(sci);
 3942     first_sel_end = sci_get_selection_end(sci);
 3943 
 3944     first_line = sci_get_line_from_position(sci, first_sel_start);
 3945     /* Find the last line with chars selected (not EOL char) */
 3946     last_line = sci_get_line_from_position(sci, first_sel_end - editor_get_eol_char_len(editor));
 3947     last_line = MAX(first_line, last_line);
 3948 
 3949     sci_start_undo_action(sci);
 3950 
 3951     smart_line_indentation(editor, first_line, last_line);
 3952 
 3953     /* set cursor position if there was no selection */
 3954     if (first_sel_start == first_sel_end)
 3955     {
 3956         gint indent_pos = SSM(sci, SCI_GETLINEINDENTPOSITION, first_line, 0);
 3957 
 3958         /* use indent position as user may wish to change indentation afterwards */
 3959         sci_set_current_position(sci, indent_pos, FALSE);
 3960     }
 3961     else
 3962     {
 3963         /* fully select all the lines affected */
 3964         sci_set_selection_start(sci, sci_get_position_from_line(sci, first_line));
 3965         sci_set_selection_end(sci, sci_get_position_from_line(sci, last_line + 1));
 3966     }
 3967 
 3968     sci_end_undo_action(sci);
 3969 }
 3970 
 3971 
 3972 /* increase / decrease current line or selection by one space */
 3973 void editor_indentation_by_one_space(GeanyEditor *editor, gint pos, gboolean decrease)
 3974 {
 3975     gint i, first_line, last_line, line_start, indentation_end, count = 0;
 3976     gint sel_start, sel_end, first_line_offset = 0;
 3977 
 3978     g_return_if_fail(editor != NULL);
 3979 
 3980     sel_start = sci_get_selection_start(editor->sci);
 3981     sel_end = sci_get_selection_end(editor->sci);
 3982 
 3983     first_line = sci_get_line_from_position(editor->sci, sel_start);
 3984     /* Find the last line with chars selected (not EOL char) */
 3985     last_line = sci_get_line_from_position(editor->sci, sel_end - editor_get_eol_char_len(editor));
 3986     last_line = MAX(first_line, last_line);
 3987 
 3988     if (pos == -1)
 3989         pos = sel_start;
 3990 
 3991     sci_start_undo_action(editor->sci);
 3992 
 3993     for (i = first_line; i <= last_line; i++)
 3994     {
 3995         indentation_end = SSM(editor->sci, SCI_GETLINEINDENTPOSITION, i, 0);
 3996         if (decrease)
 3997         {
 3998             line_start = SSM(editor->sci, SCI_POSITIONFROMLINE, i, 0);
 3999             /* searching backwards for a space to remove */
 4000             while (sci_get_char_at(editor->sci, indentation_end) != ' ' && indentation_end > line_start)
 4001                 indentation_end--;
 4002 
 4003             if (sci_get_char_at(editor->sci, indentation_end) == ' ')
 4004             {
 4005                 sci_set_selection(editor->sci, indentation_end, indentation_end + 1);
 4006                 sci_replace_sel(editor->sci, "");
 4007                 count--;
 4008                 if (i == first_line)
 4009                     first_line_offset = -1;
 4010             }
 4011         }
 4012         else
 4013         {
 4014             sci_insert_text(editor->sci, indentation_end, " ");
 4015             count++;
 4016             if (i == first_line)
 4017                 first_line_offset = 1;
 4018         }
 4019     }
 4020 
 4021     /* set cursor position */
 4022     if (sel_start < sel_end)
 4023     {
 4024         gint start = sel_start + first_line_offset;
 4025         if (first_line_offset < 0)
 4026             start = MAX(sel_start + first_line_offset,
 4027                         SSM(editor->sci, SCI_POSITIONFROMLINE, first_line, 0));
 4028 
 4029         sci_set_selection_start(editor->sci, start);
 4030         sci_set_selection_end(editor->sci, sel_end + count);
 4031     }
 4032     else
 4033         sci_set_current_position(editor->sci, pos + count, FALSE);
 4034 
 4035     sci_end_undo_action(editor->sci);
 4036 }
 4037 
 4038 
 4039 void editor_finalize(void)
 4040 {
 4041     scintilla_release_resources();
 4042 }
 4043 
 4044 
 4045 /* wordchars: NULL or a string containing characters to match a word.
 4046  * Returns: the current selection or the current word.
 4047  *
 4048  * Passing NULL as wordchars is NOT the same as passing GEANY_WORDCHARS: NULL means
 4049  * using Scintillas's word boundaries. */
 4050 gchar *editor_get_default_selection(GeanyEditor *editor, gboolean use_current_word,
 4051                                     const gchar *wordchars)
 4052 {
 4053     gchar *s = NULL;
 4054 
 4055     g_return_val_if_fail(editor != NULL, NULL);
 4056 
 4057     if (sci_get_lines_selected(editor->sci) == 1)
 4058         s = sci_get_selection_contents(editor->sci);
 4059     else if (sci_get_lines_selected(editor->sci) == 0 && use_current_word)
 4060     {   /* use the word at current cursor position */
 4061         gchar word[GEANY_MAX_WORD_LENGTH];
 4062 
 4063         if (wordchars != NULL)
 4064             editor_find_current_word(editor, -1, word, sizeof(word), wordchars);
 4065         else
 4066             editor_find_current_word_sciwc(editor, -1, word, sizeof(word));
 4067 
 4068         if (word[0] != '\0')
 4069             s = g_strdup(word);
 4070     }
 4071     return s;
 4072 }
 4073 
 4074 
 4075 /* Note: Usually the line should be made visible (not folded) before calling this.
 4076  * Returns: TRUE if line is/will be displayed to the user, or FALSE if it is
 4077  * outside the *vertical* view.
 4078  * Warning: You may need horizontal scrolling to make the cursor visible - so always call
 4079  * sci_scroll_caret() when this returns TRUE. */
 4080 gboolean editor_line_in_view(GeanyEditor *editor, gint line)
 4081 {
 4082     gint vis1, los;
 4083 
 4084     g_return_val_if_fail(editor != NULL, FALSE);
 4085 
 4086     /* If line is wrapped the result may occur on another virtual line than the first and may be
 4087      * still hidden, so increase the line number to check for the next document line */
 4088     if (SSM(editor->sci, SCI_WRAPCOUNT, line, 0) > 1)
 4089         line++;
 4090 
 4091     line = SSM(editor->sci, SCI_VISIBLEFROMDOCLINE, line, 0);   /* convert to visible line number */
 4092     vis1 = SSM(editor->sci, SCI_GETFIRSTVISIBLELINE, 0, 0);
 4093     los = SSM(editor->sci, SCI_LINESONSCREEN, 0, 0);
 4094 
 4095     return (line >= vis1 && line < vis1 + los);
 4096 }
 4097 
 4098 
 4099 /* If the current line is outside the current view window, scroll the line
 4100  * so it appears at percent_of_view. */
 4101 void editor_display_current_line(GeanyEditor *editor, gfloat percent_of_view)
 4102 {
 4103     gint line;
 4104 
 4105     g_return_if_fail(editor != NULL);
 4106 
 4107     line = sci_get_current_line(editor->sci);
 4108 
 4109     /* unfold maybe folded results */
 4110     sci_ensure_line_is_visible(editor->sci, line);
 4111 
 4112     /* scroll the line if it's off screen */
 4113     if (! editor_line_in_view(editor, line))
 4114         editor->scroll_percent = percent_of_view;
 4115     else
 4116         sci_scroll_caret(editor->sci); /* may need horizontal scrolling */
 4117 }
 4118 
 4119 
 4120  /*
 4121  *  Deletes all currently set indicators in the @a editor window.
 4122  *  Error indicators (red squiggly underlines) and usual line markers are removed.
 4123  *
 4124  *  @param editor The editor to operate on.
 4125  */
 4126 void editor_indicator_clear_errors(GeanyEditor *editor)
 4127 {
 4128     editor_indicator_clear(editor, GEANY_INDICATOR_ERROR);
 4129     sci_marker_delete_all(editor->sci, 0);  /* remove the yellow error line marker */
 4130 }
 4131 
 4132 
 4133 /**
 4134  *  Deletes all currently set indicators matching @a indic in the @a editor window.
 4135  *
 4136  *  @param editor The editor to operate on.
 4137  *  @param indic The indicator number to clear, this is a value of @ref GeanyIndicator.
 4138  *
 4139  *  @since 0.16
 4140  */
 4141 GEANY_API_SYMBOL
 4142 void editor_indicator_clear(GeanyEditor *editor, gint indic)
 4143 {
 4144     glong last_pos;
 4145 
 4146     g_return_if_fail(editor != NULL);
 4147 
 4148     last_pos = sci_get_length(editor->sci);
 4149     if (last_pos > 0)
 4150     {
 4151         sci_indicator_set(editor->sci, indic);
 4152         sci_indicator_clear(editor->sci, 0, last_pos);
 4153     }
 4154 }
 4155 
 4156 
 4157 /**
 4158  *  Sets an indicator @a indic on @a line.
 4159  *  Whitespace at the start and the end of the line is not marked.
 4160  *
 4161  *  @param editor The editor to operate on.
 4162  *  @param indic The indicator number to use, this is a value of @ref GeanyIndicator.
 4163  *  @param line The line number which should be marked.
 4164  *
 4165  *  @since 0.16
 4166  */
 4167 GEANY_API_SYMBOL
 4168 void editor_indicator_set_on_line(GeanyEditor *editor, gint indic, gint line)
 4169 {
 4170     gint start, end;
 4171     guint i = 0, len;
 4172     gchar *linebuf;
 4173 
 4174     g_return_if_fail(editor != NULL);
 4175     g_return_if_fail(line >= 0);
 4176 
 4177     start = sci_get_position_from_line(editor->sci, line);
 4178     end = sci_get_position_from_line(editor->sci, line + 1);
 4179 
 4180     /* skip blank lines */
 4181     if ((start + 1) == end ||
 4182         start > end ||
 4183         (sci_get_line_end_position(editor->sci, line) - start) == 0)
 4184     {
 4185         return;
 4186     }
 4187 
 4188     len = end - start;
 4189     linebuf = sci_get_line(editor->sci, line);
 4190 
 4191     /* don't set the indicator on whitespace */
 4192     while (isspace(linebuf[i]))
 4193         i++;
 4194     while (len > 1 && len > i && isspace(linebuf[len - 1]))
 4195     {
 4196         len--;
 4197         end--;
 4198     }
 4199     g_free(linebuf);
 4200 
 4201     editor_indicator_set_on_range(editor, indic, start + i, end);
 4202 }
 4203 
 4204 
 4205 /**
 4206  *  Sets an indicator on the range specified by @a start and @a end.
 4207  *  No error checking or whitespace removal is performed, this should be done by the calling
 4208  *  function if necessary.
 4209  *
 4210  *  @param editor The editor to operate on.
 4211  *  @param indic The indicator number to use, this is a value of @ref GeanyIndicator.
 4212  *  @param start The starting position for the marker.
 4213  *  @param end The ending position for the marker.
 4214  *
 4215  *  @since 0.16
 4216  */
 4217 GEANY_API_SYMBOL
 4218 void editor_indicator_set_on_range(GeanyEditor *editor, gint indic, gint start, gint end)
 4219 {
 4220     g_return_if_fail(editor != NULL);
 4221     if (start >= end)
 4222         return;
 4223 
 4224     sci_indicator_set(editor->sci, indic);
 4225     sci_indicator_fill(editor->sci, start, end - start);
 4226 }
 4227 
 4228 
 4229 /* Inserts the given colour (format should be #...), if there is a selection starting with 0x...
 4230  * the replacement will also start with 0x... */
 4231 void editor_insert_color(GeanyEditor *editor, const gchar *colour)
 4232 {
 4233     g_return_if_fail(editor != NULL);
 4234 
 4235     if (sci_has_selection(editor->sci))
 4236     {
 4237         gint start = sci_get_selection_start(editor->sci);
 4238         const gchar *replacement = colour;
 4239 
 4240         if (sci_get_char_at(editor->sci, start) == '0' &&
 4241             sci_get_char_at(editor->sci, start + 1) == 'x')
 4242         {
 4243             gint end = sci_get_selection_end(editor->sci);
 4244 
 4245             sci_set_selection_start(editor->sci, start + 2);
 4246             /* we need to also re-set the selection end in case the anchor was located before
 4247              * the cursor, since set_selection_start() always moves the cursor, not the anchor */
 4248             sci_set_selection_end(editor->sci, end);
 4249             replacement++; /* skip the leading "0x" */
 4250         }
 4251         else if (sci_get_char_at(editor->sci, start - 1) == '#')
 4252         {   /* double clicking something like #00ffff may only select 00ffff because of wordchars */
 4253             replacement++; /* so skip the '#' to only replace the colour value */
 4254         }
 4255         sci_replace_sel(editor->sci, replacement);
 4256     }
 4257     else
 4258         sci_add_text(editor->sci, colour);
 4259 }
 4260 
 4261 
 4262 /**
 4263  *  Retrieves the end of line characters mode (LF, CR/LF, CR) in the given editor.
 4264  *  If @a editor is @c NULL, the default end of line characters are used.
 4265  *
 4266  *  @param editor @nullable The editor to operate on, or @c NULL to query the default value.
 4267  *  @return The used end of line characters mode.
 4268  *
 4269  *  @since 0.20
 4270  */
 4271 GEANY_API_SYMBOL
 4272 gint editor_get_eol_char_mode(GeanyEditor *editor)
 4273 {
 4274     gint mode = file_prefs.default_eol_character;
 4275 
 4276     if (editor != NULL)
 4277         mode = sci_get_eol_mode(editor->sci);
 4278 
 4279     return mode;
 4280 }
 4281 
 4282 
 4283 /**
 4284  *  Retrieves the localized name (for displaying) of the used end of line characters
 4285  *  (LF, CR/LF, CR) in the given editor.
 4286  *  If @a editor is @c NULL, the default end of line characters are used.
 4287  *
 4288  *  @param editor @nullable The editor to operate on, or @c NULL to query the default value.
 4289  *  @return The name of the end of line characters.
 4290  *
 4291  *  @since 0.19
 4292  */
 4293 GEANY_API_SYMBOL
 4294 const gchar *editor_get_eol_char_name(GeanyEditor *editor)
 4295 {
 4296     gint mode = file_prefs.default_eol_character;
 4297 
 4298     if (editor != NULL)
 4299         mode = sci_get_eol_mode(editor->sci);
 4300 
 4301     return utils_get_eol_name(mode);
 4302 }
 4303 
 4304 
 4305 /**
 4306  *  Retrieves the length of the used end of line characters (LF, CR/LF, CR) in the given editor.
 4307  *  If @a editor is @c NULL, the default end of line characters are used.
 4308  *  The returned value is 1 for CR and LF and 2 for CR/LF.
 4309  *
 4310  *  @param editor @nullable The editor to operate on, or @c NULL to query the default value.
 4311  *  @return The length of the end of line characters.
 4312  *
 4313  *  @since 0.19
 4314  */
 4315 GEANY_API_SYMBOL
 4316 gint editor_get_eol_char_len(GeanyEditor *editor)
 4317 {
 4318     gint mode = file_prefs.default_eol_character;
 4319 
 4320     if (editor != NULL)
 4321         mode = sci_get_eol_mode(editor->sci);
 4322 
 4323     switch (mode)
 4324     {
 4325         case SC_EOL_CRLF: return 2; break;
 4326         default: return 1; break;
 4327     }
 4328 }
 4329 
 4330 
 4331 /**
 4332  *  Retrieves the used end of line characters (LF, CR/LF, CR) in the given editor.
 4333  *  If @a editor is @c NULL, the default end of line characters are used.
 4334  *  The returned value is either "\n", "\r\n" or "\r".
 4335  *
 4336  *  @param editor @nullable The editor to operate on, or @c NULL to query the default value.
 4337  *  @return The end of line characters.
 4338  *
 4339  *  @since 0.19
 4340  */
 4341 GEANY_API_SYMBOL
 4342 const gchar *editor_get_eol_char(GeanyEditor *editor)
 4343 {
 4344     gint mode = file_prefs.default_eol_character;
 4345 
 4346     if (editor != NULL)
 4347         mode = sci_get_eol_mode(editor->sci);
 4348 
 4349     return utils_get_eol_char(mode);
 4350 }
 4351 
 4352 
 4353 static void fold_all(GeanyEditor *editor, gboolean want_fold)
 4354 {
 4355     gint lines, first, i;
 4356 
 4357     if (editor == NULL || ! editor_prefs.folding)
 4358         return;
 4359 
 4360     lines = sci_get_line_count(editor->sci);
 4361     first = sci_get_first_visible_line(editor->sci);
 4362 
 4363     for (i = 0; i < lines; i++)
 4364     {
 4365         gint level = sci_get_fold_level(editor->sci, i);
 4366 
 4367         if (level & SC_FOLDLEVELHEADERFLAG)
 4368         {
 4369             if (sci_get_fold_expanded(editor->sci, i) == want_fold)
 4370                     sci_toggle_fold(editor->sci, i);
 4371         }
 4372     }
 4373     editor_scroll_to_line(editor, first, 0.0F);
 4374 }
 4375 
 4376 
 4377 void editor_unfold_all(GeanyEditor *editor)
 4378 {
 4379     fold_all(editor, FALSE);
 4380 }
 4381 
 4382 
 4383 void editor_fold_all(GeanyEditor *editor)
 4384 {
 4385     fold_all(editor, TRUE);
 4386 }
 4387 
 4388 
 4389 void editor_replace_tabs(GeanyEditor *editor, gboolean ignore_selection)
 4390 {
 4391     gint anchor_pos, caret_pos;
 4392     struct Sci_TextToFind ttf;
 4393 
 4394     g_return_if_fail(editor != NULL);
 4395 
 4396     sci_start_undo_action(editor->sci);
 4397     if (sci_has_selection(editor->sci) && !ignore_selection)
 4398     {
 4399         ttf.chrg.cpMin = sci_get_selection_start(editor->sci);
 4400         ttf.chrg.cpMax = sci_get_selection_end(editor->sci);
 4401     }
 4402     else
 4403     {
 4404         ttf.chrg.cpMin = 0;
 4405         ttf.chrg.cpMax = sci_get_length(editor->sci);
 4406     }
 4407     ttf.lpstrText = (gchar*) "\t";
 4408 
 4409     anchor_pos = SSM(editor->sci, SCI_GETANCHOR, 0, 0);
 4410     caret_pos = sci_get_current_position(editor->sci);
 4411     while (TRUE)
 4412     {
 4413         gint search_pos, pos_in_line, current_tab_true_length;
 4414         gint tab_len;
 4415         gchar *tab_str;
 4416 
 4417         search_pos = sci_find_text(editor->sci, SCFIND_MATCHCASE, &ttf);
 4418         if (search_pos == -1)
 4419             break;
 4420 
 4421         tab_len = sci_get_tab_width(editor->sci);
 4422         pos_in_line = sci_get_col_from_position(editor->sci, search_pos);
 4423         current_tab_true_length = tab_len - (pos_in_line % tab_len);
 4424         tab_str = g_strnfill(current_tab_true_length, ' ');
 4425         sci_set_target_start(editor->sci, search_pos);
 4426         sci_set_target_end(editor->sci, search_pos + 1);
 4427         sci_replace_target(editor->sci, tab_str, FALSE);
 4428         /* next search starts after replacement */
 4429         ttf.chrg.cpMin = search_pos + current_tab_true_length - 1;
 4430         /* update end of range now text has changed */
 4431         ttf.chrg.cpMax += current_tab_true_length - 1;
 4432         g_free(tab_str);
 4433 
 4434         if (anchor_pos > search_pos)
 4435             anchor_pos += current_tab_true_length - 1;
 4436         if (caret_pos > search_pos)
 4437             caret_pos += current_tab_true_length - 1;
 4438     }
 4439     sci_set_selection(editor->sci, anchor_pos, caret_pos);
 4440     sci_end_undo_action(editor->sci);
 4441 }
 4442 
 4443 
 4444 /* Replaces all occurrences all spaces of the length of a given tab_width,
 4445  * optionally restricting the search to the current selection. */
 4446 void editor_replace_spaces(GeanyEditor *editor, gboolean ignore_selection)
 4447 {
 4448     gint search_pos;
 4449     gint anchor_pos, caret_pos;
 4450     static gdouble tab_len_f = -1.0; /* keep the last used value */
 4451     gint tab_len;
 4452     gchar *text;
 4453     struct Sci_TextToFind ttf;
 4454 
 4455     g_return_if_fail(editor != NULL);
 4456 
 4457     if (tab_len_f < 0.0)
 4458         tab_len_f = sci_get_tab_width(editor->sci);
 4459 
 4460     if (! dialogs_show_input_numeric(
 4461         _("Enter Tab Width"),
 4462         _("Enter the amount of spaces which should be replaced by a tab character."),
 4463         &tab_len_f, 1, 100, 1))
 4464     {
 4465         return;
 4466     }
 4467     tab_len = (gint) tab_len_f;
 4468     text = g_strnfill(tab_len, ' ');
 4469 
 4470     sci_start_undo_action(editor->sci);
 4471     if (sci_has_selection(editor->sci) && !ignore_selection)
 4472     {
 4473         ttf.chrg.cpMin = sci_get_selection_start(editor->sci);
 4474         ttf.chrg.cpMax = sci_get_selection_end(editor->sci);
 4475     }
 4476     else
 4477     {
 4478         ttf.chrg.cpMin = 0;
 4479         ttf.chrg.cpMax = sci_get_length(editor->sci);
 4480     }
 4481     ttf.lpstrText = text;
 4482 
 4483     anchor_pos = SSM(editor->sci, SCI_GETANCHOR, 0, 0);
 4484     caret_pos = sci_get_current_position(editor->sci);
 4485     while (TRUE)
 4486     {
 4487         search_pos = sci_find_text(editor->sci, SCFIND_MATCHCASE, &ttf);
 4488         if (search_pos == -1)
 4489             break;
 4490         /* only replace indentation because otherwise we can mess up alignment */
 4491         if (search_pos > sci_get_line_indent_position(editor->sci,
 4492             sci_get_line_from_position(editor->sci, search_pos)))
 4493         {
 4494             ttf.chrg.cpMin = search_pos + tab_len;
 4495             continue;
 4496         }
 4497         sci_set_target_start(editor->sci, search_pos);
 4498         sci_set_target_end(editor->sci, search_pos + tab_len);
 4499         sci_replace_target(editor->sci, "\t", FALSE);
 4500         ttf.chrg.cpMin = search_pos;
 4501         /* update end of range now text has changed */
 4502         ttf.chrg.cpMax -= tab_len - 1;
 4503 
 4504         if (anchor_pos > search_pos)
 4505             anchor_pos -= tab_len - 1;
 4506         if (caret_pos > search_pos)
 4507             caret_pos -= tab_len - 1;
 4508     }
 4509     sci_set_selection(editor->sci, anchor_pos, caret_pos);
 4510     sci_end_undo_action(editor->sci);
 4511     g_free(text);
 4512 }
 4513 
 4514 
 4515 void editor_strip_line_trailing_spaces(GeanyEditor *editor, gint line)
 4516 {
 4517     gint line_start = sci_get_position_from_line(editor->sci, line);
 4518     gint line_end = sci_get_line_end_position(editor->sci, line);
 4519     gint i = line_end - 1;
 4520     gchar ch = sci_get_char_at(editor->sci, i);
 4521 
 4522     /* Diff hunks should keep trailing spaces */
 4523     if (editor->document->file_type->id == GEANY_FILETYPES_DIFF)
 4524         return;
 4525 
 4526     while ((i >= line_start) && ((ch == ' ') || (ch == '\t')))
 4527     {
 4528         i--;
 4529         ch = sci_get_char_at(editor->sci, i);
 4530     }
 4531     if (i < (line_end - 1))
 4532     {
 4533         sci_set_target_start(editor->sci, i + 1);
 4534         sci_set_target_end(editor->sci, line_end);
 4535         sci_replace_target(editor->sci, "", FALSE);
 4536     }
 4537 }
 4538 
 4539 
 4540 void editor_strip_trailing_spaces(GeanyEditor *editor, gboolean ignore_selection)
 4541 {
 4542     gint start_line;
 4543     gint end_line;
 4544     gint line;
 4545 
 4546     if (sci_has_selection(editor->sci) && !ignore_selection)
 4547     {
 4548         gint selection_start = sci_get_selection_start(editor->sci);
 4549         gint selection_end = sci_get_selection_end(editor->sci);
 4550 
 4551         start_line = sci_get_line_from_position(editor->sci, selection_start);
 4552         end_line = sci_get_line_from_position(editor->sci, selection_end);
 4553 
 4554         if (sci_get_col_from_position(editor->sci, selection_end) > 0)
 4555             end_line++;
 4556     }
 4557     else
 4558     {
 4559         start_line = 0;
 4560         end_line = sci_get_line_count(editor->sci);
 4561     }
 4562 
 4563     sci_start_undo_action(editor->sci);
 4564 
 4565     for (line = start_line; line < end_line; line++)
 4566     {
 4567         editor_strip_line_trailing_spaces(editor, line);
 4568     }
 4569     sci_end_undo_action(editor->sci);
 4570 }
 4571 
 4572 
 4573 void editor_ensure_final_newline(GeanyEditor *editor)
 4574 {
 4575     gint max_lines = sci_get_line_count(editor->sci);
 4576     gboolean append_newline = (max_lines == 1);
 4577     gint end_document = sci_get_position_from_line(editor->sci, max_lines);
 4578 
 4579     if (max_lines > 1)
 4580     {
 4581         append_newline = end_document > sci_get_position_from_line(editor->sci, max_lines - 1);
 4582     }
 4583     if (append_newline)
 4584     {
 4585         const gchar *eol = editor_get_eol_char(editor);
 4586 
 4587         sci_insert_text(editor->sci, end_document, eol);
 4588     }
 4589 }
 4590 
 4591 
 4592 void editor_set_font(GeanyEditor *editor, const gchar *font)
 4593 {
 4594     gint style;
 4595     gchar *font_name;
 4596     PangoFontDescription *pfd;
 4597     gdouble size;
 4598 
 4599     g_return_if_fail(editor);
 4600 
 4601     pfd = pango_font_description_from_string(font);
 4602     size = pango_font_description_get_size(pfd) / (gdouble) PANGO_SCALE;
 4603     font_name = g_strdup_printf("!%s", pango_font_description_get_family(pfd));
 4604     pango_font_description_free(pfd);
 4605 
 4606     for (style = 0; style <= STYLE_MAX; style++)
 4607         sci_set_font_fractional(editor->sci, style, font_name, size);
 4608 
 4609     g_free(font_name);
 4610 
 4611     /* zoom to 100% to prevent confusion */
 4612     sci_zoom_off(editor->sci);
 4613 }
 4614 
 4615 
 4616 void editor_set_line_wrapping(GeanyEditor *editor, gboolean wrap)
 4617 {
 4618     g_return_if_fail(editor != NULL);
 4619 
 4620     editor->line_wrapping = wrap;
 4621     sci_set_lines_wrapped(editor->sci, wrap);
 4622 }
 4623 
 4624 
 4625 /** Sets the indent type for @a editor.
 4626  * @param editor Editor.
 4627  * @param type Indent type.
 4628  *
 4629  *  @since 0.16
 4630  */
 4631 GEANY_API_SYMBOL
 4632 void editor_set_indent_type(GeanyEditor *editor, GeanyIndentType type)
 4633 {
 4634     editor_set_indent(editor, type, editor->indent_width);
 4635 }
 4636 
 4637 
 4638 /** Sets the indent width for @a editor.
 4639  * @param editor Editor.
 4640  * @param width New indent width.
 4641  *
 4642  * @since 1.27 (API 227)
 4643  */
 4644 GEANY_API_SYMBOL
 4645 void editor_set_indent_width(GeanyEditor *editor, gint width)
 4646 {
 4647     editor_set_indent(editor, editor->indent_type, width);
 4648 }
 4649 
 4650 
 4651 void editor_set_indent(GeanyEditor *editor, GeanyIndentType type, gint width)
 4652 {
 4653     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 4654     ScintillaObject *sci = editor->sci;
 4655     gboolean use_tabs = type != GEANY_INDENT_TYPE_SPACES;
 4656 
 4657     editor->indent_type = type;
 4658     editor->indent_width = width;
 4659     sci_set_use_tabs(sci, use_tabs);
 4660 
 4661     if (type == GEANY_INDENT_TYPE_BOTH)
 4662     {
 4663         sci_set_tab_width(sci, iprefs->hard_tab_width);
 4664         if (iprefs->hard_tab_width != 8)
 4665         {
 4666             static gboolean warn = TRUE;
 4667             if (warn)
 4668                 ui_set_statusbar(TRUE, _("Warning: non-standard hard tab width: %d != 8!"),
 4669                     iprefs->hard_tab_width);
 4670             warn = FALSE;
 4671         }
 4672     }
 4673     else
 4674         sci_set_tab_width(sci, width);
 4675 
 4676     SSM(sci, SCI_SETINDENT, width, 0);
 4677 
 4678     /* remove indent spaces on backspace, if using any spaces to indent */
 4679     SSM(sci, SCI_SETBACKSPACEUNINDENTS, type != GEANY_INDENT_TYPE_TABS, 0);
 4680 }
 4681 
 4682 
 4683 /* Convenience function for editor_goto_pos() to pass in a line number. */
 4684 gboolean editor_goto_line(GeanyEditor *editor, gint line_no, gint offset)
 4685 {
 4686     gint pos;
 4687 
 4688     g_return_val_if_fail(editor, FALSE);
 4689     if (line_no < 0 || line_no >= sci_get_line_count(editor->sci))
 4690         return FALSE;
 4691 
 4692     if (offset != 0)
 4693     {
 4694         gint current_line = sci_get_current_line(editor->sci);
 4695         line_no *= offset;
 4696         line_no = current_line + line_no;
 4697     }
 4698 
 4699     pos = sci_get_position_from_line(editor->sci, line_no);
 4700     return editor_goto_pos(editor, pos, TRUE);
 4701 }
 4702 
 4703 
 4704 /** Moves to position @a pos, switching to the document if necessary,
 4705  *  setting a marker if @a mark is set.
 4706  *
 4707  * @param editor Editor.
 4708  * @param pos The position.
 4709  * @param mark Whether to set a mark on the position.
 4710  * @return @c TRUE if action has been performed, otherwise @c FALSE.
 4711  *
 4712  *  @since 0.20
 4713  **/
 4714 GEANY_API_SYMBOL
 4715 gboolean editor_goto_pos(GeanyEditor *editor, gint pos, gboolean mark)
 4716 {
 4717     g_return_val_if_fail(editor, FALSE);
 4718     if (G_UNLIKELY(pos < 0))
 4719         return FALSE;
 4720 
 4721     if (mark)
 4722     {
 4723         gint line = sci_get_line_from_position(editor->sci, pos);
 4724 
 4725         /* mark the tag with the yellow arrow */
 4726         sci_marker_delete_all(editor->sci, 0);
 4727         sci_set_marker_at_line(editor->sci, line, 0);
 4728     }
 4729 
 4730     sci_goto_pos(editor->sci, pos, TRUE);
 4731     editor->scroll_percent = 0.25F;
 4732 
 4733     /* finally switch to the page */
 4734     document_show_tab(editor->document);
 4735     return TRUE;
 4736 }
 4737 
 4738 
 4739 static gboolean
 4740 on_editor_scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
 4741 {
 4742     GeanyEditor *editor = user_data;
 4743 
 4744     /* we only handle up and down, leave the rest to Scintilla */
 4745     if (event->direction != GDK_SCROLL_UP && event->direction != GDK_SCROLL_DOWN)
 4746         return FALSE;
 4747 
 4748     /* Handle scroll events if Alt is pressed and scroll whole pages instead of a
 4749      * few lines only, maybe this could/should be done in Scintilla directly */
 4750     if (event->state & GDK_MOD1_MASK)
 4751     {
 4752         sci_send_command(editor->sci, (event->direction == GDK_SCROLL_DOWN) ? SCI_PAGEDOWN : SCI_PAGEUP);
 4753         return TRUE;
 4754     }
 4755     else if (event->state & GDK_SHIFT_MASK)
 4756     {
 4757         gint amount = (event->direction == GDK_SCROLL_DOWN) ? 8 : -8;
 4758 
 4759         sci_scroll_columns(editor->sci, amount);
 4760         return TRUE;
 4761     }
 4762 
 4763     return FALSE; /* let Scintilla handle all other cases */
 4764 }
 4765 
 4766 
 4767 static gboolean editor_check_colourise(GeanyEditor *editor)
 4768 {
 4769     GeanyDocument *doc = editor->document;
 4770 
 4771     if (!doc->priv->colourise_needed)
 4772         return FALSE;
 4773 
 4774     doc->priv->colourise_needed = FALSE;
 4775     sci_colourise(editor->sci, 0, -1);
 4776 
 4777     /* now that the current document is colourised, fold points are now accurate,
 4778      * so force an update of the current function/tag. */
 4779     symbols_get_current_function(NULL, NULL);
 4780     ui_update_statusbar(NULL, -1);
 4781 
 4782     return TRUE;
 4783 }
 4784 
 4785 
 4786 /* We only want to colourise just before drawing, to save startup time and
 4787  * prevent unnecessary recolouring other documents after one is saved.
 4788  * Really we want a "draw" signal but there doesn't seem to be one (expose is too late,
 4789  * and "show" doesn't work). */
 4790 static gboolean on_editor_focus_in(GtkWidget *widget, GdkEventFocus *event, gpointer user_data)
 4791 {
 4792     GeanyEditor *editor = user_data;
 4793 
 4794     editor_check_colourise(editor);
 4795     return FALSE;
 4796 }
 4797 
 4798 
 4799 static gboolean on_editor_expose_event(GtkWidget *widget, GdkEventExpose *event,
 4800         gpointer user_data)
 4801 {
 4802     GeanyEditor *editor = user_data;
 4803 
 4804     /* This is just to catch any uncolourised documents being drawn that didn't receive focus
 4805      * for some reason, maybe it's not necessary but just in case. */
 4806     editor_check_colourise(editor);
 4807     return FALSE;
 4808 }
 4809 
 4810 
 4811 #if GTK_CHECK_VERSION(3, 0, 0)
 4812 static gboolean on_editor_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
 4813 {
 4814     return on_editor_expose_event(widget, NULL, user_data);
 4815 }
 4816 #endif
 4817 
 4818 
 4819 static void setup_sci_keys(ScintillaObject *sci)
 4820 {
 4821     /* disable some Scintilla keybindings to be able to redefine them cleanly */
 4822     sci_clear_cmdkey(sci, 'A' | (SCMOD_CTRL << 16)); /* select all */
 4823     sci_clear_cmdkey(sci, 'D' | (SCMOD_CTRL << 16)); /* duplicate */
 4824     sci_clear_cmdkey(sci, 'T' | (SCMOD_CTRL << 16)); /* line transpose */
 4825     sci_clear_cmdkey(sci, 'T' | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16)); /* line copy */
 4826     sci_clear_cmdkey(sci, 'L' | (SCMOD_CTRL << 16)); /* line cut */
 4827     sci_clear_cmdkey(sci, 'L' | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16)); /* line delete */
 4828     sci_clear_cmdkey(sci, SCK_DELETE | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16)); /* line to end delete */
 4829     sci_clear_cmdkey(sci, SCK_BACK | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16)); /* line to beginning delete */
 4830     sci_clear_cmdkey(sci, '/' | (SCMOD_CTRL << 16)); /* Previous word part */
 4831     sci_clear_cmdkey(sci, '\\' | (SCMOD_CTRL << 16)); /* Next word part */
 4832     sci_clear_cmdkey(sci, SCK_UP | (SCMOD_CTRL << 16)); /* scroll line up */
 4833     sci_clear_cmdkey(sci, SCK_DOWN | (SCMOD_CTRL << 16)); /* scroll line down */
 4834     sci_clear_cmdkey(sci, SCK_HOME); /* line start */
 4835     sci_clear_cmdkey(sci, SCK_END); /* line end */
 4836     sci_clear_cmdkey(sci, SCK_END | (SCMOD_ALT << 16)); /* visual line end */
 4837 
 4838     if (editor_prefs.use_gtk_word_boundaries)
 4839     {
 4840         /* use GtkEntry-like word boundaries */
 4841         sci_assign_cmdkey(sci, SCK_RIGHT | (SCMOD_CTRL << 16), SCI_WORDRIGHTEND);
 4842         sci_assign_cmdkey(sci, SCK_RIGHT | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16), SCI_WORDRIGHTENDEXTEND);
 4843         sci_assign_cmdkey(sci, SCK_DELETE | (SCMOD_CTRL << 16), SCI_DELWORDRIGHTEND);
 4844     }
 4845     sci_assign_cmdkey(sci, SCK_UP | (SCMOD_ALT << 16), SCI_LINESCROLLUP);
 4846     sci_assign_cmdkey(sci, SCK_DOWN | (SCMOD_ALT << 16), SCI_LINESCROLLDOWN);
 4847     sci_assign_cmdkey(sci, SCK_UP | (SCMOD_CTRL << 16), SCI_PARAUP);
 4848     sci_assign_cmdkey(sci, SCK_UP | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16), SCI_PARAUPEXTEND);
 4849     sci_assign_cmdkey(sci, SCK_DOWN | (SCMOD_CTRL << 16), SCI_PARADOWN);
 4850     sci_assign_cmdkey(sci, SCK_DOWN | (SCMOD_CTRL << 16) | (SCMOD_SHIFT << 16), SCI_PARADOWNEXTEND);
 4851 
 4852     sci_clear_cmdkey(sci, SCK_BACK | (SCMOD_ALT << 16)); /* clear Alt-Backspace (Undo) */
 4853 }
 4854 
 4855 
 4856 /* registers a Scintilla image from a named icon from the theme */
 4857 static gboolean register_named_icon(ScintillaObject *sci, guint id, const gchar *name)
 4858 {
 4859     GError *error = NULL;
 4860     GdkPixbuf *pixbuf;
 4861     gint n_channels, rowstride, width, height;
 4862     gint size;
 4863 
 4864     gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &size, NULL);
 4865     pixbuf = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(), name, size, 0, &error);
 4866     if (! pixbuf)
 4867     {
 4868         g_warning("failed to load icon '%s': %s", name, error->message);
 4869         g_error_free(error);
 4870         return FALSE;
 4871     }
 4872 
 4873     n_channels = gdk_pixbuf_get_n_channels(pixbuf);
 4874     rowstride = gdk_pixbuf_get_rowstride(pixbuf);
 4875     width = gdk_pixbuf_get_width(pixbuf);
 4876     height = gdk_pixbuf_get_height(pixbuf);
 4877 
 4878     if (gdk_pixbuf_get_bits_per_sample(pixbuf) != 8 ||
 4879         ! gdk_pixbuf_get_has_alpha(pixbuf) ||
 4880         n_channels != 4 ||
 4881         rowstride != width * n_channels)
 4882     {
 4883         g_warning("incompatible image data for icon '%s'", name);
 4884         g_object_unref(pixbuf);
 4885         return FALSE;
 4886     }
 4887 
 4888     SSM(sci, SCI_RGBAIMAGESETWIDTH, width, 0);
 4889     SSM(sci, SCI_RGBAIMAGESETHEIGHT, height, 0);
 4890     SSM(sci, SCI_REGISTERRGBAIMAGE, id, (sptr_t)gdk_pixbuf_get_pixels(pixbuf));
 4891 
 4892     g_object_unref(pixbuf);
 4893     return TRUE;
 4894 }
 4895 
 4896 
 4897 /* Create new editor widget (scintilla).
 4898  * @note The @c "sci-notify" signal is connected separately. */
 4899 static ScintillaObject *create_new_sci(GeanyEditor *editor)
 4900 {
 4901     ScintillaObject *sci;
 4902     int rectangular_selection_modifier;
 4903 
 4904     sci = SCINTILLA(scintilla_new());
 4905 
 4906     /* Scintilla doesn't support RTL languages properly and is primarily
 4907      * intended to be used with LTR source code, so override the
 4908      * GTK+ default text direction for the Scintilla widget. */
 4909     gtk_widget_set_direction(GTK_WIDGET(sci), GTK_TEXT_DIR_LTR);
 4910 
 4911     gtk_widget_show(GTK_WIDGET(sci));
 4912 
 4913     sci_set_codepage(sci, SC_CP_UTF8);
 4914     /*SSM(sci, SCI_SETWRAPSTARTINDENT, 4, 0);*/
 4915     /* disable scintilla provided popup menu */
 4916     sci_use_popup(sci, FALSE);
 4917 
 4918     setup_sci_keys(sci);
 4919 
 4920     sci_set_symbol_margin(sci, editor_prefs.show_markers_margin);
 4921     sci_set_lines_wrapped(sci, editor->line_wrapping);
 4922     sci_set_caret_policy_x(sci, CARET_JUMPS | CARET_EVEN, 0);
 4923     /* Y policy is set in editor_apply_update_prefs() */
 4924     SSM(sci, SCI_AUTOCSETSEPARATOR, '\n', 0);
 4925     SSM(sci, SCI_SETSCROLLWIDTHTRACKING, 1, 0);
 4926 
 4927     /* tag autocompletion images */
 4928     register_named_icon(sci, 1, "classviewer-var");
 4929     register_named_icon(sci, 2, "classviewer-method");
 4930 
 4931     /* necessary for column mode editing, implemented in Scintilla since 2.0 */
 4932     SSM(sci, SCI_SETADDITIONALSELECTIONTYPING, 1, 0);
 4933 
 4934     /* rectangular selection modifier for creating rectangular selections with the mouse.
 4935      * We use the historical Scintilla values by default. */
 4936 #ifdef G_OS_WIN32
 4937     rectangular_selection_modifier = SCMOD_ALT;
 4938 #else
 4939     rectangular_selection_modifier = SCMOD_CTRL;
 4940 #endif
 4941     SSM(sci, SCI_SETRECTANGULARSELECTIONMODIFIER, rectangular_selection_modifier, 0);
 4942 
 4943     /* virtual space */
 4944     SSM(sci, SCI_SETVIRTUALSPACEOPTIONS, editor_prefs.show_virtual_space, 0);
 4945 
 4946     /* input method editor's candidate window behaviour */
 4947     SSM(sci, SCI_SETIMEINTERACTION, editor_prefs.ime_interaction, 0);
 4948 
 4949 #ifdef GDK_WINDOWING_QUARTZ
 4950 # if ! GTK_CHECK_VERSION(3,16,0)
 4951     /* "retina" (HiDPI) display support on OS X - requires disabling buffered draw
 4952      * on older GTK versions */
 4953     SSM(sci, SCI_SETBUFFEREDDRAW, 0, 0);
 4954 # endif
 4955 #endif
 4956 
 4957     /* only connect signals if this is for the document notebook, not split window */
 4958     if (editor->sci == NULL)
 4959     {
 4960         g_signal_connect(sci, "button-press-event", G_CALLBACK(on_editor_button_press_event), editor);
 4961         g_signal_connect(sci, "scroll-event", G_CALLBACK(on_editor_scroll_event), editor);
 4962         g_signal_connect(sci, "motion-notify-event", G_CALLBACK(on_motion_event), NULL);
 4963         g_signal_connect(sci, "focus-in-event", G_CALLBACK(on_editor_focus_in), editor);
 4964 #if GTK_CHECK_VERSION(3, 0, 0)
 4965         g_signal_connect(sci, "draw", G_CALLBACK(on_editor_draw), editor);
 4966 #else
 4967         g_signal_connect(sci, "expose-event", G_CALLBACK(on_editor_expose_event), editor);
 4968 #endif
 4969     }
 4970     return sci;
 4971 }
 4972 
 4973 
 4974 /** Creates a new Scintilla @c GtkWidget based on the settings for @a editor.
 4975  * @param editor Editor settings.
 4976  * @return @transfer{floating} The new widget.
 4977  *
 4978  * @since 0.15
 4979  **/
 4980 GEANY_API_SYMBOL
 4981 ScintillaObject *editor_create_widget(GeanyEditor *editor)
 4982 {
 4983     const GeanyIndentPrefs *iprefs = get_default_indent_prefs();
 4984     ScintillaObject *old, *sci;
 4985     GeanyIndentType old_indent_type = editor->indent_type;
 4986     gint old_indent_width = editor->indent_width;
 4987 
 4988     /* temporarily change editor to use the new sci widget */
 4989     old = editor->sci;
 4990     sci = create_new_sci(editor);
 4991     editor->sci = sci;
 4992 
 4993     editor_set_indent(editor, iprefs->type, iprefs->width);
 4994     editor_set_font(editor, interface_prefs.editor_font);
 4995     editor_apply_update_prefs(editor);
 4996 
 4997     /* if editor already had a widget, restore it */
 4998     if (old)
 4999     {
 5000         editor->indent_type = old_indent_type;
 5001         editor->indent_width = old_indent_width;
 5002         editor->sci = old;
 5003     }
 5004     return sci;
 5005 }
 5006 
 5007 
 5008 GeanyEditor *editor_create(GeanyDocument *doc)
 5009 {
 5010     const GeanyIndentPrefs *iprefs = get_default_indent_prefs();
 5011     GeanyEditor *editor = g_new0(GeanyEditor, 1);
 5012 
 5013     editor->document = doc;
 5014     doc->editor = editor;   /* needed in case some editor functions/callbacks expect it */
 5015 
 5016     editor->auto_indent = (iprefs->auto_indent_mode != GEANY_AUTOINDENT_NONE);
 5017     editor->line_wrapping = get_project_pref(line_wrapping);
 5018     editor->scroll_percent = -1.0F;
 5019     editor->line_breaking = FALSE;
 5020 
 5021     editor->sci = editor_create_widget(editor);
 5022     return editor;
 5023 }
 5024 
 5025 
 5026 /* in case we need to free some fields in future */
 5027 void editor_destroy(GeanyEditor *editor)
 5028 {
 5029     g_free(editor);
 5030 }
 5031 
 5032 
 5033 static void on_document_save(GObject *obj, GeanyDocument *doc)
 5034 {
 5035     gchar *f = g_build_filename(app->configdir, "snippets.conf", NULL);
 5036 
 5037     if (utils_str_equal(doc->real_path, f))
 5038     {
 5039         /* reload snippets */
 5040         editor_snippets_free();
 5041         editor_snippets_init();
 5042     }
 5043     g_free(f);
 5044 }
 5045 
 5046 
 5047 gboolean editor_complete_word_part(GeanyEditor *editor)
 5048 {
 5049     gchar *entry;
 5050 
 5051     g_return_val_if_fail(editor, FALSE);
 5052 
 5053     if (!SSM(editor->sci, SCI_AUTOCACTIVE, 0, 0))
 5054         return FALSE;
 5055 
 5056     entry = sci_get_string(editor->sci, SCI_AUTOCGETCURRENTTEXT, 0);
 5057 
 5058     /* if no word part, complete normally */
 5059     if (!check_partial_completion(editor, entry))
 5060         SSM(editor->sci, SCI_AUTOCCOMPLETE, 0, 0);
 5061 
 5062     g_free(entry);
 5063     return TRUE;
 5064 }
 5065 
 5066 
 5067 void editor_init(void)
 5068 {
 5069     static GeanyIndentPrefs indent_prefs;
 5070     gchar *f;
 5071 
 5072     memset(&editor_prefs, 0, sizeof(GeanyEditorPrefs));
 5073     memset(&indent_prefs, 0, sizeof(GeanyIndentPrefs));
 5074     editor_prefs.indentation = &indent_prefs;
 5075 
 5076     /* use g_signal_connect_after() to allow plugins connecting to the signal before the default
 5077      * handler (on_editor_notify) is called */
 5078     g_signal_connect_after(geany_object, "editor-notify", G_CALLBACK(on_editor_notify), NULL);
 5079 
 5080     f = g_build_filename(app->configdir, "snippets.conf", NULL);
 5081     ui_add_config_file_menu_item(f, NULL, NULL);
 5082     g_free(f);
 5083     g_signal_connect(geany_object, "document-save", G_CALLBACK(on_document_save), NULL);
 5084 }
 5085 
 5086 
 5087 /* TODO: Should these be user-defined instead of hard-coded? */
 5088 void editor_set_indentation_guides(GeanyEditor *editor)
 5089 {
 5090     gint mode;
 5091     gint lexer;
 5092 
 5093     g_return_if_fail(editor != NULL);
 5094 
 5095     if (! editor_prefs.show_indent_guide)
 5096     {
 5097         sci_set_indentation_guides(editor->sci, SC_IV_NONE);
 5098         return;
 5099     }
 5100 
 5101     lexer = sci_get_lexer(editor->sci);
 5102     switch (lexer)
 5103     {
 5104         /* Lines added/removed are prefixed with +/- characters, so
 5105          * those lines will not be shown with any indentation guides.
 5106          * It can be distracting that only a few of lines in a diff/patch
 5107          * file will show the guides. */
 5108         case SCLEX_DIFF:
 5109             mode = SC_IV_NONE;
 5110             break;
 5111 
 5112         /* These languages use indentation for control blocks; the "look forward" method works
 5113          * best here */
 5114         case SCLEX_PYTHON:
 5115         case SCLEX_HASKELL:
 5116         case SCLEX_MAKEFILE:
 5117         case SCLEX_ASM:
 5118         case SCLEX_SQL:
 5119         case SCLEX_COBOL:
 5120         case SCLEX_PROPERTIES:
 5121         case SCLEX_FORTRAN: /* Is this the best option for Fortran? */
 5122         case SCLEX_CAML:
 5123             mode = SC_IV_LOOKFORWARD;
 5124             break;
 5125 
 5126         /* C-like (structured) languages benefit from the "look both" method */
 5127         case SCLEX_CPP:
 5128         case SCLEX_HTML:
 5129         case SCLEX_PHPSCRIPT:
 5130         case SCLEX_XML:
 5131         case SCLEX_PERL:
 5132         case SCLEX_LATEX:
 5133         case SCLEX_LUA:
 5134         case SCLEX_PASCAL:
 5135         case SCLEX_RUBY:
 5136         case SCLEX_TCL:
 5137         case SCLEX_F77:
 5138         case SCLEX_CSS:
 5139         case SCLEX_BASH:
 5140         case SCLEX_VHDL:
 5141         case SCLEX_FREEBASIC:
 5142         case SCLEX_D:
 5143         case SCLEX_OCTAVE:
 5144         case SCLEX_RUST:
 5145             mode = SC_IV_LOOKBOTH;
 5146             break;
 5147 
 5148         default:
 5149             mode = SC_IV_REAL;
 5150             break;
 5151     }
 5152 
 5153     sci_set_indentation_guides(editor->sci, mode);
 5154 }
 5155 
 5156 
 5157 /* Apply non-document prefs that can change in the Preferences dialog */
 5158 void editor_apply_update_prefs(GeanyEditor *editor)
 5159 {
 5160     ScintillaObject *sci;
 5161     int caret_y_policy;
 5162 
 5163     g_return_if_fail(editor != NULL);
 5164 
 5165     if (main_status.quitting)
 5166         return;
 5167 
 5168     sci = editor->sci;
 5169 
 5170     sci_set_mark_long_lines(sci, editor_get_long_line_type(),
 5171         editor_get_long_line_column(), editor_prefs.long_line_color);
 5172 
 5173     /* update indent width, tab width */
 5174     editor_set_indent(editor, editor->indent_type, editor->indent_width);
 5175     sci_set_tab_indents(sci, editor_prefs.use_tab_to_indent);
 5176 
 5177     sci_assign_cmdkey(sci, SCK_HOME | (SCMOD_SHIFT << 16),
 5178         editor_prefs.smart_home_key ? SCI_VCHOMEEXTEND : SCI_HOMEEXTEND);
 5179     sci_assign_cmdkey(sci, SCK_HOME | ((SCMOD_SHIFT | SCMOD_ALT) << 16),
 5180         editor_prefs.smart_home_key ? SCI_VCHOMERECTEXTEND : SCI_HOMERECTEXTEND);
 5181 
 5182     sci_set_autoc_max_height(sci, editor_prefs.symbolcompletion_max_height);
 5183     SSM(sci, SCI_AUTOCSETDROPRESTOFWORD, editor_prefs.completion_drops_rest_of_word, 0);
 5184 
 5185     editor_set_indentation_guides(editor);
 5186 
 5187     sci_set_visible_white_spaces(sci, editor_prefs.show_white_space);
 5188     sci_set_visible_eols(sci, editor_prefs.show_line_endings);
 5189     sci_set_symbol_margin(sci, editor_prefs.show_markers_margin);
 5190     sci_set_line_numbers(sci, editor_prefs.show_linenumber_margin);
 5191 
 5192     sci_set_folding_margin_visible(sci, editor_prefs.folding);
 5193 
 5194     /* virtual space */
 5195     SSM(sci, SCI_SETVIRTUALSPACEOPTIONS, editor_prefs.show_virtual_space, 0);
 5196 
 5197     /* caret Y policy */
 5198     caret_y_policy = CARET_EVEN;
 5199     if (editor_prefs.scroll_lines_around_cursor > 0)
 5200         caret_y_policy |= CARET_SLOP | CARET_STRICT;
 5201     sci_set_caret_policy_y(sci, caret_y_policy, editor_prefs.scroll_lines_around_cursor);
 5202 
 5203     /* (dis)allow scrolling past end of document */
 5204     sci_set_scroll_stop_at_last_line(sci, editor_prefs.scroll_stop_at_last_line);
 5205 
 5206     sci_set_scrollbar_mode(sci, editor_prefs.show_scrollbars);
 5207 }
 5208 
 5209 
 5210 /* This is for tab-indents, space aligns formatted code. Spaces should be preserved. */
 5211 static void change_tab_indentation(GeanyEditor *editor, gint line, gboolean increase)
 5212 {
 5213     ScintillaObject *sci = editor->sci;
 5214     gint pos = sci_get_position_from_line(sci, line);
 5215 
 5216     if (increase)
 5217     {
 5218         sci_insert_text(sci, pos, "\t");
 5219     }
 5220     else
 5221     {
 5222         if (sci_get_char_at(sci, pos) == '\t')
 5223         {
 5224             sci_set_selection(sci, pos, pos + 1);
 5225             sci_replace_sel(sci, "");
 5226         }
 5227         else /* remove spaces only if no tabs */
 5228         {
 5229             gint width = sci_get_line_indentation(sci, line);
 5230 
 5231             width -= editor_get_indent_prefs(editor)->width;
 5232             sci_set_line_indentation(sci, line, width);
 5233         }
 5234     }
 5235 }
 5236 
 5237 
 5238 static void editor_change_line_indent(GeanyEditor *editor, gint line, gboolean increase)
 5239 {
 5240     const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);
 5241     ScintillaObject *sci = editor->sci;
 5242 
 5243     if (iprefs->type == GEANY_INDENT_TYPE_TABS)
 5244         change_tab_indentation(editor, line, increase);
 5245     else
 5246     {
 5247         gint width = sci_get_line_indentation(sci, line);
 5248 
 5249         width += increase ? iprefs->width : -iprefs->width;
 5250         sci_set_line_indentation(sci, line, width);
 5251     }
 5252 }
 5253 
 5254 
 5255 void editor_indent(GeanyEditor *editor, gboolean increase)
 5256 {
 5257     ScintillaObject *sci = editor->sci;
 5258     gint caret_pos, caret_line, caret_offset, caret_indent_pos, caret_line_len;
 5259     gint anchor_pos, anchor_line, anchor_offset, anchor_indent_pos, anchor_line_len;
 5260 
 5261     /* backup information needed to restore caret and anchor */
 5262     caret_pos = sci_get_current_position(sci);
 5263     anchor_pos = SSM(sci, SCI_GETANCHOR, 0, 0);
 5264     caret_line = sci_get_line_from_position(sci, caret_pos);
 5265     anchor_line = sci_get_line_from_position(sci, anchor_pos);
 5266     caret_offset = caret_pos - sci_get_position_from_line(sci, caret_line);
 5267     anchor_offset = anchor_pos - sci_get_position_from_line(sci, anchor_line);
 5268     caret_indent_pos = sci_get_line_indent_position(sci, caret_line);
 5269     anchor_indent_pos = sci_get_line_indent_position(sci, anchor_line);
 5270     caret_line_len = sci_get_line_length(sci, caret_line);
 5271     anchor_line_len = sci_get_line_length(sci, anchor_line);
 5272 
 5273     if (sci_get_lines_selected(sci) <= 1)
 5274     {
 5275         editor_change_line_indent(editor, sci_get_current_line(sci), increase);
 5276     }
 5277     else
 5278     {
 5279         gint start, end;
 5280         gint line, lstart, lend;
 5281 
 5282         editor_select_lines(editor, FALSE);
 5283         start = sci_get_selection_start(sci);
 5284         end = sci_get_selection_end(sci);
 5285         lstart = sci_get_line_from_position(sci, start);
 5286         lend = sci_get_line_from_position(sci, end);
 5287         if (end == sci_get_length(sci))
 5288             lend++; /* for last line with text on it */
 5289 
 5290         sci_start_undo_action(sci);
 5291         for (line = lstart; line < lend; line++)
 5292         {
 5293             editor_change_line_indent(editor, line, increase);
 5294         }
 5295         sci_end_undo_action(sci);
 5296     }
 5297 
 5298     /* restore caret and anchor position */
 5299     if (caret_pos >= caret_indent_pos)
 5300         caret_offset += sci_get_line_length(sci, caret_line) - caret_line_len;
 5301     if (anchor_pos >= anchor_indent_pos)
 5302         anchor_offset += sci_get_line_length(sci, anchor_line) - anchor_line_len;
 5303 
 5304     SSM(sci, SCI_SETCURRENTPOS, sci_get_position_from_line(sci, caret_line) + caret_offset, 0);
 5305     SSM(sci, SCI_SETANCHOR, sci_get_position_from_line(sci, anchor_line) + anchor_offset, 0);
 5306 }
 5307 
 5308 
 5309 /** Gets snippet by name.
 5310  *
 5311  * If @a editor is passed, returns a snippet specific to the document filetype.
 5312  * If @a editor is @c NULL, returns a snippet from the default set.
 5313  *
 5314  * @param editor @nullable Editor or @c NULL.
 5315  * @param snippet_name Snippet name.
 5316  * @return @nullable snippet or @c NULL if it was not found. Must not be freed.
 5317  */
 5318 GEANY_API_SYMBOL
 5319 const gchar *editor_find_snippet(GeanyEditor *editor, const gchar *snippet_name)
 5320 {
 5321     const gchar *subhash_name = editor ? editor->document->file_type->name : "Default";
 5322     GHashTable *subhash = g_hash_table_lookup(snippet_hash, subhash_name);
 5323 
 5324     return subhash ? g_hash_table_lookup(subhash, snippet_name) : NULL;
 5325 }
 5326 
 5327 
 5328 /** Replaces all special sequences in @a snippet and inserts it at @a pos.
 5329  * If you insert at the current position, consider calling @c sci_scroll_caret()
 5330  * after this function.
 5331  * @param editor .
 5332  * @param pos .
 5333  * @param snippet .
 5334  */
 5335 GEANY_API_SYMBOL
 5336 void editor_insert_snippet(GeanyEditor *editor, gint pos, const gchar *snippet)
 5337 {
 5338     GString *pattern;
 5339 
 5340     pattern = g_string_new(snippet);
 5341     snippets_make_replacements(editor, pattern);
 5342     editor_insert_text_block(editor, pattern->str, pos, -1, -1, TRUE);
 5343     g_string_free(pattern, TRUE);
 5344 }
 5345 
 5346 static void        *copy_(void *src) { return src; }
 5347 static void         free_(void *doc) { }
 5348 
 5349 /** @gironly
 5350  * Gets the GType of GeanyEditor
 5351  *
 5352  * @return the GeanyEditor type */
 5353 GEANY_API_SYMBOL
 5354 GType editor_get_type (void);
 5355 
 5356 G_DEFINE_BOXED_TYPE(GeanyEditor, editor, copy_, free_);