| 1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- |
| 2 * @file gtksourceiter.h GTK+ Source iterator |
2 * gtksourceiter.c |
| 3 * @ingroup gtkui |
|
| 4 * |
|
| 5 * gaim |
|
| 6 * |
3 * |
| 7 * Gaim is the legal property of its developers, whose names are too numerous |
4 * Gaim is the legal property of its developers, whose names are too numerous |
| 8 * to list here. Please refer to the COPYRIGHT file distributed with this |
5 * to list here. Please refer to the COPYRIGHT file distributed with this |
| 9 * source distribution. |
6 * source distribution. |
| |
7 * |
| |
8 * The following copyright notice applies to this file: |
| |
9 * |
| |
10 * Copyright (C) 2000 - 2005 Paolo Maggi |
| |
11 * Copyright (C) 2002, 2003 Jeroen Zwartepoorte |
| 10 * |
12 * |
| 11 * This program is free software; you can redistribute it and/or modify |
13 * This program is free software; you can redistribute it and/or modify |
| 12 * it under the terms of the GNU Library General Public License as published by |
14 * it under the terms of the GNU Library General Public License as published by |
| 13 * the Free Software Foundation; either version 2 of the License, or |
15 * the Free Software Foundation; either version 2 of the License, or |
| 14 * (at your option) any later version. |
16 * (at your option) any later version. |
| 34 #include <string.h> |
36 #include <string.h> |
| 35 #include "gtksourceiter.h" |
37 #include "gtksourceiter.h" |
| 36 |
38 |
| 37 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC |
39 #define GTK_TEXT_UNKNOWN_CHAR 0xFFFC |
| 38 |
40 |
| 39 static gchar * |
41 /* this function acts like g_utf8_offset_to_pointer() except that if it finds a |
| |
42 * decomposable character it consumes the decomposition length from the given |
| |
43 * offset. So it's useful when the offset was calculated for the normalized |
| |
44 * version of str, but we need a pointer to str itself. */ |
| |
45 static const gchar * |
| |
46 pointer_from_offset_skipping_decomp (const gchar *str, gint offset) |
| |
47 { |
| |
48 gchar *casefold, *normal; |
| |
49 const gchar *p, *q; |
| |
50 |
| |
51 p = str; |
| |
52 while (offset > 0) |
| |
53 { |
| |
54 q = g_utf8_next_char (p); |
| |
55 casefold = g_utf8_casefold (p, q - p); |
| |
56 normal = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| |
57 offset -= g_utf8_strlen (normal, -1); |
| |
58 g_free (casefold); |
| |
59 g_free (normal); |
| |
60 p = q; |
| |
61 } |
| |
62 return p; |
| |
63 } |
| |
64 |
| |
65 static const gchar * |
| 40 g_utf8_strcasestr (const gchar *haystack, const gchar *needle) |
66 g_utf8_strcasestr (const gchar *haystack, const gchar *needle) |
| 41 { |
67 { |
| 42 gsize needle_len; |
68 gsize needle_len; |
| 43 gsize haystack_len; |
69 gsize haystack_len; |
| 44 gchar *ret = NULL; |
70 const gchar *ret = NULL; |
| 45 gchar *p; |
71 gchar *p; |
| 46 gchar *casefold; |
72 gchar *casefold; |
| 47 gchar *caseless_haystack; |
73 gchar *caseless_haystack; |
| 48 gint i; |
74 gint i; |
| 49 |
75 |
| 50 g_return_val_if_fail (haystack != NULL, NULL); |
76 g_return_val_if_fail (haystack != NULL, NULL); |
| 51 g_return_val_if_fail (needle != NULL, NULL); |
77 g_return_val_if_fail (needle != NULL, NULL); |
| 52 |
78 |
| 53 casefold = g_utf8_casefold (haystack, -1); |
79 casefold = g_utf8_casefold (haystack, -1); |
| 54 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
80 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 55 g_free (casefold); |
81 g_free (casefold); |
| 56 |
82 |
| 57 needle_len = g_utf8_strlen (needle, -1); |
83 needle_len = g_utf8_strlen (needle, -1); |
| 58 haystack_len = g_utf8_strlen (caseless_haystack, -1); |
84 haystack_len = g_utf8_strlen (caseless_haystack, -1); |
| 59 |
85 |
| 89 g_free (caseless_haystack); |
115 g_free (caseless_haystack); |
| 90 |
116 |
| 91 return ret; |
117 return ret; |
| 92 } |
118 } |
| 93 |
119 |
| 94 static gchar * |
120 static const gchar * |
| 95 g_utf8_strrcasestr (const gchar *haystack, const gchar *needle) |
121 g_utf8_strrcasestr (const gchar *haystack, const gchar *needle) |
| 96 { |
122 { |
| 97 gsize needle_len; |
123 gsize needle_len; |
| 98 gsize haystack_len; |
124 gsize haystack_len; |
| 99 gchar *ret = NULL; |
125 const gchar *ret = NULL; |
| 100 gchar *p; |
126 gchar *p; |
| 101 gchar *casefold; |
127 gchar *casefold; |
| 102 gchar *caseless_haystack; |
128 gchar *caseless_haystack; |
| 103 gint i; |
129 gint i; |
| 104 |
130 |
| 105 g_return_val_if_fail (haystack != NULL, NULL); |
131 g_return_val_if_fail (haystack != NULL, NULL); |
| 106 g_return_val_if_fail (needle != NULL, NULL); |
132 g_return_val_if_fail (needle != NULL, NULL); |
| 107 |
133 |
| 108 casefold = g_utf8_casefold (haystack, -1); |
134 casefold = g_utf8_casefold (haystack, -1); |
| 109 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
135 caseless_haystack = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 110 g_free (casefold); |
136 g_free (casefold); |
| 111 |
137 |
| 112 needle_len = g_utf8_strlen (needle, -1); |
138 needle_len = g_utf8_strlen (needle, -1); |
| 113 haystack_len = g_utf8_strlen (caseless_haystack, -1); |
139 haystack_len = g_utf8_strlen (caseless_haystack, -1); |
| 114 |
140 |
| 122 { |
148 { |
| 123 ret = NULL; |
149 ret = NULL; |
| 124 goto finally_1; |
150 goto finally_1; |
| 125 } |
151 } |
| 126 |
152 |
| 127 haystack_len = strlen (caseless_haystack); |
153 i = haystack_len - needle_len; |
| |
154 p = g_utf8_offset_to_pointer (caseless_haystack, i); |
| 128 needle_len = strlen (needle); |
155 needle_len = strlen (needle); |
| 129 p = (gchar *)caseless_haystack + haystack_len - needle_len; |
|
| 130 i = haystack_len - needle_len; |
|
| 131 |
156 |
| 132 while (p >= caseless_haystack) |
157 while (p >= caseless_haystack) |
| 133 { |
158 { |
| 134 if (strncasecmp (p, needle, needle_len) == 0) |
159 if (strncmp (p, needle, needle_len) == 0) |
| 135 { |
160 { |
| 136 ret = g_utf8_offset_to_pointer (haystack, i); |
161 ret = pointer_from_offset_skipping_decomp (haystack, i); |
| 137 goto finally_1; |
162 goto finally_1; |
| 138 } |
163 } |
| 139 |
164 |
| 140 p = g_utf8_prev_char (p); |
165 p = g_utf8_prev_char (p); |
| 141 i--; |
166 i--; |
| 162 g_return_val_if_fail (s2 != NULL, FALSE); |
187 g_return_val_if_fail (s2 != NULL, FALSE); |
| 163 g_return_val_if_fail (n1 > 0, FALSE); |
188 g_return_val_if_fail (n1 > 0, FALSE); |
| 164 g_return_val_if_fail (n2 > 0, FALSE); |
189 g_return_val_if_fail (n2 > 0, FALSE); |
| 165 |
190 |
| 166 casefold = g_utf8_casefold (s1, n1); |
191 casefold = g_utf8_casefold (s1, n1); |
| 167 normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
192 normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 168 g_free (casefold); |
193 g_free (casefold); |
| 169 |
194 |
| 170 casefold = g_utf8_casefold (s2, n2); |
195 casefold = g_utf8_casefold (s2, n2); |
| 171 normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
196 normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 172 g_free (casefold); |
197 g_free (casefold); |
| 173 |
198 |
| 174 len_s1 = strlen (normalized_s1); |
199 len_s1 = strlen (normalized_s1); |
| 175 len_s2 = strlen (normalized_s2); |
200 len_s2 = strlen (normalized_s2); |
| 176 |
201 |
| 188 |
213 |
| 189 static void |
214 static void |
| 190 forward_chars_with_skipping (GtkTextIter *iter, |
215 forward_chars_with_skipping (GtkTextIter *iter, |
| 191 gint count, |
216 gint count, |
| 192 gboolean skip_invisible, |
217 gboolean skip_invisible, |
| 193 gboolean skip_nontext) |
218 gboolean skip_nontext, |
| |
219 gboolean skip_decomp) |
| 194 { |
220 { |
| 195 gint i; |
221 gint i; |
| 196 |
222 |
| 197 g_return_if_fail (count >= 0); |
223 g_return_if_fail (count >= 0); |
| 198 |
224 |
| 199 i = count; |
225 i = count; |
| 200 |
226 |
| 201 while (i > 0) |
227 while (i > 0) |
| 202 { |
228 { |
| 203 gboolean ignored = FALSE; |
229 gboolean ignored = FALSE; |
| |
230 |
| |
231 /* minimal workaround to avoid the infinite loop of bug #168247. |
| |
232 * It doesn't fix the problemjust the symptom... |
| |
233 */ |
| |
234 if (gtk_text_iter_is_end (iter)) |
| |
235 return; |
| 204 |
236 |
| 205 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) |
237 if (skip_nontext && gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) |
| 206 ignored = TRUE; |
238 ignored = TRUE; |
| 207 |
239 |
| 208 #if 0 |
240 #if 0 |
| 209 if (!ignored && skip_invisible && |
241 if (!ignored && skip_invisible && |
| 210 _gtk_text_btree_char_is_invisible (iter)) |
242 /* _gtk_text_btree_char_is_invisible (iter)*/ FALSE) |
| 211 ignored = TRUE; |
243 ignored = TRUE; |
| 212 #endif |
244 #endif |
| |
245 |
| |
246 if (!ignored && skip_decomp) |
| |
247 { |
| |
248 /* being UTF8 correct sucks; this accounts for extra |
| |
249 offsets coming from canonical decompositions of |
| |
250 UTF8 characters (e.g. accented characters) which |
| |
251 g_utf8_normalize() performs */ |
| |
252 gchar *normal; |
| |
253 gchar buffer[6]; |
| |
254 gint buffer_len; |
| |
255 |
| |
256 buffer_len = g_unichar_to_utf8 (gtk_text_iter_get_char (iter), buffer); |
| |
257 normal = g_utf8_normalize (buffer, buffer_len, G_NORMALIZE_NFD); |
| |
258 i -= (g_utf8_strlen (normal, -1) - 1); |
| |
259 g_free (normal); |
| |
260 } |
| 213 |
261 |
| 214 gtk_text_iter_forward_char (iter); |
262 gtk_text_iter_forward_char (iter); |
| 215 |
263 |
| 216 if (!ignored) |
264 if (!ignored) |
| 217 --i; |
265 --i; |
| 290 next = *start; |
338 next = *start; |
| 291 |
339 |
| 292 /* If match start needs to be returned, set it to the |
340 /* If match start needs to be returned, set it to the |
| 293 * start of the search string. |
341 * start of the search string. |
| 294 */ |
342 */ |
| |
343 forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); |
| 295 if (match_start) |
344 if (match_start) |
| 296 { |
345 { |
| 297 *match_start = next; |
346 *match_start = next; |
| 298 |
|
| 299 forward_chars_with_skipping (match_start, offset, |
|
| 300 visible_only, !slice); |
|
| 301 } |
347 } |
| 302 |
348 |
| 303 /* Go to end of search string */ |
349 /* Go to end of search string */ |
| 304 offset += g_utf8_strlen (*lines, -1); |
350 forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); |
| 305 |
|
| 306 forward_chars_with_skipping (&next, offset, visible_only, !slice); |
|
| 307 |
351 |
| 308 g_free (line_text); |
352 g_free (line_text); |
| 309 |
353 |
| 310 ++lines; |
354 ++lines; |
| 311 |
355 |
| 387 } |
431 } |
| 388 |
432 |
| 389 /* Get offset to start of search string */ |
433 /* Get offset to start of search string */ |
| 390 offset = g_utf8_strlen (line_text, found - line_text); |
434 offset = g_utf8_strlen (line_text, found - line_text); |
| 391 |
435 |
| |
436 forward_chars_with_skipping (&next, offset, visible_only, !slice, FALSE); |
| |
437 |
| 392 /* If match start needs to be returned, set it to the |
438 /* If match start needs to be returned, set it to the |
| 393 * start of the search string. |
439 * start of the search string. |
| 394 */ |
440 */ |
| 395 if (match_start) |
441 if (match_start) |
| 396 { |
442 { |
| 397 *match_start = next; |
443 *match_start = next; |
| 398 gtk_text_iter_set_visible_line_offset (match_start, offset); |
|
| 399 } |
444 } |
| 400 |
445 |
| 401 /* Go to end of search string */ |
446 /* Go to end of search string */ |
| 402 offset += g_utf8_strlen (*lines, -1); |
447 forward_chars_with_skipping (&next, g_utf8_strlen (*lines, -1), visible_only, !slice, TRUE); |
| 403 |
|
| 404 forward_chars_with_skipping (&next, offset, visible_only, !slice); |
|
| 405 |
448 |
| 406 g_free (line_text); |
449 g_free (line_text); |
| 407 |
450 |
| 408 ++lines; |
451 ++lines; |
| 409 |
452 |
| 446 new_string = g_new (gchar, len + 1); |
489 new_string = g_new (gchar, len + 1); |
| 447 strncpy (new_string, string, len); |
490 strncpy (new_string, string, len); |
| 448 new_string[len] = 0; |
491 new_string[len] = 0; |
| 449 casefold = g_utf8_casefold (new_string, -1); |
492 casefold = g_utf8_casefold (new_string, -1); |
| 450 g_free (new_string); |
493 g_free (new_string); |
| 451 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
494 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 452 g_free (casefold); |
495 g_free (casefold); |
| 453 string_list = g_slist_prepend (string_list, new_string); |
496 string_list = g_slist_prepend (string_list, new_string); |
| 454 n++; |
497 n++; |
| 455 string = s + delimiter_len; |
498 string = s + delimiter_len; |
| 456 s = strstr (string, delimiter); |
499 s = strstr (string, delimiter); |
| 459 |
502 |
| 460 if (*string) |
503 if (*string) |
| 461 { |
504 { |
| 462 n++; |
505 n++; |
| 463 casefold = g_utf8_casefold (string, -1); |
506 casefold = g_utf8_casefold (string, -1); |
| 464 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL); |
507 new_string = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD); |
| 465 g_free (casefold); |
508 g_free (casefold); |
| 466 string_list = g_slist_prepend (string_list, new_string); |
509 string_list = g_slist_prepend (string_list, new_string); |
| 467 } |
510 } |
| 468 |
511 |
| 469 str_array = g_new (gchar*, n); |
512 str_array = g_new (gchar*, n); |
| 479 return str_array; |
522 return str_array; |
| 480 } |
523 } |
| 481 |
524 |
| 482 /** |
525 /** |
| 483 * gtk_source_iter_forward_search: |
526 * gtk_source_iter_forward_search: |
| |
527 * @iter: start of search. |
| |
528 * @str: a search string. |
| |
529 * @flags: flags affecting how the search is done. |
| |
530 * @match_start: return location for start of match, or %%NULL. |
| |
531 * @match_end: return location for end of match, or %%NULL. |
| |
532 * @limit: bound for the search, or %%NULL for the end of the buffer. |
| 484 * |
533 * |
| 485 * Searches forward for @str. Any match is returned by setting |
534 * Searches forward for @str. Any match is returned by setting |
| 486 * @match_start to the first character of the match and @match_end to the |
535 * @match_start to the first character of the match and @match_end to the |
| 487 * first character after the match. The search will not continue past |
536 * first character after the match. The search will not continue past |
| 488 * @limit. Note that a search is a linear or O(n) operation, so you |
537 * @limit. Note that a search is a linear or O(n) operation, so you |
| 500 * be matched regardless of what case it is in. |
549 * be matched regardless of what case it is in. |
| 501 * |
550 * |
| 502 * Same as gtk_text_iter_forward_search(), but supports case insensitive |
551 * Same as gtk_text_iter_forward_search(), but supports case insensitive |
| 503 * searching. |
552 * searching. |
| 504 * |
553 * |
| 505 * @param iter start of search |
554 * Return value: whether a match was found. |
| 506 * @param str a search string |
555 **/ |
| 507 * @param flags flags affecting how the search is done |
|
| 508 * @param match_start return location for start of match, or %NULL |
|
| 509 * @param match_end return location for end of match, or %NULL |
|
| 510 * @param limit bound for the search, or %NULL for the end of the buffer |
|
| 511 * @return returns whether a match was found |
|
| 512 */ |
|
| 513 gboolean |
556 gboolean |
| 514 gtk_source_iter_forward_search (const GtkTextIter *iter, |
557 gtk_source_iter_forward_search (const GtkTextIter *iter, |
| 515 const gchar *str, |
558 const gchar *str, |
| 516 GtkSourceSearchFlags flags, |
559 GtkSourceSearchFlags flags, |
| 517 GtkTextIter *match_start, |
560 GtkTextIter *match_start, |
| 578 break; |
621 break; |
| 579 |
622 |
| 580 if (lines_match (&search, (const gchar**)lines, |
623 if (lines_match (&search, (const gchar**)lines, |
| 581 visible_only, slice, &match, &end)) |
624 visible_only, slice, &match, &end)) |
| 582 { |
625 { |
| 583 if (limit == NULL || (limit && |
626 if (limit == NULL || |
| 584 gtk_text_iter_compare (&end, limit) < 0)) |
627 (limit && gtk_text_iter_compare (&end, limit) <= 0)) |
| 585 { |
628 { |
| 586 retval = TRUE; |
629 retval = TRUE; |
| 587 |
630 |
| 588 if (match_start) |
631 if (match_start) |
| 589 *match_start = match; |
632 *match_start = match; |
| 599 return retval; |
642 return retval; |
| 600 } |
643 } |
| 601 |
644 |
| 602 /** |
645 /** |
| 603 * gtk_source_iter_backward_search: |
646 * gtk_source_iter_backward_search: |
| |
647 * @iter: a #GtkTextIter where the search begins. |
| |
648 * @str: search string. |
| |
649 * @flags: bitmask of flags affecting the search. |
| |
650 * @match_start: return location for start of match, or %%NULL. |
| |
651 * @match_end: return location for end of match, or %%NULL. |
| |
652 * @limit: location of last possible @match_start, or %%NULL for start of buffer. |
| 604 * |
653 * |
| 605 * Same as gtk_text_iter_backward_search(), but supports case insensitive |
654 * Same as gtk_text_iter_backward_search(), but supports case insensitive |
| 606 * searching. |
655 * searching. |
| 607 * |
656 * |
| 608 * @param iter a #GtkTextIter where the search begins |
657 * Return value: whether a match was found. |
| 609 * @param str search string |
658 **/ |
| 610 * @param flags bitmask of flags affecting the search |
|
| 611 * @param match_start return location for start of match, or %NULL |
|
| 612 * @param match_end return location for end of match, or %NULL |
|
| 613 * @param limit location of last possible @match_start, or %NULL for start of buffer |
|
| 614 * @return returns whether a match was found |
|
| 615 */ |
|
| 616 gboolean |
659 gboolean |
| 617 gtk_source_iter_backward_search (const GtkTextIter *iter, |
660 gtk_source_iter_backward_search (const GtkTextIter *iter, |
| 618 const gchar *str, |
661 const gchar *str, |
| 619 GtkSourceSearchFlags flags, |
662 GtkSourceSearchFlags flags, |
| 620 GtkTextIter *match_start, |
663 GtkTextIter *match_start, |