geany  1.38
About: Geany is a text editor (using GTK2) with basic features of an integrated development environment (syntax highlighting, code folding, symbol name auto-completion, ...). F: office T: editor programming GTK+ IDE
  Fossies Dox: geany-1.38.tar.bz2  ("unofficial" and yet experimental doxygen-generated source code documentation)  

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