1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
2 /*
3 * This file is part of GtkSourceView
4 *
5 * Copyright (C) 2013-2016 - Sébastien Wilmet <swilmet@gnome.org>
6 *
7 * GtkSourceView is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * GtkSourceView is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include "gtksourcesearchcontext.h"
26 #include <string.h>
27 #include "gtksourcesearchsettings.h"
28 #include "gtksourcebuffer.h"
29 #include "gtksourcebuffer-private.h"
30 #include "gtksourcebufferinternal.h"
31 #include "gtksourcestyle.h"
32 #include "gtksourcestylescheme.h"
33 #include "gtksourceutils.h"
34 #include "gtksourceregion.h"
35 #include "gtksourceiter.h"
36 #include "gtksource-enumtypes.h"
37
38 /**
39 * SECTION:searchcontext
40 * @Short_description: Search context
41 * @Title: GtkSourceSearchContext
42 * @See_also: #GtkSourceBuffer, #GtkSourceSearchSettings
43 *
44 * A #GtkSourceSearchContext is used for the search and replace in a
45 * #GtkSourceBuffer. The search settings are represented by a
46 * #GtkSourceSearchSettings object. There can be a many-to-many relationship
47 * between buffers and search settings, with the search contexts in-between: a
48 * search settings object can be shared between several search contexts; and a
49 * buffer can contain several search contexts at the same time.
50 *
51 * The total number of search occurrences can be retrieved with
52 * gtk_source_search_context_get_occurrences_count(). To know the position of a
53 * certain match, use gtk_source_search_context_get_occurrence_position().
54 *
55 * The buffer is scanned asynchronously, so it doesn't block the user interface.
56 * For each search, the buffer is scanned at most once. After that, navigating
57 * through the occurrences doesn't require to re-scan the buffer entirely.
58 *
59 * To search forward, use gtk_source_search_context_forward() or
60 * gtk_source_search_context_forward_async() for the asynchronous version.
61 * The backward search is done similarly. To replace a search match, or all
62 * matches, use gtk_source_search_context_replace() and
63 * gtk_source_search_context_replace_all().
64 *
65 * The search occurrences are highlighted by default. To disable it, use
66 * gtk_source_search_context_set_highlight(). You can enable the search
67 * highlighting for several #GtkSourceSearchContext<!-- -->s attached to the
68 * same buffer. Moreover, each of those #GtkSourceSearchContext<!-- -->s can
69 * have a different text style associated. Use
70 * gtk_source_search_context_set_match_style() to specify the #GtkSourceStyle
71 * to apply on search matches.
72 *
73 * Note that the #GtkSourceSearchContext:highlight and
74 * #GtkSourceSearchContext:match-style properties are in the
75 * #GtkSourceSearchContext class, not #GtkSourceSearchSettings. Appearance
76 * settings should be tied to one, and only one buffer, as different buffers can
77 * have different style scheme associated (a #GtkSourceSearchSettings object
78 * can be bound indirectly to several buffers).
79 *
80 * The concept of "current match" doesn't exist yet. A way to highlight
81 * differently the current match is to select it.
82 *
83 * A search occurrence's position doesn't depend on the cursor position or other
84 * parameters. Take for instance the buffer "aaaa" with the search text "aa".
85 * The two occurrences are at positions [0:2] and [2:4]. If you begin to search
86 * at position 1, you will get the occurrence [2:4], not [1:3]. This is a
87 * prerequisite for regular expression searches. The pattern ".*" matches the
88 * entire line. If the cursor is at the middle of the line, you don't want the
89 * rest of the line as the occurrence, you want an entire line. (As a side note,
90 * regular expression searches can also match multiple lines.)
91 *
92 * In the GtkSourceView source code, there is an example of how to use the
93 * search and replace API: see the tests/test-search.c file. It is a mini
94 * application for the search and replace, with a basic user interface.
95 */
96
97 /* Implementation overview:
98 *
99 * When the state of the search changes (the text to search or the options), we
100 * have to update the highlighting and the properties values (the number of
101 * occurrences). To do so, a simple solution is to first remove all the
102 * found_tag, so we have a clean buffer to analyze. The problem with this
103 * solution is that there is some flickering when the user modifies the text to
104 * search, because removing all the found_tag's can take some time. So we keep
105 * the old found_tag's, and when we must highlight the matches in a certain
106 * region, we first remove the found_tag's in this region and then we highlight
107 * the newly found matches by applying the found_tag to them.
108 *
109 * If we only want to highlight the matches, without counting the number of
110 * occurrences, a good solution would be to highlight only the visible region of
111 * the buffer on the screen. So it would be useless to always scan all the
112 * buffer.
113 *
114 * But we actually want the number of occurrences! So we have to scan all the
115 * buffer. When the state of the search changes, an idle callback is installed,
116 * which will scan the buffer to highlight the matches. To avoid flickering, the
117 * visible region on the screen is put in a higher priority region to highlight,
118 * so the idle callback will first scan this region.
119 *
120 * Why highlighting the non-visible matches? What we want is to (1) highlight
121 * the visible matches and (2) count the number of occurrences. The code would
122 * indeed be simpler if these two tasks were clearly separated (in two different
123 * idle callbacks, with different regions to scan). With this simpler solution,
124 * we would always use forward_search() and backward_search() to navigate
125 * through the occurrences. But we can do better than that!
126 * forward_to_tag_toggle() and backward_to_tag_toggle() are far more efficient:
127 * once the buffer has been scanned, going to the previous or the next
128 * occurrence is done in O(log n), with n the length of the buffer. We must just
129 * pay attention to contiguous matches.
130 *
131 * While the user is typing the text in the search entry, the buffer is scanned
132 * to count the number of occurrences. And when the user wants to do an
133 * operation (go to the next occurrence for example), chances are that the
134 * buffer has already been scanned entirely, so almost all the operations will
135 * be really fast.
136 *
137 * Extreme example:
138 * <occurrence> [1 GB of text] <next-occurrence>
139 * Once the buffer is scanned, switching between the occurrences will be almost
140 * instantaneous.
141 *
142 * So how to count the number of occurrences then? Remember that the buffer
143 * contents can be modified during the scan, and that we keep the old
144 * found_tag's. Moreover, when we encounter an old found_tag region, in the
145 * general case we can not say how many occurrences there are in this region,
146 * since a found_tag region can contain contiguous matches. Take for example the
147 * found_tag region "aa": was it the "aa" search match, or two times "a"?
148 * The implemented solution is to set occurrences_count to 0 when the search
149 * state changes, even if old matches are still there. Because it is not
150 * possible to count the old matches to decrement occurrences_count (and storing
151 * the previous search text would not be sufficient, because even older matches
152 * can still be there). To increment and decrement occurrences_count, there is
153 * the scan_region, the region to scan. If an occurrence is contained in
154 * scan_region, it means that it has not already been scanned, so
155 * occurrences_count doesn't take into account this occurrence. On the other
156 * hand, if we find an occurrence outside scan_region, the occurrence is
157 * normally correctly highlighted, and occurrences_count take it into account.
158 *
159 * So when we highlight or when we remove the highlight of an occurrence (on
160 * text insertion, deletion, when scanning, etc.), we increment or decrement
161 * occurrences_count depending on whether the occurrence was already taken into
162 * account by occurrences_count.
163 *
164 * If the code seems too complicated and contains strange bugs, you have two
165 * choices:
166 * - Write more unit tests, understand correctly the code and fix it.
167 * - Rewrite the code to implement the simpler solution explained above :-)
168 * But with the simpler solution, multiple-lines regex search matches (see
169 * below) would not be possible for going to the previous occurrence (or the
170 * buffer must always be scanned from the beginning).
171 *
172 * Known issue
173 * -----------
174 *
175 * Contiguous matches have a performance problem. In some cases it can lead to
176 * an O(N^2) time complexity. For example if the buffer is full of contiguous
177 * matches, and we want to navigate through all of them. If an iter is in the
178 * middle of a found_tag region, we don't know where are the nearest occurrence
179 * boundaries. Take for example the buffer "aaaa" with the search text "aa". The
180 * two occurrences are at positions [0:2] and [2:4]. If we begin to search at
181 * position 1, we can not take [1:3] as an occurrence. So what the code do is to
182 * go backward to the start of the found_tag region, and do a basic search
183 * inside the found_tag region to find the occurrence boundaries.
184 *
185 * So this is really a corner case, but it's better to be aware of that.
186 * To fix the problem, one solution would be to have two found_tag, and
187 * alternate them for contiguous matches.
188 */
189
190 /* Regex search:
191 *
192 * With a regex, we don't know how many lines a match can span. A regex will
193 * most probably match only one line, but a regex can contain something like
194 * "\n*", or the dot metacharacter can also match newlines, with the "?s" option
195 * (see G_REGEX_DOTALL).
196 * Therefore a simple solution is to always begin the search at the beginning of
197 * the document. Only the scan_region is taken into account for scanning the
198 * buffer.
199 *
200 * For non-regex searches, when there is an insertion or deletion in the buffer,
201 * we don't need to re-scan all the buffer. If there is an unmodified match in
202 * the neighborhood, no need to re-scan it (unless at_word_boundaries is set).
203 * For a regex search, it is more complicated. An insertion or deletion outside
204 * a match can modify a match located in the neighborhood. Take for example the
205 * regex "(aa)+" with the buffer contents "aaa". There is one occurrence: the
206 * first two letters. If we insert an extra 'a' at the end of the buffer, the
207 * occurrence is modified to take the next two letters. That's why the buffer
208 * is re-scanned entirely on each insertion or deletion in the buffer.
209 *
210 * For searching the matches, the easiest solution is to retrieve all the buffer
211 * contents, and search the occurrences on this big string. But it takes a lot
212 * of memory space. It is better to do multi-segment matching, also called
213 * incremental matching. See the pcrepartial(3) manpage. The matching is done
214 * segment by segment, with the G_REGEX_MATCH_PARTIAL_HARD flag (for reasons
215 * explained in the manpage). We begin by the first segment of the buffer as the
216 * subject string. If a partial match is returned, we append the next segment to
217 * the subject string, and we try again to find a complete match. When a
218 * complete match is returned, we must continue to search the next occurrences.
219 * The max lookbehind of the pattern must be retrieved. The start of the next
220 * subject string is located at max_lookbehind characters before the end of the
221 * previously found match. Similarly, if no match is found (neither a complete
222 * match nor a partial match), we take the next segment, with the last
223 * max_lookbehind characters from the previous segment.
224 *
225 * Improvement idea
226 * ----------------
227 *
228 * What we would like to support in applications is the incremental search:
229 * while we type the pattern, the buffer is scanned and the matches are
230 * highlighted. When the pattern is not fully typed, strange things can happen,
231 * including a pattern that match the entire buffer. And if the user is
232 * working on a really big file, catastrophe: the UI is blocked!
233 * To avoid this problem, a solution is to search the buffer differently
234 * depending on the situation:
235 * - First situation: the subject string to scan is small enough, we retrieve it
236 * and scan it directly.
237 * - Second situation: the subject string to scan is too big, it will take
238 * too much time to retrieve it and scan it directly. We handle this situation
239 * in three phases: (1) retrieving the subject string, chunks by chunks, in
240 * several idle loop iterations. (2) Once the subject string is retrieved
241 * completely, we launch the regex matching in a thread. (3) Once the thread
242 * is finished, we highlight the matches in the buffer. And voilà.
243 *
244 * But in practice, when trying a pattern that match the entire buffer, we
245 * quickly get an error like:
246 *
247 * Regex matching error: Error while matching regular expression (.*\n)*:
248 * recursion limit reached
249 *
250 * It happens with test-search, with this file as the buffer (~3500 lines).
251 *
252 * Improvement idea
253 * ----------------
254 *
255 * GRegex currently doesn't support JIT pattern compilation:
256 * https://bugzilla.gnome.org/show_bug.cgi?id=679155
257 *
258 * Once available, it can be a nice performance improvement (but it must be
259 * measured, since g_regex_new() is slower with JIT enabled).
260 *
261 * Known issue
262 * -----------
263 *
264 * To search at word boundaries, \b is added at the beginning and at the
265 * end of the pattern. But \b is not the same as
266 * _gtk_source_iter_starts_extra_natural_word() and
267 * _gtk_source_iter_ends_extra_natural_word(). \b for
268 * example doesn't take the underscore as a word boundary.
269 * Using _gtk_source_iter_starts_extra_natural_word() and ends_word() for regex searches
270 * is not easily possible: if the GRegex returns a match, but doesn't
271 * start and end a word, maybe a shorter match (for a greedy pattern)
272 * start and end a word, or a longer match (for an ungreedy pattern). To
273 * be able to use the _gtk_source_iter_starts_extra_natural_word() and ends_word()
274 * functions for regex search, g_regex_match_all_full() must be used, to
275 * retrieve _all_ matches, and test the word boundaries until a match is
276 * found at word boundaries.
277 */
278
279 /*
280 #define ENABLE_DEBUG
281 */
282 #undef ENABLE_DEBUG
283
284 #ifdef ENABLE_DEBUG
285 #define DEBUG(x) (x)
286 #else
287 #define DEBUG(x)
288 #endif
289
290 /* Maximum number of lines to scan in one batch.
291 * A lower value means more overhead when scanning the buffer asynchronously.
292 */
293 #define SCAN_BATCH_SIZE 100
294
295 enum
296 {
297 PROP_0,
298 PROP_BUFFER,
299 PROP_SETTINGS,
300 PROP_HIGHLIGHT,
301 PROP_MATCH_STYLE,
302 PROP_OCCURRENCES_COUNT,
303 PROP_REGEX_ERROR
304 };
305
306 struct _GtkSourceSearchContextPrivate
307 {
308 /* Weak ref to the buffer. The buffer has also a weak ref to the search
309 * context. A strong ref in either direction would prevent the pointed
310 * object to be finalized.
311 */
312 GtkTextBuffer *buffer;
313
314 GtkSourceSearchSettings *settings;
315
316 /* The tag to apply to search occurrences. Even if the highlighting is
317 * disabled, the tag is applied.
318 */
319 GtkTextTag *found_tag;
320
321 /* A reference to the tag table where the found_tag is added. The sole
322 * purpose is to remove the found_tag in dispose(). We can not rely on
323 * 'buffer' since it is a weak reference.
324 */
325 GtkTextTagTable *tag_table;
326
327 /* The region to scan and highlight. If NULL, the scan is finished. */
328 GtkSourceRegion *scan_region;
329
330 /* The region to scan and highlight in priority. I.e. the visible part
331 * of the buffer on the screen.
332 */
333 GtkSourceRegion *high_priority_region;
334
335 /* An asynchronous running task. task_region has a higher priority than
336 * scan_region, but a lower priority than high_priority_region.
337 */
338 GTask *task;
339 GtkSourceRegion *task_region;
340
341 /* If the regex search is disabled, text_nb_lines is the number of lines
342 * of the search text. It is useful to adjust the region to scan.
343 */
344 gint text_nb_lines;
345
346 GRegex *regex;
347 GError *regex_error;
348
349 gint occurrences_count;
350 gulong idle_scan_id;
351
352 GtkSourceStyle *match_style;
353 guint highlight : 1;
354 };
355
356 /* Data for the asynchronous forward and backward search tasks. */
357 typedef struct
358 {
359 GtkTextMark *start_at;
360 GtkTextMark *match_start;
361 GtkTextMark *match_end;
362 guint found : 1;
363 guint wrapped_around : 1;
364
365 /* forward or backward */
366 guint is_forward : 1;
367 } ForwardBackwardData;
368
369 G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceSearchContext, gtk_source_search_context, G_TYPE_OBJECT);
370
371 static void install_idle_scan (GtkSourceSearchContext *search);
372
373 #ifdef ENABLE_DEBUG
374 static void
print_region(GtkSourceRegion * region)375 print_region (GtkSourceRegion *region)
376 {
377 gchar *str;
378
379 str = gtk_source_region_to_string (region);
380 g_print ("%s\n", str);
381 g_free (str);
382 }
383 #endif
384
385 static void
sync_found_tag(GtkSourceSearchContext * search)386 sync_found_tag (GtkSourceSearchContext *search)
387 {
388 GtkSourceStyle *style = search->priv->match_style;
389 GtkSourceStyleScheme *style_scheme;
390
391 if (search->priv->buffer == NULL)
392 {
393 return;
394 }
395
396 if (!search->priv->highlight)
397 {
398 gtk_source_style_apply (NULL, search->priv->found_tag);
399 return;
400 }
401
402 if (style == NULL)
403 {
404 style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (search->priv->buffer));
405
406 if (style_scheme != NULL)
407 {
408 style = gtk_source_style_scheme_get_style (style_scheme, "search-match");
409 }
410 }
411
412 if (style == NULL)
413 {
414 g_warning ("No match style defined nor 'search-match' style available.");
415 }
416
417 gtk_source_style_apply (style, search->priv->found_tag);
418 }
419
420 static void
text_tag_set_highest_priority(GtkTextTag * tag,GtkTextBuffer * buffer)421 text_tag_set_highest_priority (GtkTextTag *tag,
422 GtkTextBuffer *buffer)
423 {
424 GtkTextTagTable *table;
425 gint n;
426
427 table = gtk_text_buffer_get_tag_table (buffer);
428 n = gtk_text_tag_table_get_size (table);
429 gtk_text_tag_set_priority (tag, n - 1);
430 }
431
432 /* Sets @start and @end to the first non-empty subregion.
433 * Returns FALSE if the region is empty.
434 */
435 static gboolean
get_first_subregion(GtkSourceRegion * region,GtkTextIter * start,GtkTextIter * end)436 get_first_subregion (GtkSourceRegion *region,
437 GtkTextIter *start,
438 GtkTextIter *end)
439 {
440 GtkSourceRegionIter region_iter;
441
442 if (region == NULL)
443 {
444 return FALSE;
445 }
446
447 gtk_source_region_get_start_region_iter (region, ®ion_iter);
448
449 while (!gtk_source_region_iter_is_end (®ion_iter))
450 {
451 if (!gtk_source_region_iter_get_subregion (®ion_iter, start, end))
452 {
453 return FALSE;
454 }
455
456 if (!gtk_text_iter_equal (start, end))
457 {
458 return TRUE;
459 }
460
461 gtk_source_region_iter_next (®ion_iter);
462 }
463
464 return FALSE;
465 }
466
467 /* Sets @start and @end to the last non-empty subregion.
468 * Returns FALSE if the region is empty.
469 */
470 static gboolean
get_last_subregion(GtkSourceRegion * region,GtkTextIter * start,GtkTextIter * end)471 get_last_subregion (GtkSourceRegion *region,
472 GtkTextIter *start,
473 GtkTextIter *end)
474 {
475 GtkSourceRegionIter region_iter;
476 gboolean found = FALSE;
477
478 if (region == NULL)
479 {
480 return FALSE;
481 }
482
483 gtk_source_region_get_start_region_iter (region, ®ion_iter);
484
485 while (!gtk_source_region_iter_is_end (®ion_iter))
486 {
487 GtkTextIter start_subregion;
488 GtkTextIter end_subregion;
489
490 if (!gtk_source_region_iter_get_subregion (®ion_iter,
491 &start_subregion,
492 &end_subregion))
493 {
494 return FALSE;
495 }
496
497 if (!gtk_text_iter_equal (&start_subregion, &end_subregion))
498 {
499 found = TRUE;
500 *start = start_subregion;
501 *end = end_subregion;
502 }
503
504 gtk_source_region_iter_next (®ion_iter);
505 }
506
507 return found;
508 }
509
510 static void
clear_task(GtkSourceSearchContext * search)511 clear_task (GtkSourceSearchContext *search)
512 {
513 g_clear_object (&search->priv->task_region);
514
515 if (search->priv->task != NULL)
516 {
517 GCancellable *cancellable = g_task_get_cancellable (search->priv->task);
518
519 if (cancellable != NULL)
520 {
521 g_cancellable_cancel (cancellable);
522 g_task_return_error_if_cancelled (search->priv->task);
523 }
524
525 g_clear_object (&search->priv->task);
526 }
527 }
528
529 static void
clear_search(GtkSourceSearchContext * search)530 clear_search (GtkSourceSearchContext *search)
531 {
532 g_clear_object (&search->priv->scan_region);
533 g_clear_object (&search->priv->high_priority_region);
534
535 if (search->priv->idle_scan_id != 0)
536 {
537 g_source_remove (search->priv->idle_scan_id);
538 search->priv->idle_scan_id = 0;
539 }
540
541 if (search->priv->regex_error != NULL)
542 {
543 g_clear_error (&search->priv->regex_error);
544 g_object_notify (G_OBJECT (search), "regex-error");
545 }
546
547 clear_task (search);
548
549 search->priv->occurrences_count = 0;
550 }
551
552 static GtkTextSearchFlags
get_text_search_flags(GtkSourceSearchContext * search)553 get_text_search_flags (GtkSourceSearchContext *search)
554 {
555 GtkTextSearchFlags flags = GTK_TEXT_SEARCH_TEXT_ONLY | GTK_TEXT_SEARCH_VISIBLE_ONLY;
556
557 if (!gtk_source_search_settings_get_case_sensitive (search->priv->settings))
558 {
559 flags |= GTK_TEXT_SEARCH_CASE_INSENSITIVE;
560 }
561
562 return flags;
563 }
564
565 /* @start_pos is in bytes. */
566 static void
regex_search_get_real_start(GtkSourceSearchContext * search,const GtkTextIter * start,GtkTextIter * real_start,gint * start_pos)567 regex_search_get_real_start (GtkSourceSearchContext *search,
568 const GtkTextIter *start,
569 GtkTextIter *real_start,
570 gint *start_pos)
571 {
572 gint max_lookbehind = g_regex_get_max_lookbehind (search->priv->regex);
573 gint i;
574 gchar *text;
575
576 *real_start = *start;
577
578 for (i = 0; i < max_lookbehind; i++)
579 {
580 if (!gtk_text_iter_backward_char (real_start))
581 {
582 break;
583 }
584 }
585
586 text = gtk_text_iter_get_visible_text (real_start, start);
587 *start_pos = strlen (text);
588
589 g_free (text);
590 }
591
592 static GRegexMatchFlags
regex_search_get_match_options(const GtkTextIter * real_start,const GtkTextIter * end)593 regex_search_get_match_options (const GtkTextIter *real_start,
594 const GtkTextIter *end)
595 {
596 GRegexMatchFlags match_options = 0;
597
598 if (!gtk_text_iter_starts_line (real_start))
599 {
600 match_options |= G_REGEX_MATCH_NOTBOL;
601 }
602
603 if (!gtk_text_iter_ends_line (end))
604 {
605 match_options |= G_REGEX_MATCH_NOTEOL;
606 }
607
608 if (!gtk_text_iter_is_end (end))
609 {
610 match_options |= G_REGEX_MATCH_PARTIAL_HARD;
611 }
612
613 return match_options;
614 }
615
616 /* Get the @match_start and @match_end iters of the @match_info.
617 * g_match_info_fetch_pos() returns byte positions. To get the iters, we need to
618 * know the number of UTF-8 characters. A GMatchInfo can contain several matches
619 * (with g_match_info_next()). So instead of calling g_utf8_strlen() each time
620 * at the beginning of @subject, @iter and @iter_byte_pos are used to remember
621 * where g_utf8_strlen() stopped.
622 */
623 static gboolean
regex_search_fetch_match(GMatchInfo * match_info,const gchar * subject,gssize subject_length,GtkTextIter * iter,gint * iter_byte_pos,GtkTextIter * match_start,GtkTextIter * match_end)624 regex_search_fetch_match (GMatchInfo *match_info,
625 const gchar *subject,
626 gssize subject_length,
627 GtkTextIter *iter,
628 gint *iter_byte_pos,
629 GtkTextIter *match_start,
630 GtkTextIter *match_end)
631 {
632 gint start_byte_pos;
633 gint end_byte_pos;
634 gint nb_chars;
635
636 g_assert (*iter_byte_pos <= subject_length);
637 g_assert (match_start != NULL);
638 g_assert (match_end != NULL);
639
640 if (!g_match_info_matches (match_info))
641 {
642 return FALSE;
643 }
644
645 if (!g_match_info_fetch_pos (match_info, 0, &start_byte_pos, &end_byte_pos))
646 {
647 g_warning ("Impossible to fetch regex match position.");
648 return FALSE;
649 }
650
651 g_assert (start_byte_pos < subject_length);
652 g_assert (end_byte_pos <= subject_length);
653 g_assert (*iter_byte_pos <= start_byte_pos);
654 g_assert (start_byte_pos < end_byte_pos);
655
656 nb_chars = g_utf8_strlen (subject + *iter_byte_pos,
657 start_byte_pos - *iter_byte_pos);
658
659 *match_start = *iter;
660 gtk_text_iter_forward_chars (match_start, nb_chars);
661
662 nb_chars = g_utf8_strlen (subject + start_byte_pos,
663 end_byte_pos - start_byte_pos);
664
665 *match_end = *match_start;
666 gtk_text_iter_forward_chars (match_end, nb_chars);
667
668 *iter = *match_end;
669 *iter_byte_pos = end_byte_pos;
670
671 return TRUE;
672 }
673
674 /* If you retrieve only [match_start, match_end] from the GtkTextBuffer, it
675 * does not match the regex if the regex contains look-ahead assertions. For
676 * that, get the @real_end. Note that [match_start, real_end] is not the minimum
677 * amount of text that still matches the regex, it can contain several
678 * occurrences, so you can add the G_REGEX_MATCH_ANCHORED option to match only
679 * the first occurrence.
680 * Note that @limit is the limit for @match_end, not @real_end.
681 */
682 static gboolean
basic_forward_regex_search(GtkSourceSearchContext * search,const GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end,GtkTextIter * real_end,const GtkTextIter * limit)683 basic_forward_regex_search (GtkSourceSearchContext *search,
684 const GtkTextIter *start_at,
685 GtkTextIter *match_start,
686 GtkTextIter *match_end,
687 GtkTextIter *real_end,
688 const GtkTextIter *limit)
689 {
690 GtkTextIter real_start;
691 GtkTextIter end;
692 gint start_pos;
693 gboolean found = FALSE;
694 gint nb_lines = 1;
695
696 if (search->priv->regex == NULL ||
697 search->priv->regex_error != NULL)
698 {
699 return FALSE;
700 }
701
702 regex_search_get_real_start (search, start_at, &real_start, &start_pos);
703
704 if (limit == NULL)
705 {
706 gtk_text_buffer_get_end_iter (search->priv->buffer, &end);
707 }
708 else
709 {
710 end = *limit;
711 }
712
713 while (TRUE)
714 {
715 GRegexMatchFlags match_options;
716 gchar *subject;
717 gssize subject_length;
718 GMatchInfo *match_info;
719 GtkTextIter iter;
720 gint iter_byte_pos;
721 GtkTextIter m_start;
722 GtkTextIter m_end;
723
724 match_options = regex_search_get_match_options (&real_start, &end);
725 subject = gtk_text_iter_get_visible_text (&real_start, &end);
726 subject_length = strlen (subject);
727
728 g_regex_match_full (search->priv->regex,
729 subject,
730 subject_length,
731 start_pos,
732 match_options,
733 &match_info,
734 &search->priv->regex_error);
735
736 iter = real_start;
737 iter_byte_pos = 0;
738
739 found = regex_search_fetch_match (match_info,
740 subject,
741 subject_length,
742 &iter,
743 &iter_byte_pos,
744 &m_start,
745 &m_end);
746
747 if (!found && g_match_info_is_partial_match (match_info))
748 {
749 gtk_text_iter_forward_lines (&end, nb_lines);
750 nb_lines <<= 1;
751
752 g_free (subject);
753 g_match_info_free (match_info);
754 continue;
755 }
756
757 /* Check that the match is not beyond the limit. This can happen
758 * if a partial match is found on the first iteration. Then the
759 * partial match was actually not a good match, but a second
760 * good match is found.
761 */
762 if (found && limit != NULL && gtk_text_iter_compare (limit, &m_end) < 0)
763 {
764 found = FALSE;
765 }
766
767 if (search->priv->regex_error != NULL)
768 {
769 g_object_notify (G_OBJECT (search), "regex-error");
770 found = FALSE;
771 }
772
773 if (found)
774 {
775 if (match_start != NULL)
776 {
777 *match_start = m_start;
778 }
779
780 if (match_end != NULL)
781 {
782 *match_end = m_end;
783 }
784
785 if (real_end != NULL)
786 {
787 *real_end = end;
788 }
789 }
790
791 g_free (subject);
792 g_match_info_free (match_info);
793 break;
794 }
795
796 return found;
797 }
798
799 static gboolean
basic_forward_search(GtkSourceSearchContext * search,const GtkTextIter * iter,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit)800 basic_forward_search (GtkSourceSearchContext *search,
801 const GtkTextIter *iter,
802 GtkTextIter *match_start,
803 GtkTextIter *match_end,
804 const GtkTextIter *limit)
805 {
806 GtkTextIter begin_search = *iter;
807 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
808 GtkTextSearchFlags flags;
809
810 if (search_text == NULL)
811 {
812 return FALSE;
813 }
814
815 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
816 {
817 return basic_forward_regex_search (search,
818 iter,
819 match_start,
820 match_end,
821 NULL,
822 limit);
823 }
824
825 flags = get_text_search_flags (search);
826
827 while (TRUE)
828 {
829 gboolean found = gtk_text_iter_forward_search (&begin_search,
830 search_text,
831 flags,
832 match_start,
833 match_end,
834 limit);
835
836 if (!found || !gtk_source_search_settings_get_at_word_boundaries (search->priv->settings))
837 {
838 return found;
839 }
840
841 if (_gtk_source_iter_starts_extra_natural_word (match_start, FALSE) &&
842 _gtk_source_iter_ends_extra_natural_word (match_end, FALSE))
843 {
844 return TRUE;
845 }
846
847 begin_search = *match_end;
848 }
849 }
850
851 /* We fake the backward regex search by doing a forward search, and taking the
852 * last match.
853 */
854 static gboolean
basic_backward_regex_search(GtkSourceSearchContext * search,const GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit)855 basic_backward_regex_search (GtkSourceSearchContext *search,
856 const GtkTextIter *start_at,
857 GtkTextIter *match_start,
858 GtkTextIter *match_end,
859 const GtkTextIter *limit)
860 {
861 GtkTextIter lower_bound;
862 GtkTextIter m_start;
863 GtkTextIter m_end;
864 gboolean found = FALSE;
865
866 if (search->priv->regex == NULL ||
867 search->priv->regex_error != NULL)
868 {
869 return FALSE;
870 }
871
872 if (limit == NULL)
873 {
874 gtk_text_buffer_get_start_iter (search->priv->buffer, &lower_bound);
875 }
876 else
877 {
878 lower_bound = *limit;
879 }
880
881 while (basic_forward_regex_search (search, &lower_bound, &m_start, &m_end, NULL, start_at))
882 {
883 found = TRUE;
884
885 if (match_start != NULL)
886 {
887 *match_start = m_start;
888 }
889
890 if (match_end != NULL)
891 {
892 *match_end = m_end;
893 }
894
895 lower_bound = m_end;
896 }
897
898 return found;
899 }
900
901 static gboolean
basic_backward_search(GtkSourceSearchContext * search,const GtkTextIter * iter,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit)902 basic_backward_search (GtkSourceSearchContext *search,
903 const GtkTextIter *iter,
904 GtkTextIter *match_start,
905 GtkTextIter *match_end,
906 const GtkTextIter *limit)
907 {
908 GtkTextIter begin_search = *iter;
909 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
910 GtkTextSearchFlags flags;
911
912 if (search_text == NULL)
913 {
914 return FALSE;
915 }
916
917 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
918 {
919 return basic_backward_regex_search (search,
920 iter,
921 match_start,
922 match_end,
923 limit);
924 }
925
926 flags = get_text_search_flags (search);
927
928 while (TRUE)
929 {
930 gboolean found = gtk_text_iter_backward_search (&begin_search,
931 search_text,
932 flags,
933 match_start,
934 match_end,
935 limit);
936
937 if (!found || !gtk_source_search_settings_get_at_word_boundaries (search->priv->settings))
938 {
939 return found;
940 }
941
942 if (_gtk_source_iter_starts_extra_natural_word (match_start, FALSE) &&
943 _gtk_source_iter_ends_extra_natural_word (match_end, FALSE))
944 {
945 return TRUE;
946 }
947
948 begin_search = *match_start;
949 }
950 }
951
952 static void
forward_backward_data_free(ForwardBackwardData * data)953 forward_backward_data_free (ForwardBackwardData *data)
954 {
955 if (data->start_at != NULL)
956 {
957 GtkTextBuffer *buffer = gtk_text_mark_get_buffer (data->start_at);
958 gtk_text_buffer_delete_mark (buffer, data->start_at);
959 }
960
961 if (data->match_start != NULL)
962 {
963 GtkTextBuffer *buffer = gtk_text_mark_get_buffer (data->match_start);
964 gtk_text_buffer_delete_mark (buffer, data->match_start);
965 }
966
967 if (data->match_end != NULL)
968 {
969 GtkTextBuffer *buffer = gtk_text_mark_get_buffer (data->match_end);
970 gtk_text_buffer_delete_mark (buffer, data->match_end);
971 }
972
973 g_slice_free (ForwardBackwardData, data);
974 }
975
976 /* Returns TRUE if finished. */
977 static gboolean
smart_forward_search_async_step(GtkSourceSearchContext * search,GtkTextIter * start_at,gboolean * wrapped_around)978 smart_forward_search_async_step (GtkSourceSearchContext *search,
979 GtkTextIter *start_at,
980 gboolean *wrapped_around)
981 {
982 GtkTextIter iter = *start_at;
983 GtkTextIter limit;
984 GtkTextIter region_start = *start_at;
985 GtkSourceRegion *region = NULL;
986 ForwardBackwardData *task_data;
987 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
988
989 if (gtk_text_iter_is_end (start_at))
990 {
991 if (search_text != NULL &&
992 !*wrapped_around &&
993 gtk_source_search_settings_get_wrap_around (search->priv->settings))
994 {
995 gtk_text_buffer_get_start_iter (search->priv->buffer, start_at);
996 *wrapped_around = TRUE;
997 return FALSE;
998 }
999
1000 task_data = g_slice_new0 (ForwardBackwardData);
1001 task_data->found = FALSE;
1002 task_data->is_forward = TRUE;
1003 task_data->wrapped_around = *wrapped_around;
1004
1005 g_task_return_pointer (search->priv->task,
1006 task_data,
1007 (GDestroyNotify)forward_backward_data_free);
1008
1009 g_clear_object (&search->priv->task);
1010 return TRUE;
1011 }
1012
1013 if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
1014 {
1015 gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
1016 }
1017 else if (!gtk_text_iter_starts_tag (&iter, search->priv->found_tag))
1018 {
1019 gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
1020 region_start = iter;
1021 }
1022
1023 limit = iter;
1024 gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
1025
1026 if (search->priv->scan_region != NULL)
1027 {
1028 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
1029 ®ion_start,
1030 &limit);
1031 }
1032
1033 if (gtk_source_region_is_empty (region))
1034 {
1035 GtkTextIter match_start;
1036 GtkTextIter match_end;
1037
1038 g_clear_object (®ion);
1039
1040 while (basic_forward_search (search, &iter, &match_start, &match_end, &limit))
1041 {
1042 if (gtk_text_iter_compare (&match_start, start_at) < 0)
1043 {
1044 iter = match_end;
1045 continue;
1046 }
1047
1048 task_data = g_slice_new0 (ForwardBackwardData);
1049 task_data->found = TRUE;
1050 task_data->match_start =
1051 gtk_text_buffer_create_mark (search->priv->buffer,
1052 NULL,
1053 &match_start,
1054 TRUE);
1055 task_data->match_end =
1056 gtk_text_buffer_create_mark (search->priv->buffer,
1057 NULL,
1058 &match_end,
1059 FALSE);
1060 task_data->is_forward = TRUE;
1061 task_data->wrapped_around = *wrapped_around;
1062
1063 g_task_return_pointer (search->priv->task,
1064 task_data,
1065 (GDestroyNotify)forward_backward_data_free);
1066
1067 g_clear_object (&search->priv->task);
1068 return TRUE;
1069 }
1070
1071 *start_at = limit;
1072 return FALSE;
1073 }
1074
1075 task_data = g_slice_new0 (ForwardBackwardData);
1076 task_data->is_forward = TRUE;
1077 task_data->wrapped_around = *wrapped_around;
1078 task_data->start_at = gtk_text_buffer_create_mark (search->priv->buffer,
1079 NULL,
1080 start_at,
1081 TRUE);
1082
1083 g_task_set_task_data (search->priv->task,
1084 task_data,
1085 (GDestroyNotify)forward_backward_data_free);
1086
1087 g_clear_object (&search->priv->task_region);
1088 search->priv->task_region = region;
1089
1090 install_idle_scan (search);
1091
1092 /* The idle that scan the task region will call
1093 * smart_forward_search_async() to continue the task. But for the
1094 * moment, we are done.
1095 */
1096 return TRUE;
1097 }
1098
1099 static void
smart_forward_search_async(GtkSourceSearchContext * search,const GtkTextIter * start_at,gboolean wrapped_around)1100 smart_forward_search_async (GtkSourceSearchContext *search,
1101 const GtkTextIter *start_at,
1102 gboolean wrapped_around)
1103 {
1104 GtkTextIter iter = *start_at;
1105
1106 /* A recursive function would have been more natural, but a loop is
1107 * better to avoid stack overflows.
1108 */
1109 while (!smart_forward_search_async_step (search, &iter, &wrapped_around));
1110 }
1111
1112
1113 /* Returns TRUE if finished. */
1114 static gboolean
smart_backward_search_async_step(GtkSourceSearchContext * search,GtkTextIter * start_at,gboolean * wrapped_around)1115 smart_backward_search_async_step (GtkSourceSearchContext *search,
1116 GtkTextIter *start_at,
1117 gboolean *wrapped_around)
1118 {
1119 GtkTextIter iter = *start_at;
1120 GtkTextIter limit;
1121 GtkTextIter region_end = *start_at;
1122 GtkSourceRegion *region = NULL;
1123 ForwardBackwardData *task_data;
1124 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
1125
1126 if (gtk_text_iter_is_start (start_at))
1127 {
1128 if (search_text != NULL &&
1129 !*wrapped_around &&
1130 gtk_source_search_settings_get_wrap_around (search->priv->settings))
1131 {
1132 gtk_text_buffer_get_end_iter (search->priv->buffer, start_at);
1133 *wrapped_around = TRUE;
1134 return FALSE;
1135 }
1136
1137 task_data = g_slice_new0 (ForwardBackwardData);
1138 task_data->found = FALSE;
1139 task_data->is_forward = FALSE;
1140 task_data->wrapped_around = *wrapped_around;
1141
1142 g_task_return_pointer (search->priv->task,
1143 task_data,
1144 (GDestroyNotify)forward_backward_data_free);
1145
1146 g_clear_object (&search->priv->task);
1147 return TRUE;
1148 }
1149
1150 if (gtk_text_iter_starts_tag (&iter, search->priv->found_tag) ||
1151 (!gtk_text_iter_has_tag (&iter, search->priv->found_tag) &&
1152 !gtk_text_iter_ends_tag (&iter, search->priv->found_tag)))
1153 {
1154 gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
1155 }
1156 else if (gtk_text_iter_has_tag (&iter, search->priv->found_tag))
1157 {
1158 gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
1159 region_end = iter;
1160 }
1161
1162 limit = iter;
1163 gtk_text_iter_backward_to_tag_toggle (&limit, search->priv->found_tag);
1164
1165 if (search->priv->scan_region != NULL)
1166 {
1167 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
1168 &limit,
1169 ®ion_end);
1170 }
1171
1172 if (gtk_source_region_is_empty (region))
1173 {
1174 GtkTextIter match_start;
1175 GtkTextIter match_end;
1176
1177 g_clear_object (®ion);
1178
1179 while (basic_backward_search (search, &iter, &match_start, &match_end, &limit))
1180 {
1181 if (gtk_text_iter_compare (start_at, &match_end) < 0)
1182 {
1183 iter = match_start;
1184 continue;
1185 }
1186
1187 task_data = g_slice_new0 (ForwardBackwardData);
1188 task_data->found = TRUE;
1189 task_data->match_start =
1190 gtk_text_buffer_create_mark (search->priv->buffer,
1191 NULL,
1192 &match_start,
1193 TRUE);
1194 task_data->match_end =
1195 gtk_text_buffer_create_mark (search->priv->buffer,
1196 NULL,
1197 &match_end,
1198 FALSE);
1199 task_data->is_forward = FALSE;
1200 task_data->wrapped_around = *wrapped_around;
1201
1202 g_task_return_pointer (search->priv->task,
1203 task_data,
1204 (GDestroyNotify)forward_backward_data_free);
1205
1206 g_clear_object (&search->priv->task);
1207 return TRUE;
1208 }
1209
1210 *start_at = limit;
1211 return FALSE;
1212 }
1213
1214 task_data = g_slice_new0 (ForwardBackwardData);
1215 task_data->is_forward = FALSE;
1216 task_data->wrapped_around = *wrapped_around;
1217 task_data->start_at = gtk_text_buffer_create_mark (search->priv->buffer,
1218 NULL,
1219 start_at,
1220 TRUE);
1221
1222 g_task_set_task_data (search->priv->task,
1223 task_data,
1224 (GDestroyNotify)forward_backward_data_free);
1225
1226 g_clear_object (&search->priv->task_region);
1227 search->priv->task_region = region;
1228
1229 install_idle_scan (search);
1230
1231 /* The idle that scan the task region will call
1232 * smart_backward_search_async() to continue the task. But for the
1233 * moment, we are done.
1234 */
1235 return TRUE;
1236 }
1237
1238 static void
smart_backward_search_async(GtkSourceSearchContext * search,const GtkTextIter * start_at,gboolean wrapped_around)1239 smart_backward_search_async (GtkSourceSearchContext *search,
1240 const GtkTextIter *start_at,
1241 gboolean wrapped_around)
1242 {
1243 GtkTextIter iter = *start_at;
1244
1245 /* A recursive function would have been more natural, but a loop is
1246 * better to avoid stack overflows.
1247 */
1248 while (!smart_backward_search_async_step (search, &iter, &wrapped_around));
1249 }
1250
1251 /* Adjust the subregion so we are sure that all matches that are visible or
1252 * partially visible between @start and @end are highlighted.
1253 */
1254 static void
adjust_subregion(GtkSourceSearchContext * search,GtkTextIter * start,GtkTextIter * end)1255 adjust_subregion (GtkSourceSearchContext *search,
1256 GtkTextIter *start,
1257 GtkTextIter *end)
1258 {
1259 DEBUG ({
1260 g_print ("adjust_subregion(), before adjusting: [%u (%u), %u (%u)]\n",
1261 gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
1262 gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
1263 });
1264
1265 gtk_text_iter_backward_lines (start, MAX (0, search->priv->text_nb_lines - 1));
1266 gtk_text_iter_forward_lines (end, MAX (0, search->priv->text_nb_lines - 1));
1267
1268 if (!gtk_text_iter_starts_line (start))
1269 {
1270 gtk_text_iter_set_line_offset (start, 0);
1271 }
1272
1273 if (!gtk_text_iter_ends_line (end))
1274 {
1275 gtk_text_iter_forward_to_line_end (end);
1276 }
1277
1278 /* When we are in the middle of a found_tag, a simple solution is to
1279 * always backward_to_tag_toggle(). The problem is that occurrences can
1280 * be contiguous. So a full scan of the buffer can have a O(n^2) in the
1281 * worst case, if we use the simple solution. Therefore we use a more
1282 * complicated solution, that checks if we are in an old found_tag or
1283 * not.
1284 */
1285
1286 if (gtk_text_iter_has_tag (start, search->priv->found_tag))
1287 {
1288 if (gtk_source_region_is_empty (search->priv->scan_region))
1289 {
1290 /* 'start' is in a correct match, we can skip it. */
1291 gtk_text_iter_forward_to_tag_toggle (start, search->priv->found_tag);
1292 }
1293 else
1294 {
1295 GtkTextIter tag_start = *start;
1296 GtkTextIter tag_end = *start;
1297 GtkSourceRegion *region;
1298
1299 if (!gtk_text_iter_starts_tag (&tag_start, search->priv->found_tag))
1300 {
1301 gtk_text_iter_backward_to_tag_toggle (&tag_start, search->priv->found_tag);
1302 }
1303
1304 gtk_text_iter_forward_to_tag_toggle (&tag_end, search->priv->found_tag);
1305
1306 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
1307 &tag_start,
1308 &tag_end);
1309
1310 if (gtk_source_region_is_empty (region))
1311 {
1312 /* 'region' has already been scanned, so 'start' is in a
1313 * correct match, we can skip it.
1314 */
1315 *start = tag_end;
1316 }
1317 else
1318 {
1319 /* 'region' has not already been scanned completely, so
1320 * 'start' is most probably in an old match that must be
1321 * removed.
1322 */
1323 *start = tag_start;
1324 }
1325
1326 g_clear_object (®ion);
1327 }
1328 }
1329
1330 /* Symmetric for 'end'. */
1331
1332 if (gtk_text_iter_has_tag (end, search->priv->found_tag))
1333 {
1334 if (gtk_source_region_is_empty (search->priv->scan_region))
1335 {
1336 /* 'end' is in a correct match, we can skip it. */
1337
1338 if (!gtk_text_iter_starts_tag (end, search->priv->found_tag))
1339 {
1340 gtk_text_iter_backward_to_tag_toggle (end, search->priv->found_tag);
1341 }
1342 }
1343 else
1344 {
1345 GtkTextIter tag_start = *end;
1346 GtkTextIter tag_end = *end;
1347 GtkSourceRegion *region;
1348
1349 if (!gtk_text_iter_starts_tag (&tag_start, search->priv->found_tag))
1350 {
1351 gtk_text_iter_backward_to_tag_toggle (&tag_start, search->priv->found_tag);
1352 }
1353
1354 gtk_text_iter_forward_to_tag_toggle (&tag_end, search->priv->found_tag);
1355
1356 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
1357 &tag_start,
1358 &tag_end);
1359
1360 if (gtk_source_region_is_empty (region))
1361 {
1362 /* 'region' has already been scanned, so 'end' is in a
1363 * correct match, we can skip it.
1364 */
1365 *end = tag_start;
1366 }
1367 else
1368 {
1369 /* 'region' has not already been scanned completely, so
1370 * 'end' is most probably in an old match that must be
1371 * removed.
1372 */
1373 *end = tag_end;
1374 }
1375
1376 g_clear_object (®ion);
1377 }
1378 }
1379
1380 DEBUG ({
1381 g_print ("adjust_subregion(), after adjusting: [%u (%u), %u (%u)]\n",
1382 gtk_text_iter_get_line (start), gtk_text_iter_get_offset (start),
1383 gtk_text_iter_get_line (end), gtk_text_iter_get_offset (end));
1384 });
1385 }
1386
1387 /* Do not take into account the scan_region. Take the result with a grain of
1388 * salt. You should verify before or after calling this function that the
1389 * region has been scanned, to be sure that the returned occurrence is correct.
1390 */
1391 static gboolean
smart_forward_search_without_scanning(GtkSourceSearchContext * search,const GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * stop_at)1392 smart_forward_search_without_scanning (GtkSourceSearchContext *search,
1393 const GtkTextIter *start_at,
1394 GtkTextIter *match_start,
1395 GtkTextIter *match_end,
1396 const GtkTextIter *stop_at)
1397 {
1398 GtkTextIter iter;
1399 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
1400
1401 g_assert (start_at != NULL);
1402 g_assert (match_start != NULL);
1403 g_assert (match_end != NULL);
1404 g_assert (stop_at != NULL);
1405
1406 iter = *start_at;
1407
1408 if (search_text == NULL)
1409 {
1410 return FALSE;
1411 }
1412
1413 while (gtk_text_iter_compare (&iter, stop_at) < 0)
1414 {
1415 GtkTextIter limit;
1416
1417 if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
1418 {
1419 gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
1420 }
1421 else if (!gtk_text_iter_starts_tag (&iter, search->priv->found_tag))
1422 {
1423 gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
1424 }
1425
1426 limit = iter;
1427 gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
1428
1429 if (gtk_text_iter_compare (stop_at, &limit) < 0)
1430 {
1431 limit = *stop_at;
1432 }
1433
1434 while (basic_forward_search (search, &iter, match_start, match_end, &limit))
1435 {
1436 if (gtk_text_iter_compare (start_at, match_start) <= 0)
1437 {
1438 return TRUE;
1439 }
1440
1441 iter = *match_end;
1442 }
1443
1444 iter = limit;
1445 }
1446
1447 return FALSE;
1448 }
1449
1450 /* Remove the occurrences in the range. @start and @end may be adjusted, if they
1451 * are in a found_tag region.
1452 */
1453 static void
remove_occurrences_in_range(GtkSourceSearchContext * search,GtkTextIter * start,GtkTextIter * end)1454 remove_occurrences_in_range (GtkSourceSearchContext *search,
1455 GtkTextIter *start,
1456 GtkTextIter *end)
1457 {
1458 GtkTextIter iter;
1459 GtkTextIter match_start;
1460 GtkTextIter match_end;
1461
1462 if ((gtk_text_iter_has_tag (start, search->priv->found_tag) &&
1463 !gtk_text_iter_starts_tag (start, search->priv->found_tag)) ||
1464 (gtk_source_search_settings_get_at_word_boundaries (search->priv->settings) &&
1465 gtk_text_iter_ends_tag (start, search->priv->found_tag)))
1466 {
1467 gtk_text_iter_backward_to_tag_toggle (start, search->priv->found_tag);
1468 }
1469
1470 if ((gtk_text_iter_has_tag (end, search->priv->found_tag) &&
1471 !gtk_text_iter_starts_tag (end, search->priv->found_tag)) ||
1472 (gtk_source_search_settings_get_at_word_boundaries (search->priv->settings) &&
1473 gtk_text_iter_starts_tag (end, search->priv->found_tag)))
1474 {
1475 gtk_text_iter_forward_to_tag_toggle (end, search->priv->found_tag);
1476 }
1477
1478 iter = *start;
1479
1480 while (smart_forward_search_without_scanning (search, &iter, &match_start, &match_end, end))
1481 {
1482 if (search->priv->scan_region == NULL)
1483 {
1484 /* The occurrence has already been scanned, and thus
1485 * occurrence_count take it into account. */
1486 search->priv->occurrences_count--;
1487 }
1488 else
1489 {
1490 GtkSourceRegion *region;
1491
1492 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
1493 &match_start,
1494 &match_end);
1495
1496 if (gtk_source_region_is_empty (region))
1497 {
1498 search->priv->occurrences_count--;
1499 }
1500
1501 g_clear_object (®ion);
1502 }
1503
1504 iter = match_end;
1505 }
1506
1507 gtk_text_buffer_remove_tag (search->priv->buffer,
1508 search->priv->found_tag,
1509 start,
1510 end);
1511 }
1512
1513 static void
scan_subregion(GtkSourceSearchContext * search,GtkTextIter * start,GtkTextIter * end)1514 scan_subregion (GtkSourceSearchContext *search,
1515 GtkTextIter *start,
1516 GtkTextIter *end)
1517 {
1518 GtkTextIter iter;
1519 GtkTextIter *limit;
1520 gboolean found = TRUE;
1521 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
1522
1523 /* Make sure the 'found' tag has the priority over syntax highlighting
1524 * tags. */
1525 text_tag_set_highest_priority (search->priv->found_tag,
1526 search->priv->buffer);
1527
1528 adjust_subregion (search, start, end);
1529 remove_occurrences_in_range (search, start, end);
1530
1531 if (search->priv->scan_region != NULL)
1532 {
1533 DEBUG ({
1534 g_print ("Region to scan, before:\n");
1535 print_region (search->priv->scan_region);
1536 });
1537
1538 gtk_source_region_subtract_subregion (search->priv->scan_region, start, end);
1539
1540 DEBUG ({
1541 g_print ("Region to scan, after:\n");
1542 print_region (search->priv->scan_region);
1543 });
1544 }
1545
1546 if (search->priv->task_region != NULL)
1547 {
1548 gtk_source_region_subtract_subregion (search->priv->task_region, start, end);
1549 }
1550
1551 if (search_text == NULL)
1552 {
1553 /* We have removed the found_tag, we are done. */
1554 return;
1555 }
1556
1557 iter = *start;
1558
1559 if (gtk_text_iter_is_end (end))
1560 {
1561 limit = NULL;
1562 }
1563 else
1564 {
1565 limit = end;
1566 }
1567
1568 do
1569 {
1570 GtkTextIter match_start;
1571 GtkTextIter match_end;
1572
1573 found = basic_forward_search (search, &iter, &match_start, &match_end, limit);
1574
1575 if (found)
1576 {
1577 gtk_text_buffer_apply_tag (search->priv->buffer,
1578 search->priv->found_tag,
1579 &match_start,
1580 &match_end);
1581
1582 search->priv->occurrences_count++;
1583 }
1584
1585 iter = match_end;
1586
1587 } while (found);
1588 }
1589
1590 static void
scan_all_region(GtkSourceSearchContext * search,GtkSourceRegion * region)1591 scan_all_region (GtkSourceSearchContext *search,
1592 GtkSourceRegion *region)
1593 {
1594 GtkSourceRegionIter region_iter;
1595
1596 gtk_source_region_get_start_region_iter (region, ®ion_iter);
1597
1598 while (!gtk_source_region_iter_is_end (®ion_iter))
1599 {
1600 GtkTextIter subregion_start;
1601 GtkTextIter subregion_end;
1602
1603 if (!gtk_source_region_iter_get_subregion (®ion_iter,
1604 &subregion_start,
1605 &subregion_end))
1606 {
1607 break;
1608 }
1609
1610 scan_subregion (search,
1611 &subregion_start,
1612 &subregion_end);
1613
1614 gtk_source_region_iter_next (®ion_iter);
1615 }
1616 }
1617
1618 /* Scan a chunk of the region. If the region is small enough, all the region
1619 * will be scanned. But if the region is big, scanning only the chunk will not
1620 * block the UI normally. Begin the scan at the beginning of the region.
1621 */
1622 static void
scan_region_forward(GtkSourceSearchContext * search,GtkSourceRegion * region)1623 scan_region_forward (GtkSourceSearchContext *search,
1624 GtkSourceRegion *region)
1625 {
1626 gint nb_remaining_lines = SCAN_BATCH_SIZE;
1627 GtkTextIter start;
1628 GtkTextIter end;
1629
1630 while (nb_remaining_lines > 0 &&
1631 get_first_subregion (region, &start, &end))
1632 {
1633 GtkTextIter limit = start;
1634 gint start_line;
1635 gint limit_line;
1636
1637 gtk_text_iter_forward_lines (&limit, nb_remaining_lines);
1638
1639 if (gtk_text_iter_compare (&end, &limit) < 0)
1640 {
1641 limit = end;
1642 }
1643
1644 scan_subregion (search, &start, &limit);
1645
1646 gtk_source_region_subtract_subregion (region, &start, &limit);
1647
1648 start_line = gtk_text_iter_get_line (&start);
1649 limit_line = gtk_text_iter_get_line (&limit);
1650
1651 nb_remaining_lines -= limit_line - start_line;
1652 }
1653 }
1654
1655 /* Same as scan_region_forward(), but begins the scan at the end of the region. */
1656 static void
scan_region_backward(GtkSourceSearchContext * search,GtkSourceRegion * region)1657 scan_region_backward (GtkSourceSearchContext *search,
1658 GtkSourceRegion *region)
1659 {
1660 gint nb_remaining_lines = SCAN_BATCH_SIZE;
1661 GtkTextIter start;
1662 GtkTextIter end;
1663
1664 while (nb_remaining_lines > 0 &&
1665 get_last_subregion (region, &start, &end))
1666 {
1667 GtkTextIter limit = end;
1668 gint limit_line;
1669 gint end_line;
1670
1671 gtk_text_iter_backward_lines (&limit, nb_remaining_lines);
1672
1673 if (gtk_text_iter_compare (&limit, &start) < 0)
1674 {
1675 limit = start;
1676 }
1677
1678 scan_subregion (search, &limit, &end);
1679
1680 gtk_source_region_subtract_subregion (region, &limit, &end);
1681
1682 limit_line = gtk_text_iter_get_line (&limit);
1683 end_line = gtk_text_iter_get_line (&end);
1684
1685 nb_remaining_lines -= end_line - limit_line;
1686 }
1687 }
1688
1689 static void
resume_task(GtkSourceSearchContext * search)1690 resume_task (GtkSourceSearchContext *search)
1691 {
1692 ForwardBackwardData *task_data = g_task_get_task_data (search->priv->task);
1693 GtkTextIter start_at;
1694
1695 g_clear_object (&search->priv->task_region);
1696
1697 gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
1698 &start_at,
1699 task_data->start_at);
1700
1701 if (task_data->is_forward)
1702 {
1703 smart_forward_search_async (search,
1704 &start_at,
1705 task_data->wrapped_around);
1706 }
1707 else
1708 {
1709 smart_backward_search_async (search,
1710 &start_at,
1711 task_data->wrapped_around);
1712 }
1713 }
1714
1715 static void
scan_task_region(GtkSourceSearchContext * search)1716 scan_task_region (GtkSourceSearchContext *search)
1717 {
1718 ForwardBackwardData *task_data = g_task_get_task_data (search->priv->task);
1719
1720 if (task_data->is_forward)
1721 {
1722 scan_region_forward (search, search->priv->task_region);
1723 }
1724 else
1725 {
1726 scan_region_backward (search, search->priv->task_region);
1727 }
1728
1729 resume_task (search);
1730 }
1731
1732 static gboolean
idle_scan_normal_search(GtkSourceSearchContext * search)1733 idle_scan_normal_search (GtkSourceSearchContext *search)
1734 {
1735 if (search->priv->high_priority_region != NULL)
1736 {
1737 /* Normally the high priority region is not really big, since it
1738 * is the visible area on the screen. So we can highlight it in
1739 * one batch.
1740 */
1741 scan_all_region (search, search->priv->high_priority_region);
1742
1743 g_clear_object (&search->priv->high_priority_region);
1744 return G_SOURCE_CONTINUE;
1745 }
1746
1747 if (search->priv->task_region != NULL)
1748 {
1749 scan_task_region (search);
1750 return G_SOURCE_CONTINUE;
1751 }
1752
1753 scan_region_forward (search, search->priv->scan_region);
1754
1755 if (gtk_source_region_is_empty (search->priv->scan_region))
1756 {
1757 search->priv->idle_scan_id = 0;
1758
1759 g_object_notify (G_OBJECT (search), "occurrences-count");
1760
1761 g_clear_object (&search->priv->scan_region);
1762 return G_SOURCE_REMOVE;
1763 }
1764
1765 return G_SOURCE_CONTINUE;
1766 }
1767
1768 /* Just remove the found_tag's located in the high-priority region. For big
1769 * documents, if the pattern is modified, it can take some time to re-scan all
1770 * the buffer, so it's better to clear the highlighting as soon as possible. If
1771 * the highlighting is not cleared, the user can wrongly think that the new
1772 * pattern matches the old occurrences.
1773 * The drawback of clearing the highlighting is that for small documents, there
1774 * is some flickering.
1775 */
1776 static void
regex_search_handle_high_priority_region(GtkSourceSearchContext * search)1777 regex_search_handle_high_priority_region (GtkSourceSearchContext *search)
1778 {
1779 GtkSourceRegion *region;
1780 GtkSourceRegionIter region_iter;
1781
1782 region = gtk_source_region_intersect_region (search->priv->high_priority_region,
1783 search->priv->scan_region);
1784
1785 if (region == NULL)
1786 {
1787 return;
1788 }
1789
1790 gtk_source_region_get_start_region_iter (region, ®ion_iter);
1791
1792 while (!gtk_source_region_iter_is_end (®ion_iter))
1793 {
1794 GtkTextIter subregion_start;
1795 GtkTextIter subregion_end;
1796
1797 if (!gtk_source_region_iter_get_subregion (®ion_iter,
1798 &subregion_start,
1799 &subregion_end))
1800 {
1801 break;
1802 }
1803
1804 gtk_text_buffer_remove_tag (search->priv->buffer,
1805 search->priv->found_tag,
1806 &subregion_start,
1807 &subregion_end);
1808
1809 gtk_source_region_iter_next (®ion_iter);
1810 }
1811
1812 g_clear_object (®ion);
1813 }
1814
1815 /* Returns TRUE if the segment is finished, and FALSE on partial match. */
1816 static gboolean
regex_search_scan_segment(GtkSourceSearchContext * search,const GtkTextIter * segment_start,const GtkTextIter * segment_end,GtkTextIter * stopped_at)1817 regex_search_scan_segment (GtkSourceSearchContext *search,
1818 const GtkTextIter *segment_start,
1819 const GtkTextIter *segment_end,
1820 GtkTextIter *stopped_at)
1821 {
1822 GtkTextIter real_start;
1823 gint start_pos;
1824 gchar *subject;
1825 gssize subject_length;
1826 GRegexMatchFlags match_options;
1827 GMatchInfo *match_info;
1828 GtkTextIter iter;
1829 gint iter_byte_pos;
1830 gboolean segment_finished;
1831 GtkTextIter match_start;
1832 GtkTextIter match_end;
1833
1834 g_assert (stopped_at != NULL);
1835
1836 gtk_text_buffer_remove_tag (search->priv->buffer,
1837 search->priv->found_tag,
1838 segment_start,
1839 segment_end);
1840
1841 if (search->priv->regex == NULL ||
1842 search->priv->regex_error != NULL)
1843 {
1844 *stopped_at = *segment_end;
1845 return TRUE;
1846 }
1847
1848 regex_search_get_real_start (search,
1849 segment_start,
1850 &real_start,
1851 &start_pos);
1852
1853 DEBUG ({
1854 g_print ("\n*** regex search - scan segment ***\n");
1855 g_print ("start position in the subject (in bytes): %d\n", start_pos);
1856 });
1857
1858 match_options = regex_search_get_match_options (&real_start, segment_end);
1859
1860 if (match_options & G_REGEX_MATCH_NOTBOL)
1861 {
1862 DEBUG ({
1863 g_print ("match notbol\n");
1864 });
1865 }
1866
1867 if (match_options & G_REGEX_MATCH_NOTEOL)
1868 {
1869 DEBUG ({
1870 g_print ("match noteol\n");
1871 });
1872 }
1873
1874 if (match_options & G_REGEX_MATCH_PARTIAL_HARD)
1875 {
1876 DEBUG ({
1877 g_print ("match partial hard\n");
1878 });
1879 }
1880
1881 subject = gtk_text_iter_get_visible_text (&real_start, segment_end);
1882 subject_length = strlen (subject);
1883
1884 DEBUG ({
1885 gchar *subject_escaped = gtk_source_utils_escape_search_text (subject);
1886 g_print ("subject (escaped): %s\n", subject_escaped);
1887 g_free (subject_escaped);
1888 });
1889
1890 g_regex_match_full (search->priv->regex,
1891 subject,
1892 subject_length,
1893 start_pos,
1894 match_options,
1895 &match_info,
1896 &search->priv->regex_error);
1897
1898 iter = real_start;
1899 iter_byte_pos = 0;
1900
1901 while (regex_search_fetch_match (match_info,
1902 subject,
1903 subject_length,
1904 &iter,
1905 &iter_byte_pos,
1906 &match_start,
1907 &match_end))
1908 {
1909 gtk_text_buffer_apply_tag (search->priv->buffer,
1910 search->priv->found_tag,
1911 &match_start,
1912 &match_end);
1913
1914 DEBUG ({
1915 gchar *match_text = gtk_text_iter_get_visible_text (&match_start, &match_end);
1916 gchar *match_escaped = gtk_source_utils_escape_search_text (match_text);
1917 g_print ("match found (escaped): %s\n", match_escaped);
1918 g_free (match_text);
1919 g_free (match_escaped);
1920 });
1921
1922 search->priv->occurrences_count++;
1923
1924 g_match_info_next (match_info, &search->priv->regex_error);
1925 }
1926
1927 if (search->priv->regex_error != NULL)
1928 {
1929 g_object_notify (G_OBJECT (search), "regex-error");
1930 }
1931
1932 if (g_match_info_is_partial_match (match_info))
1933 {
1934 segment_finished = FALSE;
1935
1936 if (gtk_text_iter_compare (segment_start, &iter) < 0)
1937 {
1938 *stopped_at = iter;
1939 }
1940 else
1941 {
1942 *stopped_at = *segment_start;
1943 }
1944
1945 DEBUG ({
1946 g_print ("partial match\n");
1947 });
1948 }
1949 else
1950 {
1951 *stopped_at = *segment_end;
1952 segment_finished = TRUE;
1953 }
1954
1955 g_free (subject);
1956 g_match_info_free (match_info);
1957
1958 return segment_finished;
1959 }
1960
1961 static void
regex_search_scan_chunk(GtkSourceSearchContext * search,const GtkTextIter * chunk_start,const GtkTextIter * chunk_end)1962 regex_search_scan_chunk (GtkSourceSearchContext *search,
1963 const GtkTextIter *chunk_start,
1964 const GtkTextIter *chunk_end)
1965 {
1966 GtkTextIter segment_start = *chunk_start;
1967
1968 while (gtk_text_iter_compare (&segment_start, chunk_end) < 0)
1969 {
1970 GtkTextIter segment_end;
1971 GtkTextIter stopped_at;
1972 gint nb_lines = 1;
1973
1974 segment_end = segment_start;
1975 gtk_text_iter_forward_line (&segment_end);
1976
1977 while (!regex_search_scan_segment (search,
1978 &segment_start,
1979 &segment_end,
1980 &stopped_at))
1981 {
1982 /* TODO: performance improvement. On partial match, use
1983 * a GString to grow the subject.
1984 */
1985
1986 segment_start = stopped_at;
1987 gtk_text_iter_forward_lines (&segment_end, nb_lines);
1988 nb_lines <<= 1;
1989 }
1990
1991 segment_start = stopped_at;
1992 }
1993
1994 gtk_source_region_subtract_subregion (search->priv->scan_region,
1995 chunk_start,
1996 &segment_start);
1997
1998 if (search->priv->task_region != NULL)
1999 {
2000 gtk_source_region_subtract_subregion (search->priv->task_region,
2001 chunk_start,
2002 &segment_start);
2003 }
2004 }
2005
2006 static void
regex_search_scan_next_chunk(GtkSourceSearchContext * search)2007 regex_search_scan_next_chunk (GtkSourceSearchContext *search)
2008 {
2009 GtkTextIter chunk_start;
2010 GtkTextIter chunk_end;
2011
2012 if (gtk_source_region_is_empty (search->priv->scan_region))
2013 {
2014 return;
2015 }
2016
2017 if (!gtk_source_region_get_bounds (search->priv->scan_region, &chunk_start, NULL))
2018 {
2019 return;
2020 }
2021
2022 chunk_end = chunk_start;
2023 gtk_text_iter_forward_lines (&chunk_end, SCAN_BATCH_SIZE);
2024
2025 regex_search_scan_chunk (search, &chunk_start, &chunk_end);
2026 }
2027
2028 static gboolean
idle_scan_regex_search(GtkSourceSearchContext * search)2029 idle_scan_regex_search (GtkSourceSearchContext *search)
2030 {
2031 if (search->priv->high_priority_region != NULL)
2032 {
2033 regex_search_handle_high_priority_region (search);
2034 g_clear_object (&search->priv->high_priority_region);
2035 return G_SOURCE_CONTINUE;
2036 }
2037
2038 regex_search_scan_next_chunk (search);
2039
2040 if (search->priv->task != NULL)
2041 {
2042 /* Always resume the task, even if the task region has not been
2043 * fully scanned. The task region can be huge (the whole
2044 * buffer), and an occurrence can be found earlier. Obviously it
2045 * would be better to resume the task only if an occurrence has
2046 * been found in the task region. But it would be a little more
2047 * complicated to implement, for not a big performance
2048 * improvement.
2049 */
2050 resume_task (search);
2051 return G_SOURCE_CONTINUE;
2052 }
2053
2054 if (gtk_source_region_is_empty (search->priv->scan_region))
2055 {
2056 search->priv->idle_scan_id = 0;
2057
2058 g_object_notify (G_OBJECT (search), "occurrences-count");
2059
2060 g_clear_object (&search->priv->scan_region);
2061 return G_SOURCE_REMOVE;
2062 }
2063
2064 return G_SOURCE_CONTINUE;
2065 }
2066
2067 static gboolean
idle_scan_cb(GtkSourceSearchContext * search)2068 idle_scan_cb (GtkSourceSearchContext *search)
2069 {
2070 if (search->priv->buffer == NULL)
2071 {
2072 search->priv->idle_scan_id = 0;
2073 clear_search (search);
2074 return G_SOURCE_REMOVE;
2075 }
2076
2077 return gtk_source_search_settings_get_regex_enabled (search->priv->settings) ?
2078 idle_scan_regex_search (search) :
2079 idle_scan_normal_search (search);
2080 }
2081
2082 static void
install_idle_scan(GtkSourceSearchContext * search)2083 install_idle_scan (GtkSourceSearchContext *search)
2084 {
2085 if (search->priv->idle_scan_id == 0)
2086 {
2087 search->priv->idle_scan_id = g_idle_add ((GSourceFunc)idle_scan_cb, search);
2088 }
2089 }
2090
2091 /* Returns TRUE when finished. */
2092 static gboolean
smart_forward_search_step(GtkSourceSearchContext * search,GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end)2093 smart_forward_search_step (GtkSourceSearchContext *search,
2094 GtkTextIter *start_at,
2095 GtkTextIter *match_start,
2096 GtkTextIter *match_end)
2097 {
2098 GtkTextIter iter = *start_at;
2099 GtkTextIter limit;
2100 GtkTextIter region_start = *start_at;
2101 GtkSourceRegion *region = NULL;
2102
2103 if (!gtk_text_iter_has_tag (&iter, search->priv->found_tag))
2104 {
2105 gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
2106 }
2107 else if (!gtk_text_iter_starts_tag (&iter, search->priv->found_tag))
2108 {
2109 gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
2110 region_start = iter;
2111 }
2112
2113 limit = iter;
2114 gtk_text_iter_forward_to_tag_toggle (&limit, search->priv->found_tag);
2115
2116 if (search->priv->scan_region != NULL)
2117 {
2118 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
2119 ®ion_start,
2120 &limit);
2121 }
2122
2123 if (gtk_source_region_is_empty (region))
2124 {
2125 g_clear_object (®ion);
2126
2127 while (basic_forward_search (search, &iter, match_start, match_end, &limit))
2128 {
2129 if (gtk_text_iter_compare (start_at, match_start) <= 0)
2130 {
2131 return TRUE;
2132 }
2133
2134 iter = *match_end;
2135 }
2136
2137 *start_at = limit;
2138 return FALSE;
2139 }
2140
2141 /* Scan a chunk of the buffer, not the whole 'region'. An occurrence can
2142 * be found before the 'region' is scanned entirely.
2143 */
2144 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2145 {
2146 regex_search_scan_next_chunk (search);
2147 }
2148 else
2149 {
2150 scan_region_forward (search, region);
2151 }
2152
2153 g_clear_object (®ion);
2154
2155 return FALSE;
2156 }
2157
2158 /* Doesn't wrap around. */
2159 static gboolean
smart_forward_search(GtkSourceSearchContext * search,const GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end)2160 smart_forward_search (GtkSourceSearchContext *search,
2161 const GtkTextIter *start_at,
2162 GtkTextIter *match_start,
2163 GtkTextIter *match_end)
2164 {
2165 GtkTextIter iter = *start_at;
2166 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
2167
2168 g_return_val_if_fail (match_start != NULL, FALSE);
2169 g_return_val_if_fail (match_end != NULL, FALSE);
2170
2171 if (search_text == NULL)
2172 {
2173 return FALSE;
2174 }
2175
2176 while (!gtk_text_iter_is_end (&iter))
2177 {
2178 if (smart_forward_search_step (search, &iter, match_start, match_end))
2179 {
2180 return TRUE;
2181 }
2182 }
2183
2184 return FALSE;
2185 }
2186
2187 /* Returns TRUE when finished. */
2188 static gboolean
smart_backward_search_step(GtkSourceSearchContext * search,GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end)2189 smart_backward_search_step (GtkSourceSearchContext *search,
2190 GtkTextIter *start_at,
2191 GtkTextIter *match_start,
2192 GtkTextIter *match_end)
2193 {
2194 GtkTextIter iter = *start_at;
2195 GtkTextIter limit;
2196 GtkTextIter region_end = *start_at;
2197 GtkSourceRegion *region = NULL;
2198
2199 if (gtk_text_iter_starts_tag (&iter, search->priv->found_tag) ||
2200 (!gtk_text_iter_has_tag (&iter, search->priv->found_tag) &&
2201 !gtk_text_iter_ends_tag (&iter, search->priv->found_tag)))
2202 {
2203 gtk_text_iter_backward_to_tag_toggle (&iter, search->priv->found_tag);
2204 }
2205 else if (gtk_text_iter_has_tag (&iter, search->priv->found_tag))
2206 {
2207 gtk_text_iter_forward_to_tag_toggle (&iter, search->priv->found_tag);
2208 region_end = iter;
2209 }
2210
2211 limit = iter;
2212 gtk_text_iter_backward_to_tag_toggle (&limit, search->priv->found_tag);
2213
2214 if (search->priv->scan_region != NULL)
2215 {
2216 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
2217 &limit,
2218 ®ion_end);
2219 }
2220
2221 if (gtk_source_region_is_empty (region))
2222 {
2223 g_clear_object (®ion);
2224
2225 while (basic_backward_search (search, &iter, match_start, match_end, &limit))
2226 {
2227 if (gtk_text_iter_compare (match_end, start_at) <= 0)
2228 {
2229 return TRUE;
2230 }
2231
2232 iter = *match_start;
2233 }
2234
2235 *start_at = limit;
2236 return FALSE;
2237 }
2238
2239 /* Scan a chunk of the buffer, not the whole 'region'. An occurrence can
2240 * be found before the 'region' is scanned entirely.
2241 */
2242 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2243 {
2244 regex_search_scan_next_chunk (search);
2245 }
2246 else
2247 {
2248 scan_region_forward (search, region);
2249 }
2250
2251 g_clear_object (®ion);
2252
2253 return FALSE;
2254 }
2255
2256 /* Doesn't wrap around. */
2257 static gboolean
smart_backward_search(GtkSourceSearchContext * search,const GtkTextIter * start_at,GtkTextIter * match_start,GtkTextIter * match_end)2258 smart_backward_search (GtkSourceSearchContext *search,
2259 const GtkTextIter *start_at,
2260 GtkTextIter *match_start,
2261 GtkTextIter *match_end)
2262 {
2263 GtkTextIter iter = *start_at;
2264 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
2265
2266 g_return_val_if_fail (match_start != NULL, FALSE);
2267 g_return_val_if_fail (match_end != NULL, FALSE);
2268
2269 if (search_text == NULL)
2270 {
2271 return FALSE;
2272 }
2273
2274 while (!gtk_text_iter_is_start (&iter))
2275 {
2276 if (smart_backward_search_step (search, &iter, match_start, match_end))
2277 {
2278 return TRUE;
2279 }
2280 }
2281
2282 return FALSE;
2283 }
2284
2285 static void
add_subregion_to_scan(GtkSourceSearchContext * search,const GtkTextIter * subregion_start,const GtkTextIter * subregion_end)2286 add_subregion_to_scan (GtkSourceSearchContext *search,
2287 const GtkTextIter *subregion_start,
2288 const GtkTextIter *subregion_end)
2289 {
2290 GtkTextIter start = *subregion_start;
2291 GtkTextIter end = *subregion_end;
2292
2293 if (search->priv->scan_region == NULL)
2294 {
2295 search->priv->scan_region = gtk_source_region_new (search->priv->buffer);
2296 }
2297
2298 DEBUG ({
2299 g_print ("add_subregion_to_scan(): region to scan, before:\n");
2300 print_region (search->priv->scan_region);
2301 });
2302
2303 gtk_source_region_add_subregion (search->priv->scan_region, &start, &end);
2304
2305 DEBUG ({
2306 g_print ("add_subregion_to_scan(): region to scan, after:\n");
2307 print_region (search->priv->scan_region);
2308 });
2309
2310 install_idle_scan (search);
2311 }
2312
2313 static void
update_regex(GtkSourceSearchContext * search)2314 update_regex (GtkSourceSearchContext *search)
2315 {
2316 gboolean regex_error_changed = FALSE;
2317 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
2318
2319 if (search->priv->regex != NULL)
2320 {
2321 g_regex_unref (search->priv->regex);
2322 search->priv->regex = NULL;
2323 }
2324
2325 if (search->priv->regex_error != NULL)
2326 {
2327 g_clear_error (&search->priv->regex_error);
2328 regex_error_changed = TRUE;
2329 }
2330
2331 if (search_text != NULL &&
2332 gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2333 {
2334 GRegexCompileFlags compile_flags = G_REGEX_OPTIMIZE | G_REGEX_MULTILINE;
2335 gchar *pattern = (gchar *)search_text;
2336
2337 search->priv->text_nb_lines = 0;
2338
2339 if (!gtk_source_search_settings_get_case_sensitive (search->priv->settings))
2340 {
2341 compile_flags |= G_REGEX_CASELESS;
2342 }
2343
2344 if (gtk_source_search_settings_get_at_word_boundaries (search->priv->settings))
2345 {
2346 pattern = g_strdup_printf ("\\b%s\\b", search_text);
2347 }
2348
2349 search->priv->regex = g_regex_new (pattern,
2350 compile_flags,
2351 G_REGEX_MATCH_NOTEMPTY,
2352 &search->priv->regex_error);
2353
2354 if (search->priv->regex_error != NULL)
2355 {
2356 regex_error_changed = TRUE;
2357 }
2358
2359 if (gtk_source_search_settings_get_at_word_boundaries (search->priv->settings))
2360 {
2361 g_free (pattern);
2362 }
2363 }
2364
2365 if (regex_error_changed)
2366 {
2367 g_object_notify (G_OBJECT (search), "regex-error");
2368 }
2369 }
2370
2371 static void
update(GtkSourceSearchContext * search)2372 update (GtkSourceSearchContext *search)
2373 {
2374 GtkTextIter start;
2375 GtkTextIter end;
2376 GtkSourceBufferInternal *buffer_internal;
2377
2378 if (search->priv->buffer == NULL)
2379 {
2380 return;
2381 }
2382
2383 clear_search (search);
2384 update_regex (search);
2385
2386 search->priv->scan_region = gtk_source_region_new (search->priv->buffer);
2387
2388 gtk_text_buffer_get_bounds (search->priv->buffer, &start, &end);
2389 add_subregion_to_scan (search, &start, &end);
2390
2391 /* Notify the GtkSourceViews that the search is starting, so that
2392 * _gtk_source_search_context_update_highlight() can be called for the
2393 * visible regions of the buffer.
2394 */
2395 buffer_internal = _gtk_source_buffer_internal_get_from_buffer (GTK_SOURCE_BUFFER (search->priv->buffer));
2396 _gtk_source_buffer_internal_emit_search_start (buffer_internal, search);
2397 }
2398
2399 static void
insert_text_before_cb(GtkSourceSearchContext * search,GtkTextIter * location,gchar * text,gint length)2400 insert_text_before_cb (GtkSourceSearchContext *search,
2401 GtkTextIter *location,
2402 gchar *text,
2403 gint length)
2404 {
2405 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
2406
2407 clear_task (search);
2408
2409 if (search_text != NULL &&
2410 !gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2411 {
2412 GtkTextIter start = *location;
2413 GtkTextIter end = *location;
2414
2415 remove_occurrences_in_range (search, &start, &end);
2416 add_subregion_to_scan (search, &start, &end);
2417 }
2418 }
2419
2420 static void
insert_text_after_cb(GtkSourceSearchContext * search,GtkTextIter * location,gchar * text,gint length)2421 insert_text_after_cb (GtkSourceSearchContext *search,
2422 GtkTextIter *location,
2423 gchar *text,
2424 gint length)
2425 {
2426 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2427 {
2428 update (search);
2429 }
2430 else
2431 {
2432 GtkTextIter start;
2433 GtkTextIter end;
2434
2435 start = end = *location;
2436
2437 gtk_text_iter_backward_chars (&start,
2438 g_utf8_strlen (text, length));
2439
2440 add_subregion_to_scan (search, &start, &end);
2441 }
2442 }
2443
2444 static void
delete_range_before_cb(GtkSourceSearchContext * search,GtkTextIter * delete_start,GtkTextIter * delete_end)2445 delete_range_before_cb (GtkSourceSearchContext *search,
2446 GtkTextIter *delete_start,
2447 GtkTextIter *delete_end)
2448 {
2449 GtkTextIter start_buffer;
2450 GtkTextIter end_buffer;
2451 const gchar *search_text = gtk_source_search_settings_get_search_text (search->priv->settings);
2452
2453 clear_task (search);
2454
2455 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2456 {
2457 return;
2458 }
2459
2460 gtk_text_buffer_get_bounds (search->priv->buffer, &start_buffer, &end_buffer);
2461
2462 if (gtk_text_iter_equal (delete_start, &start_buffer) &&
2463 gtk_text_iter_equal (delete_end, &end_buffer))
2464 {
2465 /* Special case when removing all the text. */
2466 search->priv->occurrences_count = 0;
2467 return;
2468 }
2469
2470 if (search_text != NULL)
2471 {
2472 GtkTextIter start = *delete_start;
2473 GtkTextIter end = *delete_end;
2474
2475 gtk_text_iter_backward_lines (&start, search->priv->text_nb_lines);
2476 gtk_text_iter_forward_lines (&end, search->priv->text_nb_lines);
2477
2478 remove_occurrences_in_range (search, &start, &end);
2479 add_subregion_to_scan (search, &start, &end);
2480 }
2481 }
2482
2483 static void
delete_range_after_cb(GtkSourceSearchContext * search,GtkTextIter * start,GtkTextIter * end)2484 delete_range_after_cb (GtkSourceSearchContext *search,
2485 GtkTextIter *start,
2486 GtkTextIter *end)
2487 {
2488 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2489 {
2490 update (search);
2491 }
2492 else
2493 {
2494 add_subregion_to_scan (search, start, end);
2495 }
2496 }
2497
2498 static void
set_buffer(GtkSourceSearchContext * search,GtkSourceBuffer * buffer)2499 set_buffer (GtkSourceSearchContext *search,
2500 GtkSourceBuffer *buffer)
2501 {
2502 g_assert (search->priv->buffer == NULL);
2503 g_assert (search->priv->tag_table == NULL);
2504
2505 search->priv->buffer = GTK_TEXT_BUFFER (buffer);
2506
2507 g_object_add_weak_pointer (G_OBJECT (buffer),
2508 (gpointer *)&search->priv->buffer);
2509
2510 search->priv->tag_table = gtk_text_buffer_get_tag_table (search->priv->buffer);
2511 g_object_ref (search->priv->tag_table);
2512
2513 g_signal_connect_object (buffer,
2514 "insert-text",
2515 G_CALLBACK (insert_text_before_cb),
2516 search,
2517 G_CONNECT_SWAPPED);
2518
2519 g_signal_connect_object (buffer,
2520 "insert-text",
2521 G_CALLBACK (insert_text_after_cb),
2522 search,
2523 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2524
2525 g_signal_connect_object (buffer,
2526 "delete-range",
2527 G_CALLBACK (delete_range_before_cb),
2528 search,
2529 G_CONNECT_SWAPPED);
2530
2531 g_signal_connect_object (buffer,
2532 "delete-range",
2533 G_CALLBACK (delete_range_after_cb),
2534 search,
2535 G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2536
2537 search->priv->found_tag = gtk_text_buffer_create_tag (search->priv->buffer, NULL, NULL);
2538 g_object_ref (search->priv->found_tag);
2539
2540 sync_found_tag (search);
2541
2542 g_signal_connect_object (search->priv->buffer,
2543 "notify::style-scheme",
2544 G_CALLBACK (sync_found_tag),
2545 search,
2546 G_CONNECT_SWAPPED);
2547
2548 _gtk_source_buffer_add_search_context (buffer, search);
2549 }
2550
2551 static gint
compute_number_of_lines(const gchar * text)2552 compute_number_of_lines (const gchar *text)
2553 {
2554 const gchar *p;
2555 gint len;
2556 gint nb_of_lines = 1;
2557
2558 if (text == NULL)
2559 {
2560 return 0;
2561 }
2562
2563 len = strlen (text);
2564 p = text;
2565
2566 while (len > 0)
2567 {
2568 gint delimiter;
2569 gint next_paragraph;
2570
2571 pango_find_paragraph_boundary (p, len, &delimiter, &next_paragraph);
2572
2573 if (delimiter == next_paragraph)
2574 {
2575 /* not found */
2576 break;
2577 }
2578
2579 p += next_paragraph;
2580 len -= next_paragraph;
2581 nb_of_lines++;
2582 }
2583
2584 return nb_of_lines;
2585 }
2586
2587 static void
search_text_updated(GtkSourceSearchContext * search)2588 search_text_updated (GtkSourceSearchContext *search)
2589 {
2590 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
2591 {
2592 search->priv->text_nb_lines = 0;
2593 }
2594 else
2595 {
2596 const gchar *text = gtk_source_search_settings_get_search_text (search->priv->settings);
2597 search->priv->text_nb_lines = compute_number_of_lines (text);
2598 }
2599 }
2600
2601 static void
settings_notify_cb(GtkSourceSearchContext * search,GParamSpec * pspec,GtkSourceSearchSettings * settings)2602 settings_notify_cb (GtkSourceSearchContext *search,
2603 GParamSpec *pspec,
2604 GtkSourceSearchSettings *settings)
2605 {
2606 const gchar *property = g_param_spec_get_name (pspec);
2607
2608 if (g_str_equal (property, "search-text"))
2609 {
2610 search_text_updated (search);
2611 }
2612
2613 update (search);
2614 }
2615
2616 static void
set_settings(GtkSourceSearchContext * search,GtkSourceSearchSettings * settings)2617 set_settings (GtkSourceSearchContext *search,
2618 GtkSourceSearchSettings *settings)
2619 {
2620 g_assert (search->priv->settings == NULL);
2621
2622 if (settings != NULL)
2623 {
2624 search->priv->settings = g_object_ref (settings);
2625 }
2626 else
2627 {
2628 search->priv->settings = gtk_source_search_settings_new ();
2629 }
2630
2631 g_signal_connect_object (search->priv->settings,
2632 "notify",
2633 G_CALLBACK (settings_notify_cb),
2634 search,
2635 G_CONNECT_SWAPPED);
2636
2637 search_text_updated (search);
2638 update (search);
2639
2640 g_object_notify (G_OBJECT (search), "settings");
2641 }
2642
2643 static void
gtk_source_search_context_dispose(GObject * object)2644 gtk_source_search_context_dispose (GObject *object)
2645 {
2646 GtkSourceSearchContext *search = GTK_SOURCE_SEARCH_CONTEXT (object);
2647
2648 clear_search (search);
2649
2650 if (search->priv->found_tag != NULL &&
2651 search->priv->tag_table != NULL)
2652 {
2653 gtk_text_tag_table_remove (search->priv->tag_table,
2654 search->priv->found_tag);
2655
2656 g_clear_object (&search->priv->found_tag);
2657 g_clear_object (&search->priv->tag_table);
2658 }
2659
2660 if (search->priv->buffer != NULL)
2661 {
2662 g_object_remove_weak_pointer (G_OBJECT (search->priv->buffer),
2663 (gpointer *)&search->priv->buffer);
2664
2665 search->priv->buffer = NULL;
2666 }
2667
2668 g_clear_object (&search->priv->settings);
2669
2670 G_OBJECT_CLASS (gtk_source_search_context_parent_class)->dispose (object);
2671 }
2672
2673 static void
gtk_source_search_context_finalize(GObject * object)2674 gtk_source_search_context_finalize (GObject *object)
2675 {
2676 GtkSourceSearchContext *search = GTK_SOURCE_SEARCH_CONTEXT (object);
2677
2678 if (search->priv->regex != NULL)
2679 {
2680 g_regex_unref (search->priv->regex);
2681 }
2682
2683 g_clear_error (&search->priv->regex_error);
2684
2685 G_OBJECT_CLASS (gtk_source_search_context_parent_class)->finalize (object);
2686 }
2687
2688 static void
gtk_source_search_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)2689 gtk_source_search_context_get_property (GObject *object,
2690 guint prop_id,
2691 GValue *value,
2692 GParamSpec *pspec)
2693 {
2694 GtkSourceSearchContext *search;
2695
2696 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (object));
2697
2698 search = GTK_SOURCE_SEARCH_CONTEXT (object);
2699
2700 switch (prop_id)
2701 {
2702 case PROP_BUFFER:
2703 g_value_set_object (value, search->priv->buffer);
2704 break;
2705
2706 case PROP_SETTINGS:
2707 g_value_set_object (value, search->priv->settings);
2708 break;
2709
2710 case PROP_HIGHLIGHT:
2711 g_value_set_boolean (value, search->priv->highlight);
2712 break;
2713
2714 case PROP_MATCH_STYLE:
2715 g_value_set_object (value, search->priv->match_style);
2716 break;
2717
2718 case PROP_OCCURRENCES_COUNT:
2719 g_value_set_int (value, gtk_source_search_context_get_occurrences_count (search));
2720 break;
2721
2722 case PROP_REGEX_ERROR:
2723 g_value_set_pointer (value, gtk_source_search_context_get_regex_error (search));
2724 break;
2725
2726 default:
2727 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2728 break;
2729 }
2730 }
2731
2732 static void
gtk_source_search_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)2733 gtk_source_search_context_set_property (GObject *object,
2734 guint prop_id,
2735 const GValue *value,
2736 GParamSpec *pspec)
2737 {
2738 GtkSourceSearchContext *search;
2739
2740 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (object));
2741
2742 search = GTK_SOURCE_SEARCH_CONTEXT (object);
2743
2744 switch (prop_id)
2745 {
2746 case PROP_BUFFER:
2747 set_buffer (search, g_value_get_object (value));
2748 break;
2749
2750 case PROP_SETTINGS:
2751 set_settings (search, g_value_get_object (value));
2752 break;
2753
2754 case PROP_HIGHLIGHT:
2755 gtk_source_search_context_set_highlight (search, g_value_get_boolean (value));
2756 break;
2757
2758 case PROP_MATCH_STYLE:
2759 gtk_source_search_context_set_match_style (search, g_value_get_object (value));
2760 break;
2761
2762 default:
2763 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2764 break;
2765 }
2766 }
2767
2768 static void
gtk_source_search_context_class_init(GtkSourceSearchContextClass * klass)2769 gtk_source_search_context_class_init (GtkSourceSearchContextClass *klass)
2770 {
2771 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2772
2773 object_class->dispose = gtk_source_search_context_dispose;
2774 object_class->finalize = gtk_source_search_context_finalize;
2775 object_class->get_property = gtk_source_search_context_get_property;
2776 object_class->set_property = gtk_source_search_context_set_property;
2777
2778 /**
2779 * GtkSourceSearchContext:buffer:
2780 *
2781 * The #GtkSourceBuffer associated to the search context.
2782 *
2783 * Since: 3.10
2784 */
2785 g_object_class_install_property (object_class,
2786 PROP_BUFFER,
2787 g_param_spec_object ("buffer",
2788 "Buffer",
2789 "The associated GtkSourceBuffer",
2790 GTK_SOURCE_TYPE_BUFFER,
2791 G_PARAM_READWRITE |
2792 G_PARAM_CONSTRUCT_ONLY |
2793 G_PARAM_STATIC_STRINGS));
2794
2795 /**
2796 * GtkSourceSearchContext:settings:
2797 *
2798 * The #GtkSourceSearchSettings associated to the search context.
2799 *
2800 * This property is construct-only since version 4.0.
2801 *
2802 * Since: 3.10
2803 */
2804 g_object_class_install_property (object_class,
2805 PROP_SETTINGS,
2806 g_param_spec_object ("settings",
2807 "Settings",
2808 "The associated GtkSourceSearchSettings",
2809 GTK_SOURCE_TYPE_SEARCH_SETTINGS,
2810 G_PARAM_READWRITE |
2811 G_PARAM_CONSTRUCT_ONLY |
2812 G_PARAM_STATIC_STRINGS));
2813
2814 /**
2815 * GtkSourceSearchContext:highlight:
2816 *
2817 * Highlight the search occurrences.
2818 *
2819 * Since: 3.10
2820 */
2821 g_object_class_install_property (object_class,
2822 PROP_HIGHLIGHT,
2823 g_param_spec_boolean ("highlight",
2824 "Highlight",
2825 "Highlight search occurrences",
2826 TRUE,
2827 G_PARAM_READWRITE |
2828 G_PARAM_CONSTRUCT |
2829 G_PARAM_STATIC_STRINGS));
2830
2831 /**
2832 * GtkSourceSearchContext:match-style:
2833 *
2834 * A #GtkSourceStyle, or %NULL for theme's scheme default style.
2835 *
2836 * Since: 3.16
2837 */
2838 g_object_class_install_property (object_class,
2839 PROP_MATCH_STYLE,
2840 g_param_spec_object ("match-style",
2841 "Match style",
2842 "The text style for matches",
2843 GTK_SOURCE_TYPE_STYLE,
2844 G_PARAM_READWRITE |
2845 G_PARAM_CONSTRUCT |
2846 G_PARAM_STATIC_STRINGS));
2847
2848 /**
2849 * GtkSourceSearchContext:occurrences-count:
2850 *
2851 * The total number of search occurrences. If the search is disabled,
2852 * the value is 0. If the buffer is not already fully scanned, the value
2853 * is -1.
2854 *
2855 * Since: 3.10
2856 */
2857 g_object_class_install_property (object_class,
2858 PROP_OCCURRENCES_COUNT,
2859 g_param_spec_int ("occurrences-count",
2860 "Occurrences count",
2861 "Total number of search occurrences",
2862 -1,
2863 G_MAXINT,
2864 0,
2865 G_PARAM_READABLE |
2866 G_PARAM_STATIC_STRINGS));
2867
2868 /**
2869 * GtkSourceSearchContext:regex-error:
2870 *
2871 * If the regex search pattern doesn't follow all the rules, this
2872 * #GError property will be set. If the pattern is valid, the value is
2873 * %NULL.
2874 *
2875 * Free with g_error_free().
2876 *
2877 * Since: 3.10
2878 */
2879 g_object_class_install_property (object_class,
2880 PROP_REGEX_ERROR,
2881 g_param_spec_pointer ("regex-error",
2882 "Regex error",
2883 "Regular expression error",
2884 G_PARAM_READABLE |
2885 G_PARAM_STATIC_STRINGS));
2886 }
2887
2888 static void
gtk_source_search_context_init(GtkSourceSearchContext * search)2889 gtk_source_search_context_init (GtkSourceSearchContext *search)
2890 {
2891 search->priv = gtk_source_search_context_get_instance_private (search);
2892 }
2893
2894 /**
2895 * gtk_source_search_context_new:
2896 * @buffer: a #GtkSourceBuffer.
2897 * @settings: (nullable): a #GtkSourceSearchSettings, or %NULL.
2898 *
2899 * Creates a new search context, associated with @buffer, and customized with
2900 * @settings. If @settings is %NULL, a new #GtkSourceSearchSettings object will
2901 * be created, that you can retrieve with
2902 * gtk_source_search_context_get_settings().
2903 *
2904 * Returns: a new search context.
2905 * Since: 3.10
2906 */
2907 GtkSourceSearchContext *
gtk_source_search_context_new(GtkSourceBuffer * buffer,GtkSourceSearchSettings * settings)2908 gtk_source_search_context_new (GtkSourceBuffer *buffer,
2909 GtkSourceSearchSettings *settings)
2910 {
2911 g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
2912 g_return_val_if_fail (settings == NULL || GTK_SOURCE_IS_SEARCH_SETTINGS (settings), NULL);
2913
2914 return g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT,
2915 "buffer", buffer,
2916 "settings", settings,
2917 NULL);
2918 }
2919
2920 /**
2921 * gtk_source_search_context_get_buffer:
2922 * @search: a #GtkSourceSearchContext.
2923 *
2924 * Returns: (transfer none): the associated buffer.
2925 * Since: 3.10
2926 */
2927 GtkSourceBuffer *
gtk_source_search_context_get_buffer(GtkSourceSearchContext * search)2928 gtk_source_search_context_get_buffer (GtkSourceSearchContext *search)
2929 {
2930 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), NULL);
2931
2932 return GTK_SOURCE_BUFFER (search->priv->buffer);
2933 }
2934
2935 /**
2936 * gtk_source_search_context_get_settings:
2937 * @search: a #GtkSourceSearchContext.
2938 *
2939 * Returns: (transfer none): the search settings.
2940 * Since: 3.10
2941 */
2942 GtkSourceSearchSettings *
gtk_source_search_context_get_settings(GtkSourceSearchContext * search)2943 gtk_source_search_context_get_settings (GtkSourceSearchContext *search)
2944 {
2945 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), NULL);
2946
2947 return search->priv->settings;
2948 }
2949
2950 /**
2951 * gtk_source_search_context_get_highlight:
2952 * @search: a #GtkSourceSearchContext.
2953 *
2954 * Returns: whether to highlight the search occurrences.
2955 * Since: 3.10
2956 */
2957 gboolean
gtk_source_search_context_get_highlight(GtkSourceSearchContext * search)2958 gtk_source_search_context_get_highlight (GtkSourceSearchContext *search)
2959 {
2960 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), FALSE);
2961
2962 return search->priv->highlight;
2963 }
2964
2965 /**
2966 * gtk_source_search_context_set_highlight:
2967 * @search: a #GtkSourceSearchContext.
2968 * @highlight: the setting.
2969 *
2970 * Enables or disables the search occurrences highlighting.
2971 *
2972 * Since: 3.10
2973 */
2974 void
gtk_source_search_context_set_highlight(GtkSourceSearchContext * search,gboolean highlight)2975 gtk_source_search_context_set_highlight (GtkSourceSearchContext *search,
2976 gboolean highlight)
2977 {
2978 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search));
2979
2980 highlight = highlight != FALSE;
2981
2982 if (search->priv->highlight != highlight)
2983 {
2984 search->priv->highlight = highlight;
2985 sync_found_tag (search);
2986
2987 g_object_notify (G_OBJECT (search), "highlight");
2988 }
2989 }
2990
2991 /**
2992 * gtk_source_search_context_get_match_style:
2993 * @search: a #GtkSourceSearchContext.
2994 *
2995 * Returns: (transfer none): the #GtkSourceStyle to apply on search matches.
2996 *
2997 * Since: 3.16
2998 */
2999 GtkSourceStyle *
gtk_source_search_context_get_match_style(GtkSourceSearchContext * search)3000 gtk_source_search_context_get_match_style (GtkSourceSearchContext *search)
3001 {
3002 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), NULL);
3003
3004 return search->priv->match_style;
3005 }
3006
3007 /**
3008 * gtk_source_search_context_set_match_style:
3009 * @search: a #GtkSourceSearchContext.
3010 * @match_style: (nullable): a #GtkSourceStyle, or %NULL.
3011 *
3012 * Set the style to apply on search matches. If @match_style is %NULL, default
3013 * theme's scheme 'match-style' will be used.
3014 * To enable or disable the search highlighting, use
3015 * gtk_source_search_context_set_highlight().
3016 *
3017 * Since: 3.16
3018 */
3019 void
gtk_source_search_context_set_match_style(GtkSourceSearchContext * search,GtkSourceStyle * match_style)3020 gtk_source_search_context_set_match_style (GtkSourceSearchContext *search,
3021 GtkSourceStyle *match_style)
3022 {
3023 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search));
3024 g_return_if_fail (match_style == NULL || GTK_SOURCE_IS_STYLE (match_style));
3025
3026 if (search->priv->match_style == match_style)
3027 {
3028 return;
3029 }
3030
3031 if (search->priv->match_style != NULL)
3032 {
3033 g_object_unref (search->priv->match_style);
3034 }
3035
3036 search->priv->match_style = match_style;
3037
3038 if (match_style != NULL)
3039 {
3040 g_object_ref (match_style);
3041 }
3042
3043 g_object_notify (G_OBJECT (search), "match-style");
3044 }
3045
3046 /**
3047 * gtk_source_search_context_get_regex_error:
3048 * @search: a #GtkSourceSearchContext.
3049 *
3050 * Regular expression patterns must follow certain rules. If
3051 * #GtkSourceSearchSettings:search-text breaks a rule, the error can be retrieved
3052 * with this function. The error domain is #G_REGEX_ERROR.
3053 *
3054 * Free the return value with g_error_free().
3055 *
3056 * Returns: (nullable): the #GError, or %NULL if the pattern is valid.
3057 * Since: 3.10
3058 */
3059 GError *
gtk_source_search_context_get_regex_error(GtkSourceSearchContext * search)3060 gtk_source_search_context_get_regex_error (GtkSourceSearchContext *search)
3061 {
3062 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), NULL);
3063
3064 if (search->priv->regex_error == NULL)
3065 {
3066 return NULL;
3067 }
3068
3069 return g_error_copy (search->priv->regex_error);
3070 }
3071
3072 /**
3073 * gtk_source_search_context_get_occurrences_count:
3074 * @search: a #GtkSourceSearchContext.
3075 *
3076 * Gets the total number of search occurrences. If the buffer is not already
3077 * fully scanned, the total number of occurrences is unknown, and -1 is
3078 * returned.
3079 *
3080 * Returns: the total number of search occurrences, or -1 if unknown.
3081 * Since: 3.10
3082 */
3083 gint
gtk_source_search_context_get_occurrences_count(GtkSourceSearchContext * search)3084 gtk_source_search_context_get_occurrences_count (GtkSourceSearchContext *search)
3085 {
3086 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), -1);
3087
3088 if (!gtk_source_region_is_empty (search->priv->scan_region))
3089 {
3090 return -1;
3091 }
3092
3093 return search->priv->occurrences_count;
3094 }
3095
3096 /**
3097 * gtk_source_search_context_get_occurrence_position:
3098 * @search: a #GtkSourceSearchContext.
3099 * @match_start: the start of the occurrence.
3100 * @match_end: the end of the occurrence.
3101 *
3102 * Gets the position of a search occurrence. If the buffer is not already fully
3103 * scanned, the position may be unknown, and -1 is returned. If 0 is returned,
3104 * it means that this part of the buffer has already been scanned, and that
3105 * @match_start and @match_end don't delimit an occurrence.
3106 *
3107 * Returns: the position of the search occurrence. The first occurrence has the
3108 * position 1 (not 0). Returns 0 if @match_start and @match_end don't delimit
3109 * an occurrence. Returns -1 if the position is not yet known.
3110 *
3111 * Since: 3.10
3112 */
3113 gint
gtk_source_search_context_get_occurrence_position(GtkSourceSearchContext * search,const GtkTextIter * match_start,const GtkTextIter * match_end)3114 gtk_source_search_context_get_occurrence_position (GtkSourceSearchContext *search,
3115 const GtkTextIter *match_start,
3116 const GtkTextIter *match_end)
3117 {
3118 GtkTextIter m_start;
3119 GtkTextIter m_end;
3120 GtkTextIter iter;
3121 gboolean found;
3122 gint position = 0;
3123 GtkSourceRegion *region;
3124 gboolean empty;
3125
3126 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), -1);
3127 g_return_val_if_fail (match_start != NULL, -1);
3128 g_return_val_if_fail (match_end != NULL, -1);
3129
3130 if (search->priv->buffer == NULL)
3131 {
3132 return -1;
3133 }
3134
3135 /* Verify that the [match_start; match_end] region has been scanned. */
3136
3137 if (search->priv->scan_region != NULL)
3138 {
3139 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
3140 match_start,
3141 match_end);
3142
3143 empty = gtk_source_region_is_empty (region);
3144
3145 g_clear_object (®ion);
3146
3147 if (!empty)
3148 {
3149 return -1;
3150 }
3151 }
3152
3153 /* Verify that the occurrence is correct. */
3154
3155 found = smart_forward_search_without_scanning (search,
3156 match_start,
3157 &m_start,
3158 &m_end,
3159 match_end);
3160
3161 if (!found ||
3162 !gtk_text_iter_equal (match_start, &m_start) ||
3163 !gtk_text_iter_equal (match_end, &m_end))
3164 {
3165 return 0;
3166 }
3167
3168 /* Verify that the scan region is empty between the start of the buffer
3169 * and the end of the occurrence.
3170 */
3171
3172 gtk_text_buffer_get_start_iter (search->priv->buffer, &iter);
3173
3174 if (search->priv->scan_region != NULL)
3175 {
3176 region = gtk_source_region_intersect_subregion (search->priv->scan_region,
3177 &iter,
3178 match_end);
3179
3180 empty = gtk_source_region_is_empty (region);
3181
3182 g_clear_object (®ion);
3183
3184 if (!empty)
3185 {
3186 return -1;
3187 }
3188 }
3189
3190 /* Everything is fine, count the number of previous occurrences. */
3191
3192 while (smart_forward_search_without_scanning (search, &iter, &m_start, &m_end, match_start))
3193 {
3194 position++;
3195 iter = m_end;
3196 }
3197
3198 return position + 1;
3199 }
3200
3201 /**
3202 * gtk_source_search_context_forward:
3203 * @search: a #GtkSourceSearchContext.
3204 * @iter: start of search.
3205 * @match_start: (out) (optional): return location for start of match, or %NULL.
3206 * @match_end: (out) (optional): return location for end of match, or %NULL.
3207 * @has_wrapped_around: (out) (optional): return location to know whether the
3208 * search has wrapped around, or %NULL.
3209 *
3210 * Synchronous forward search. It is recommended to use the asynchronous
3211 * functions instead, to not block the user interface. However, if you are sure
3212 * that the @buffer is small, this function is more convenient to use.
3213 *
3214 * If the #GtkSourceSearchSettings:wrap-around property is %FALSE, this function
3215 * doesn't try to wrap around.
3216 *
3217 * The @has_wrapped_around out parameter is set independently of whether a match
3218 * is found. So if this function returns %FALSE, @has_wrapped_around will have
3219 * the same value as the #GtkSourceSearchSettings:wrap-around property.
3220 *
3221 * Returns: whether a match was found.
3222 * Since: 4.0
3223 */
3224 gboolean
gtk_source_search_context_forward(GtkSourceSearchContext * search,const GtkTextIter * iter,GtkTextIter * match_start,GtkTextIter * match_end,gboolean * has_wrapped_around)3225 gtk_source_search_context_forward (GtkSourceSearchContext *search,
3226 const GtkTextIter *iter,
3227 GtkTextIter *match_start,
3228 GtkTextIter *match_end,
3229 gboolean *has_wrapped_around)
3230 {
3231 GtkTextIter m_start;
3232 GtkTextIter m_end;
3233 gboolean found;
3234
3235 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), FALSE);
3236 g_return_val_if_fail (iter != NULL, FALSE);
3237
3238 if (has_wrapped_around != NULL)
3239 {
3240 *has_wrapped_around = FALSE;
3241 }
3242
3243 if (search->priv->buffer == NULL)
3244 {
3245 return FALSE;
3246 }
3247
3248 found = smart_forward_search (search, iter, &m_start, &m_end);
3249
3250 if (!found && gtk_source_search_settings_get_wrap_around (search->priv->settings))
3251 {
3252 GtkTextIter start_iter;
3253 gtk_text_buffer_get_start_iter (search->priv->buffer, &start_iter);
3254
3255 found = smart_forward_search (search, &start_iter, &m_start, &m_end);
3256
3257 if (has_wrapped_around != NULL)
3258 {
3259 *has_wrapped_around = TRUE;
3260 }
3261 }
3262
3263 if (found && match_start != NULL)
3264 {
3265 *match_start = m_start;
3266 }
3267
3268 if (found && match_end != NULL)
3269 {
3270 *match_end = m_end;
3271 }
3272
3273 return found;
3274 }
3275
3276 /**
3277 * gtk_source_search_context_forward_async:
3278 * @search: a #GtkSourceSearchContext.
3279 * @iter: start of search.
3280 * @cancellable: (nullable): a #GCancellable, or %NULL.
3281 * @callback: a #GAsyncReadyCallback to call when the operation is finished.
3282 * @user_data: the data to pass to the @callback function.
3283 *
3284 * The asynchronous version of gtk_source_search_context_forward().
3285 *
3286 * See the documentation of gtk_source_search_context_forward() for more
3287 * details.
3288 *
3289 * See the #GAsyncResult documentation to know how to use this function.
3290 *
3291 * If the operation is cancelled, the @callback will only be called if
3292 * @cancellable was not %NULL. gtk_source_search_context_forward_async() takes
3293 * ownership of @cancellable, so you can unref it after calling this function.
3294 *
3295 * Since: 3.10
3296 */
3297 void
gtk_source_search_context_forward_async(GtkSourceSearchContext * search,const GtkTextIter * iter,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3298 gtk_source_search_context_forward_async (GtkSourceSearchContext *search,
3299 const GtkTextIter *iter,
3300 GCancellable *cancellable,
3301 GAsyncReadyCallback callback,
3302 gpointer user_data)
3303 {
3304 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search));
3305 g_return_if_fail (iter != NULL);
3306
3307 if (search->priv->buffer == NULL)
3308 {
3309 return;
3310 }
3311
3312 clear_task (search);
3313 search->priv->task = g_task_new (search, cancellable, callback, user_data);
3314
3315 smart_forward_search_async (search, iter, FALSE);
3316 }
3317
3318 /**
3319 * gtk_source_search_context_forward_finish:
3320 * @search: a #GtkSourceSearchContext.
3321 * @result: a #GAsyncResult.
3322 * @match_start: (out) (optional): return location for start of match, or %NULL.
3323 * @match_end: (out) (optional): return location for end of match, or %NULL.
3324 * @has_wrapped_around: (out) (optional): return location to know whether the
3325 * search has wrapped around, or %NULL.
3326 * @error: a #GError, or %NULL.
3327 *
3328 * Finishes a forward search started with
3329 * gtk_source_search_context_forward_async().
3330 *
3331 * See the documentation of gtk_source_search_context_forward() for more
3332 * details.
3333 *
3334 * Returns: whether a match was found.
3335 * Since: 4.0
3336 */
3337 gboolean
gtk_source_search_context_forward_finish(GtkSourceSearchContext * search,GAsyncResult * result,GtkTextIter * match_start,GtkTextIter * match_end,gboolean * has_wrapped_around,GError ** error)3338 gtk_source_search_context_forward_finish (GtkSourceSearchContext *search,
3339 GAsyncResult *result,
3340 GtkTextIter *match_start,
3341 GtkTextIter *match_end,
3342 gboolean *has_wrapped_around,
3343 GError **error)
3344 {
3345 ForwardBackwardData *data;
3346 gboolean found;
3347
3348 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), FALSE);
3349
3350 if (has_wrapped_around != NULL)
3351 {
3352 *has_wrapped_around = FALSE;
3353 }
3354
3355 if (search->priv->buffer == NULL)
3356 {
3357 return FALSE;
3358 }
3359
3360 g_return_val_if_fail (g_task_is_valid (result, search), FALSE);
3361
3362 data = g_task_propagate_pointer (G_TASK (result), error);
3363
3364 if (data == NULL)
3365 {
3366 return FALSE;
3367 }
3368
3369 found = data->found;
3370
3371 if (found)
3372 {
3373 if (match_start != NULL)
3374 {
3375 gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
3376 match_start,
3377 data->match_start);
3378 }
3379
3380 if (match_end != NULL)
3381 {
3382 gtk_text_buffer_get_iter_at_mark (search->priv->buffer,
3383 match_end,
3384 data->match_end);
3385 }
3386 }
3387
3388 if (has_wrapped_around != NULL)
3389 {
3390 *has_wrapped_around = data->wrapped_around;
3391 }
3392
3393 forward_backward_data_free (data);
3394 return found;
3395 }
3396
3397 /**
3398 * gtk_source_search_context_backward:
3399 * @search: a #GtkSourceSearchContext.
3400 * @iter: start of search.
3401 * @match_start: (out) (optional): return location for start of match, or %NULL.
3402 * @match_end: (out) (optional): return location for end of match, or %NULL.
3403 * @has_wrapped_around: (out) (optional): return location to know whether the
3404 * search has wrapped around, or %NULL.
3405 *
3406 * Synchronous backward search. It is recommended to use the asynchronous
3407 * functions instead, to not block the user interface. However, if you are sure
3408 * that the @buffer is small, this function is more convenient to use.
3409 *
3410 * If the #GtkSourceSearchSettings:wrap-around property is %FALSE, this function
3411 * doesn't try to wrap around.
3412 *
3413 * The @has_wrapped_around out parameter is set independently of whether a match
3414 * is found. So if this function returns %FALSE, @has_wrapped_around will have
3415 * the same value as the #GtkSourceSearchSettings:wrap-around property.
3416 *
3417 * Returns: whether a match was found.
3418 * Since: 4.0
3419 */
3420 gboolean
gtk_source_search_context_backward(GtkSourceSearchContext * search,const GtkTextIter * iter,GtkTextIter * match_start,GtkTextIter * match_end,gboolean * has_wrapped_around)3421 gtk_source_search_context_backward (GtkSourceSearchContext *search,
3422 const GtkTextIter *iter,
3423 GtkTextIter *match_start,
3424 GtkTextIter *match_end,
3425 gboolean *has_wrapped_around)
3426 {
3427 GtkTextIter m_start;
3428 GtkTextIter m_end;
3429 gboolean found;
3430
3431 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), FALSE);
3432 g_return_val_if_fail (iter != NULL, FALSE);
3433
3434 if (has_wrapped_around != NULL)
3435 {
3436 *has_wrapped_around = FALSE;
3437 }
3438
3439 if (search->priv->buffer == NULL)
3440 {
3441 return FALSE;
3442 }
3443
3444 found = smart_backward_search (search, iter, &m_start, &m_end);
3445
3446 if (!found && gtk_source_search_settings_get_wrap_around (search->priv->settings))
3447 {
3448 GtkTextIter end_iter;
3449
3450 gtk_text_buffer_get_end_iter (search->priv->buffer, &end_iter);
3451
3452 found = smart_backward_search (search, &end_iter, &m_start, &m_end);
3453
3454 if (has_wrapped_around != NULL)
3455 {
3456 *has_wrapped_around = TRUE;
3457 }
3458 }
3459
3460 if (found && match_start != NULL)
3461 {
3462 *match_start = m_start;
3463 }
3464
3465 if (found && match_end != NULL)
3466 {
3467 *match_end = m_end;
3468 }
3469
3470 return found;
3471 }
3472
3473 /**
3474 * gtk_source_search_context_backward_async:
3475 * @search: a #GtkSourceSearchContext.
3476 * @iter: start of search.
3477 * @cancellable: (nullable): a #GCancellable, or %NULL.
3478 * @callback: a #GAsyncReadyCallback to call when the operation is finished.
3479 * @user_data: the data to pass to the @callback function.
3480 *
3481 * The asynchronous version of gtk_source_search_context_backward().
3482 *
3483 * See the documentation of gtk_source_search_context_backward() for more
3484 * details.
3485 *
3486 * See the #GAsyncResult documentation to know how to use this function.
3487 *
3488 * If the operation is cancelled, the @callback will only be called if
3489 * @cancellable was not %NULL. gtk_source_search_context_backward_async() takes
3490 * ownership of @cancellable, so you can unref it after calling this function.
3491 *
3492 * Since: 3.10
3493 */
3494 void
gtk_source_search_context_backward_async(GtkSourceSearchContext * search,const GtkTextIter * iter,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3495 gtk_source_search_context_backward_async (GtkSourceSearchContext *search,
3496 const GtkTextIter *iter,
3497 GCancellable *cancellable,
3498 GAsyncReadyCallback callback,
3499 gpointer user_data)
3500 {
3501 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search));
3502 g_return_if_fail (iter != NULL);
3503
3504 if (search->priv->buffer == NULL)
3505 {
3506 return;
3507 }
3508
3509 clear_task (search);
3510 search->priv->task = g_task_new (search, cancellable, callback, user_data);
3511
3512 smart_backward_search_async (search, iter, FALSE);
3513 }
3514
3515 /**
3516 * gtk_source_search_context_backward_finish:
3517 * @search: a #GtkSourceSearchContext.
3518 * @result: a #GAsyncResult.
3519 * @match_start: (out) (optional): return location for start of match, or %NULL.
3520 * @match_end: (out) (optional): return location for end of match, or %NULL.
3521 * @has_wrapped_around: (out) (optional): return location to know whether the
3522 * search has wrapped around, or %NULL.
3523 * @error: a #GError, or %NULL.
3524 *
3525 * Finishes a backward search started with
3526 * gtk_source_search_context_backward_async().
3527 *
3528 * See the documentation of gtk_source_search_context_backward() for more
3529 * details.
3530 *
3531 * Returns: whether a match was found.
3532 * Since: 4.0
3533 */
3534 gboolean
gtk_source_search_context_backward_finish(GtkSourceSearchContext * search,GAsyncResult * result,GtkTextIter * match_start,GtkTextIter * match_end,gboolean * has_wrapped_around,GError ** error)3535 gtk_source_search_context_backward_finish (GtkSourceSearchContext *search,
3536 GAsyncResult *result,
3537 GtkTextIter *match_start,
3538 GtkTextIter *match_end,
3539 gboolean *has_wrapped_around,
3540 GError **error)
3541 {
3542 return gtk_source_search_context_forward_finish (search,
3543 result,
3544 match_start,
3545 match_end,
3546 has_wrapped_around,
3547 error);
3548 }
3549
3550 /* If correctly replaced, returns %TRUE and @match_end is updated to point to
3551 * the replacement end.
3552 */
3553 static gboolean
regex_replace(GtkSourceSearchContext * search,const GtkTextIter * match_start,GtkTextIter * match_end,const gchar * replace,GError ** error)3554 regex_replace (GtkSourceSearchContext *search,
3555 const GtkTextIter *match_start,
3556 GtkTextIter *match_end,
3557 const gchar *replace,
3558 GError **error)
3559 {
3560 GtkTextIter real_start;
3561 GtkTextIter real_end;
3562 GtkTextIter match_start_check;
3563 GtkTextIter match_end_check;
3564 GtkTextIter match_start_copy;
3565 gint start_pos;
3566 gchar *subject;
3567 gchar *suffix;
3568 gchar *subject_replaced;
3569 GRegexMatchFlags match_options;
3570 GError *tmp_error = NULL;
3571 gboolean replaced = FALSE;
3572
3573 if (search->priv->regex == NULL ||
3574 search->priv->regex_error != NULL)
3575 {
3576 return FALSE;
3577 }
3578
3579 regex_search_get_real_start (search, match_start, &real_start, &start_pos);
3580 g_assert_cmpint (start_pos, >=, 0);
3581
3582 if (!basic_forward_regex_search (search,
3583 match_start,
3584 &match_start_check,
3585 &match_end_check,
3586 &real_end,
3587 match_end))
3588 {
3589 g_assert_not_reached ();
3590 }
3591
3592 g_assert (gtk_text_iter_equal (match_start, &match_start_check));
3593 g_assert (gtk_text_iter_equal (match_end, &match_end_check));
3594
3595 subject = gtk_text_iter_get_visible_text (&real_start, &real_end);
3596
3597 suffix = gtk_text_iter_get_visible_text (match_end, &real_end);
3598 if (suffix == NULL)
3599 {
3600 suffix = g_strdup ("");
3601 }
3602
3603 match_options = regex_search_get_match_options (&real_start, &real_end);
3604 match_options |= G_REGEX_MATCH_ANCHORED;
3605
3606 subject_replaced = g_regex_replace (search->priv->regex,
3607 subject,
3608 -1,
3609 start_pos,
3610 replace,
3611 match_options,
3612 &tmp_error);
3613
3614 if (tmp_error != NULL)
3615 {
3616 g_propagate_error (error, tmp_error);
3617 goto end;
3618 }
3619
3620 g_return_val_if_fail (g_str_has_suffix (subject_replaced, suffix), FALSE);
3621
3622 /* Truncate subject_replaced to not contain the suffix, so we can
3623 * replace only [match_start, match_end], not [match_start, real_end].
3624 * The first solution is slightly simpler, and avoids the need to
3625 * re-scan [match_end, real_end] for matches, which is convenient for a
3626 * replace all.
3627 */
3628 subject_replaced[strlen (subject_replaced) - strlen (suffix)] = '\0';
3629 g_return_val_if_fail (strlen (subject_replaced) >= (guint)start_pos, FALSE);
3630
3631 match_start_copy = *match_start;
3632
3633 gtk_text_buffer_begin_user_action (search->priv->buffer);
3634 gtk_text_buffer_delete (search->priv->buffer, &match_start_copy, match_end);
3635 gtk_text_buffer_insert (search->priv->buffer, match_end, subject_replaced + start_pos, -1);
3636 gtk_text_buffer_end_user_action (search->priv->buffer);
3637
3638 replaced = TRUE;
3639
3640 end:
3641 g_free (subject);
3642 g_free (suffix);
3643 g_free (subject_replaced);
3644 return replaced;
3645 }
3646
3647 /**
3648 * gtk_source_search_context_replace:
3649 * @search: a #GtkSourceSearchContext.
3650 * @match_start: the start of the match to replace.
3651 * @match_end: the end of the match to replace.
3652 * @replace: the replacement text.
3653 * @replace_length: the length of @replace in bytes, or -1.
3654 * @error: location to a #GError, or %NULL to ignore errors.
3655 *
3656 * Replaces a search match by another text. If @match_start and @match_end
3657 * doesn't correspond to a search match, %FALSE is returned.
3658 *
3659 * @match_start and @match_end iters are revalidated to point to the replacement
3660 * text boundaries.
3661 *
3662 * For a regular expression replacement, you can check if @replace is valid by
3663 * calling g_regex_check_replacement(). The @replace text can contain
3664 * backreferences; read the g_regex_replace() documentation for more details.
3665 *
3666 * Returns: whether the match has been replaced.
3667 * Since: 4.0
3668 */
3669 gboolean
gtk_source_search_context_replace(GtkSourceSearchContext * search,GtkTextIter * match_start,GtkTextIter * match_end,const gchar * replace,gint replace_length,GError ** error)3670 gtk_source_search_context_replace (GtkSourceSearchContext *search,
3671 GtkTextIter *match_start,
3672 GtkTextIter *match_end,
3673 const gchar *replace,
3674 gint replace_length,
3675 GError **error)
3676 {
3677 GtkTextIter start;
3678 GtkTextIter end;
3679 GtkTextMark *start_mark;
3680 gboolean replaced = FALSE;
3681
3682 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), FALSE);
3683 g_return_val_if_fail (match_start != NULL, FALSE);
3684 g_return_val_if_fail (match_end != NULL, FALSE);
3685 g_return_val_if_fail (replace != NULL, FALSE);
3686 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
3687
3688 if (search->priv->buffer == NULL)
3689 {
3690 return FALSE;
3691 }
3692
3693 if (!smart_forward_search (search, match_start, &start, &end))
3694 {
3695 return FALSE;
3696 }
3697
3698 if (!gtk_text_iter_equal (match_start, &start) ||
3699 !gtk_text_iter_equal (match_end, &end))
3700 {
3701 return FALSE;
3702 }
3703
3704 start_mark = gtk_text_buffer_create_mark (search->priv->buffer, NULL, &start, TRUE);
3705
3706 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
3707 {
3708 replaced = regex_replace (search, &start, &end, replace, error);
3709 }
3710 else
3711 {
3712 gtk_text_buffer_begin_user_action (search->priv->buffer);
3713 gtk_text_buffer_delete (search->priv->buffer, &start, &end);
3714 gtk_text_buffer_insert (search->priv->buffer, &end, replace, replace_length);
3715 gtk_text_buffer_end_user_action (search->priv->buffer);
3716
3717 replaced = TRUE;
3718 }
3719
3720 if (replaced)
3721 {
3722 gtk_text_buffer_get_iter_at_mark (search->priv->buffer, match_start, start_mark);
3723 *match_end = end;
3724 }
3725
3726 gtk_text_buffer_delete_mark (search->priv->buffer, start_mark);
3727
3728 return replaced;
3729 }
3730
3731 /**
3732 * gtk_source_search_context_replace_all:
3733 * @search: a #GtkSourceSearchContext.
3734 * @replace: the replacement text.
3735 * @replace_length: the length of @replace in bytes, or -1.
3736 * @error: location to a #GError, or %NULL to ignore errors.
3737 *
3738 * Replaces all search matches by another text. It is a synchronous function, so
3739 * it can block the user interface.
3740 *
3741 * For a regular expression replacement, you can check if @replace is valid by
3742 * calling g_regex_check_replacement(). The @replace text can contain
3743 * backreferences; read the g_regex_replace() documentation for more details.
3744 *
3745 * Returns: the number of replaced matches.
3746 * Since: 3.10
3747 */
3748 guint
gtk_source_search_context_replace_all(GtkSourceSearchContext * search,const gchar * replace,gint replace_length,GError ** error)3749 gtk_source_search_context_replace_all (GtkSourceSearchContext *search,
3750 const gchar *replace,
3751 gint replace_length,
3752 GError **error)
3753 {
3754 GtkTextIter iter;
3755 GtkTextIter match_start;
3756 GtkTextIter match_end;
3757 guint nb_matches_replaced = 0;
3758 gboolean highlight_matching_brackets;
3759 gboolean has_regex_references = FALSE;
3760
3761 g_return_val_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search), 0);
3762 g_return_val_if_fail (replace != NULL, 0);
3763 g_return_val_if_fail (error == NULL || *error == NULL, 0);
3764
3765 if (search->priv->buffer == NULL)
3766 {
3767 return 0;
3768 }
3769
3770 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
3771 {
3772 GError *tmp_error = NULL;
3773
3774 if (search->priv->regex == NULL ||
3775 search->priv->regex_error != NULL)
3776 {
3777 return 0;
3778 }
3779
3780 g_regex_check_replacement (replace,
3781 &has_regex_references,
3782 &tmp_error);
3783
3784 if (tmp_error != NULL)
3785 {
3786 g_propagate_error (error, tmp_error);
3787 return 0;
3788 }
3789 }
3790
3791 g_signal_handlers_block_by_func (search->priv->buffer, insert_text_before_cb, search);
3792 g_signal_handlers_block_by_func (search->priv->buffer, insert_text_after_cb, search);
3793 g_signal_handlers_block_by_func (search->priv->buffer, delete_range_before_cb, search);
3794 g_signal_handlers_block_by_func (search->priv->buffer, delete_range_after_cb, search);
3795
3796 highlight_matching_brackets =
3797 gtk_source_buffer_get_highlight_matching_brackets (GTK_SOURCE_BUFFER (search->priv->buffer));
3798
3799 gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (search->priv->buffer),
3800 FALSE);
3801
3802 _gtk_source_buffer_save_and_clear_selection (GTK_SOURCE_BUFFER (search->priv->buffer));
3803
3804 gtk_text_buffer_get_start_iter (search->priv->buffer, &iter);
3805
3806 gtk_text_buffer_begin_user_action (search->priv->buffer);
3807
3808 while (smart_forward_search (search, &iter, &match_start, &match_end))
3809 {
3810 if (has_regex_references)
3811 {
3812 if (!regex_replace (search, &match_start, &match_end, replace, error))
3813 {
3814 break;
3815 }
3816 }
3817 else
3818 {
3819 gtk_text_buffer_delete (search->priv->buffer, &match_start, &match_end);
3820 gtk_text_buffer_insert (search->priv->buffer, &match_end, replace, replace_length);
3821 }
3822
3823 nb_matches_replaced++;
3824 iter = match_end;
3825 }
3826
3827 gtk_text_buffer_end_user_action (search->priv->buffer);
3828
3829 _gtk_source_buffer_restore_selection (GTK_SOURCE_BUFFER (search->priv->buffer));
3830
3831 gtk_source_buffer_set_highlight_matching_brackets (GTK_SOURCE_BUFFER (search->priv->buffer),
3832 highlight_matching_brackets);
3833
3834 g_signal_handlers_unblock_by_func (search->priv->buffer, insert_text_before_cb, search);
3835 g_signal_handlers_unblock_by_func (search->priv->buffer, insert_text_after_cb, search);
3836 g_signal_handlers_unblock_by_func (search->priv->buffer, delete_range_before_cb, search);
3837 g_signal_handlers_unblock_by_func (search->priv->buffer, delete_range_after_cb, search);
3838
3839 update (search);
3840
3841 return nb_matches_replaced;
3842 }
3843
3844 /* Highlight the [start,end] region in priority. */
3845 void
_gtk_source_search_context_update_highlight(GtkSourceSearchContext * search,const GtkTextIter * start,const GtkTextIter * end,gboolean synchronous)3846 _gtk_source_search_context_update_highlight (GtkSourceSearchContext *search,
3847 const GtkTextIter *start,
3848 const GtkTextIter *end,
3849 gboolean synchronous)
3850 {
3851 GtkSourceRegion *region_to_highlight = NULL;
3852
3853 g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search));
3854 g_return_if_fail (start != NULL);
3855 g_return_if_fail (end != NULL);
3856
3857 if (search->priv->buffer == NULL ||
3858 gtk_source_region_is_empty (search->priv->scan_region) ||
3859 !search->priv->highlight)
3860 {
3861 return;
3862 }
3863
3864 region_to_highlight = gtk_source_region_intersect_subregion (search->priv->scan_region,
3865 start,
3866 end);
3867
3868 if (gtk_source_region_is_empty (region_to_highlight))
3869 {
3870 goto out;
3871 }
3872
3873 if (!synchronous)
3874 {
3875 if (search->priv->high_priority_region == NULL)
3876 {
3877 search->priv->high_priority_region = region_to_highlight;
3878 region_to_highlight = NULL;
3879 }
3880 else
3881 {
3882 gtk_source_region_add_region (search->priv->high_priority_region,
3883 region_to_highlight);
3884 }
3885
3886 install_idle_scan (search);
3887 goto out;
3888 }
3889
3890 if (gtk_source_search_settings_get_regex_enabled (search->priv->settings))
3891 {
3892 GtkTextIter region_start;
3893
3894 if (!gtk_source_region_get_bounds (search->priv->scan_region,
3895 ®ion_start,
3896 NULL))
3897 {
3898 goto out;
3899 }
3900
3901 regex_search_scan_chunk (search, ®ion_start, end);
3902 }
3903 else
3904 {
3905 scan_all_region (search, region_to_highlight);
3906 }
3907
3908 out:
3909 g_clear_object (®ion_to_highlight);
3910 }
3911