1 | /* GTK - The GIMP Toolkit |
2 | * gtktextiter.c Copyright (C) 2000 Red Hat, Inc. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | /* |
19 | * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS |
20 | * file for a list of people on the GTK+ Team. See the ChangeLog |
21 | * files for a list of changes. These files are distributed with |
22 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "gtktextiter.h" |
27 | #include "gtktextbtree.h" |
28 | #include "gtktextbufferprivate.h" |
29 | #include "gtktextiterprivate.h" |
30 | #include "gtkintl.h" |
31 | #include "gtkdebug.h" |
32 | |
33 | #include <string.h> |
34 | |
35 | |
36 | /** |
37 | * GtkTextIter: |
38 | * |
39 | * An iterator for the contents of a `GtkTextBuffer`. |
40 | * |
41 | * You may wish to begin by reading the |
42 | * [text widget conceptual overview](section-text-widget.html), |
43 | * which gives an overview of all the objects and data types |
44 | * related to the text widget and how they work together. |
45 | */ |
46 | |
47 | |
48 | #define FIX_OVERFLOWS(varname) if ((varname) == G_MININT) (varname) = G_MININT + 1 |
49 | |
50 | typedef struct _GtkTextRealIter GtkTextRealIter; |
51 | |
52 | struct G_GNUC_MAY_ALIAS _GtkTextRealIter |
53 | { |
54 | /* Always-valid information */ |
55 | GtkTextBTree *tree; |
56 | GtkTextLine *line; |
57 | /* At least one of these is always valid; |
58 | if invalid, they are -1. |
59 | |
60 | If the line byte offset is valid, so is the segment byte offset; |
61 | and ditto for char offsets. */ |
62 | int line_byte_offset; |
63 | int line_char_offset; |
64 | /* These two are valid if >= 0 */ |
65 | int cached_char_index; |
66 | int cached_line_number; |
67 | /* Stamps to detect the buffer changing under us */ |
68 | int chars_changed_stamp; |
69 | int segments_changed_stamp; |
70 | /* Valid if the segments_changed_stamp is up-to-date */ |
71 | GtkTextLineSegment *segment; /* indexable segment we index */ |
72 | GtkTextLineSegment *any_segment; /* first segment in our location, |
73 | maybe same as "segment" */ |
74 | /* One of these will always be valid if segments_changed_stamp is |
75 | up-to-date. If invalid, they are -1. |
76 | |
77 | If the line byte offset is valid, so is the segment byte offset; |
78 | and ditto for char offsets. */ |
79 | int segment_byte_offset; |
80 | int segment_char_offset; |
81 | |
82 | /* padding */ |
83 | int pad1; |
84 | gpointer pad2; |
85 | }; |
86 | |
87 | /* These "set" functions should not assume any fields |
88 | other than the char stamp and the tree are valid. |
89 | */ |
90 | static void |
91 | iter_set_common (GtkTextRealIter *iter, |
92 | GtkTextLine *line) |
93 | { |
94 | /* Update segments stamp */ |
95 | iter->segments_changed_stamp = |
96 | _gtk_text_btree_get_segments_changed_stamp (tree: iter->tree); |
97 | |
98 | iter->line = line; |
99 | |
100 | iter->line_byte_offset = -1; |
101 | iter->line_char_offset = -1; |
102 | iter->segment_byte_offset = -1; |
103 | iter->segment_char_offset = -1; |
104 | iter->cached_char_index = -1; |
105 | iter->cached_line_number = -1; |
106 | } |
107 | |
108 | static void |
109 | iter_set_from_byte_offset (GtkTextRealIter *iter, |
110 | GtkTextLine *line, |
111 | int byte_offset) |
112 | { |
113 | iter_set_common (iter, line); |
114 | |
115 | if (!_gtk_text_line_byte_locate (line: iter->line, |
116 | byte_offset, |
117 | segment: &iter->segment, |
118 | any_segment: &iter->any_segment, |
119 | seg_byte_offset: &iter->segment_byte_offset, |
120 | line_byte_offset: &iter->line_byte_offset)) |
121 | g_error ("Byte index %d is off the end of the line" , |
122 | byte_offset); |
123 | } |
124 | |
125 | static void |
126 | iter_set_from_char_offset (GtkTextRealIter *iter, |
127 | GtkTextLine *line, |
128 | int char_offset) |
129 | { |
130 | iter_set_common (iter, line); |
131 | |
132 | if (!_gtk_text_line_char_locate (line: iter->line, |
133 | char_offset, |
134 | segment: &iter->segment, |
135 | any_segment: &iter->any_segment, |
136 | seg_char_offset: &iter->segment_char_offset, |
137 | line_char_offset: &iter->line_char_offset)) |
138 | g_error ("Char offset %d is off the end of the line" , |
139 | char_offset); |
140 | } |
141 | |
142 | static void |
143 | iter_set_from_segment (GtkTextRealIter *iter, |
144 | GtkTextLine *line, |
145 | GtkTextLineSegment *segment) |
146 | { |
147 | GtkTextLineSegment *seg; |
148 | int byte_offset; |
149 | |
150 | /* This could theoretically be optimized by computing all the iter |
151 | fields in this same loop, but I'm skipping it for now. */ |
152 | byte_offset = 0; |
153 | seg = line->segments; |
154 | while (seg != segment) |
155 | { |
156 | byte_offset += seg->byte_count; |
157 | seg = seg->next; |
158 | } |
159 | |
160 | iter_set_from_byte_offset (iter, line, byte_offset); |
161 | } |
162 | |
163 | /* This function ensures that the segment-dependent information is |
164 | truly computed lazily; often we don't need to do the full make_real |
165 | work. This ensures the btree and line are valid, but doesn't |
166 | update the segments. */ |
167 | static GtkTextRealIter* |
168 | gtk_text_iter_make_surreal (const GtkTextIter *_iter) |
169 | { |
170 | GtkTextRealIter *iter = (GtkTextRealIter*)_iter; |
171 | |
172 | if (iter->chars_changed_stamp != |
173 | _gtk_text_btree_get_chars_changed_stamp (tree: iter->tree)) |
174 | { |
175 | g_warning ("Invalid text buffer iterator: either the iterator " |
176 | "is uninitialized, or the characters/paintables/widgets " |
177 | "in the buffer have been modified since the iterator " |
178 | "was created.\nYou must use marks, character numbers, " |
179 | "or line numbers to preserve a position across buffer " |
180 | "modifications.\nYou can apply tags and insert marks " |
181 | "without invalidating your iterators,\n" |
182 | "but any mutation that affects 'indexable' buffer contents " |
183 | "(contents that can be referred to by character offset)\n" |
184 | "will invalidate all outstanding iterators" ); |
185 | return NULL; |
186 | } |
187 | |
188 | /* We don't update the segments information since we are becoming |
189 | only surreal. However we do invalidate the segments information |
190 | if appropriate, to be sure we segfault if we try to use it and we |
191 | should have used make_real. */ |
192 | |
193 | if (iter->segments_changed_stamp != |
194 | _gtk_text_btree_get_segments_changed_stamp (tree: iter->tree)) |
195 | { |
196 | iter->segment = NULL; |
197 | iter->any_segment = NULL; |
198 | /* set to segfault-causing values. */ |
199 | iter->segment_byte_offset = -10000; |
200 | iter->segment_char_offset = -10000; |
201 | } |
202 | |
203 | return iter; |
204 | } |
205 | |
206 | static GtkTextRealIter* |
207 | gtk_text_iter_make_real (const GtkTextIter *_iter) |
208 | { |
209 | GtkTextRealIter *iter; |
210 | |
211 | iter = gtk_text_iter_make_surreal (_iter); |
212 | if (iter == NULL) |
213 | return NULL; |
214 | |
215 | if (iter->segments_changed_stamp != |
216 | _gtk_text_btree_get_segments_changed_stamp (tree: iter->tree)) |
217 | { |
218 | if (iter->line_byte_offset >= 0) |
219 | { |
220 | iter_set_from_byte_offset (iter, |
221 | line: iter->line, |
222 | byte_offset: iter->line_byte_offset); |
223 | } |
224 | else |
225 | { |
226 | g_assert (iter->line_char_offset >= 0); |
227 | |
228 | iter_set_from_char_offset (iter, |
229 | line: iter->line, |
230 | char_offset: iter->line_char_offset); |
231 | } |
232 | } |
233 | |
234 | g_assert (iter->segment != NULL); |
235 | g_assert (iter->any_segment != NULL); |
236 | g_assert (iter->segment->char_count > 0); |
237 | |
238 | return iter; |
239 | } |
240 | |
241 | static GtkTextRealIter* |
242 | iter_init_common (GtkTextIter *_iter, |
243 | GtkTextBTree *tree) |
244 | { |
245 | GtkTextRealIter *iter = (GtkTextRealIter*)_iter; |
246 | |
247 | g_assert (iter != NULL); |
248 | g_assert (tree != NULL); |
249 | |
250 | memset (s: iter, c: 0, n: sizeof (GtkTextRealIter)); |
251 | |
252 | iter->tree = tree; |
253 | |
254 | iter->chars_changed_stamp = |
255 | _gtk_text_btree_get_chars_changed_stamp (tree: iter->tree); |
256 | |
257 | return iter; |
258 | } |
259 | |
260 | static GtkTextRealIter* |
261 | iter_init_from_segment (GtkTextIter *iter, |
262 | GtkTextBTree *tree, |
263 | GtkTextLine *line, |
264 | GtkTextLineSegment *segment) |
265 | { |
266 | GtkTextRealIter *real; |
267 | |
268 | g_return_val_if_fail (line != NULL, NULL); |
269 | |
270 | real = iter_init_common (iter: iter, tree); |
271 | |
272 | iter_set_from_segment (iter: real, line, segment); |
273 | |
274 | return real; |
275 | } |
276 | |
277 | static GtkTextRealIter* |
278 | iter_init_from_byte_offset (GtkTextIter *iter, |
279 | GtkTextBTree *tree, |
280 | GtkTextLine *line, |
281 | int line_byte_offset) |
282 | { |
283 | GtkTextRealIter *real; |
284 | |
285 | g_return_val_if_fail (line != NULL, NULL); |
286 | |
287 | real = iter_init_common (iter: iter, tree); |
288 | |
289 | iter_set_from_byte_offset (iter: real, line, byte_offset: line_byte_offset); |
290 | |
291 | if (real->segment->type == >k_text_char_type && |
292 | (real->segment->body.chars[real->segment_byte_offset] & 0xc0) == 0x80) |
293 | g_warning ("Incorrect line byte index %d falls in the middle of a UTF-8 " |
294 | "character; this will crash the text buffer. " |
295 | "Byte indexes must refer to the start of a character." , |
296 | line_byte_offset); |
297 | |
298 | return real; |
299 | } |
300 | |
301 | static GtkTextRealIter* |
302 | iter_init_from_char_offset (GtkTextIter *iter, |
303 | GtkTextBTree *tree, |
304 | GtkTextLine *line, |
305 | int line_char_offset) |
306 | { |
307 | GtkTextRealIter *real; |
308 | |
309 | g_return_val_if_fail (line != NULL, NULL); |
310 | |
311 | real = iter_init_common (iter: iter, tree); |
312 | |
313 | iter_set_from_char_offset (iter: real, line, char_offset: line_char_offset); |
314 | |
315 | return real; |
316 | } |
317 | |
318 | static inline void |
319 | invalidate_char_index (GtkTextRealIter *iter) |
320 | { |
321 | iter->cached_char_index = -1; |
322 | } |
323 | |
324 | static inline void |
325 | adjust_char_index (GtkTextRealIter *iter, int count) |
326 | { |
327 | if (iter->cached_char_index >= 0) |
328 | iter->cached_char_index += count; |
329 | } |
330 | |
331 | static inline void |
332 | adjust_line_number (GtkTextRealIter *iter, int count) |
333 | { |
334 | if (iter->cached_line_number >= 0) |
335 | iter->cached_line_number += count; |
336 | } |
337 | |
338 | static inline void |
339 | ensure_char_offsets (GtkTextRealIter *iter) |
340 | { |
341 | if (iter->line_char_offset < 0) |
342 | { |
343 | g_assert (iter->line_byte_offset >= 0); |
344 | |
345 | _gtk_text_line_byte_to_char_offsets (line: iter->line, |
346 | byte_offset: iter->line_byte_offset, |
347 | line_char_offset: &iter->line_char_offset, |
348 | seg_char_offset: &iter->segment_char_offset); |
349 | } |
350 | } |
351 | |
352 | static inline void |
353 | ensure_byte_offsets (GtkTextRealIter *iter) |
354 | { |
355 | if (iter->line_byte_offset < 0) |
356 | { |
357 | g_assert (iter->line_char_offset >= 0); |
358 | |
359 | _gtk_text_line_char_to_byte_offsets (line: iter->line, |
360 | char_offset: iter->line_char_offset, |
361 | line_byte_offset: &iter->line_byte_offset, |
362 | seg_byte_offset: &iter->segment_byte_offset); |
363 | } |
364 | } |
365 | |
366 | static inline gboolean |
367 | is_segment_start (GtkTextRealIter *real) |
368 | { |
369 | return real->segment_byte_offset == 0 || real->segment_char_offset == 0; |
370 | } |
371 | |
372 | #ifdef G_ENABLE_DEBUG |
373 | static void |
374 | check_invariants (const GtkTextIter *iter) |
375 | { |
376 | if (GTK_DEBUG_CHECK (TEXT)) |
377 | _gtk_text_iter_check (iter); |
378 | } |
379 | #else |
380 | #define check_invariants(x) |
381 | #endif |
382 | |
383 | /** |
384 | * gtk_text_iter_get_buffer: |
385 | * @iter: an iterator |
386 | * |
387 | * Returns the `GtkTextBuffer` this iterator is associated with. |
388 | * |
389 | * Returns: (transfer none): the buffer |
390 | **/ |
391 | GtkTextBuffer* |
392 | gtk_text_iter_get_buffer (const GtkTextIter *iter) |
393 | { |
394 | GtkTextRealIter *real; |
395 | |
396 | g_return_val_if_fail (iter != NULL, NULL); |
397 | |
398 | real = gtk_text_iter_make_surreal (iter: iter); |
399 | |
400 | if (real == NULL) |
401 | return NULL; |
402 | |
403 | check_invariants (iter); |
404 | |
405 | return _gtk_text_btree_get_buffer (tree: real->tree); |
406 | } |
407 | |
408 | /** |
409 | * gtk_text_iter_copy: |
410 | * @iter: an iterator |
411 | * |
412 | * Creates a dynamically-allocated copy of an iterator. |
413 | * |
414 | * This function is not useful in applications, because |
415 | * iterators can be copied with a simple assignment |
416 | * (`GtkTextIter i = j;`). |
417 | * |
418 | * The function is used by language bindings. |
419 | * |
420 | * Returns: a copy of the @iter, free with [method@Gtk.TextIter.free] |
421 | */ |
422 | GtkTextIter* |
423 | gtk_text_iter_copy (const GtkTextIter *iter) |
424 | { |
425 | GtkTextIter *new_iter; |
426 | |
427 | g_return_val_if_fail (iter != NULL, NULL); |
428 | |
429 | new_iter = g_slice_new (GtkTextIter); |
430 | |
431 | *new_iter = *iter; |
432 | |
433 | return new_iter; |
434 | } |
435 | |
436 | /** |
437 | * gtk_text_iter_free: |
438 | * @iter: a dynamically-allocated iterator |
439 | * |
440 | * Free an iterator allocated on the heap. |
441 | * |
442 | * This function is intended for use in language bindings, |
443 | * and is not especially useful for applications, because |
444 | * iterators can simply be allocated on the stack. |
445 | */ |
446 | void |
447 | gtk_text_iter_free (GtkTextIter *iter) |
448 | { |
449 | g_return_if_fail (iter != NULL); |
450 | |
451 | g_slice_free (GtkTextIter, iter); |
452 | } |
453 | |
454 | /** |
455 | * gtk_text_iter_assign: |
456 | * @iter: a `GtkTextIter` |
457 | * @other: another `GtkTextIter` |
458 | * |
459 | * Assigns the value of @other to @iter. |
460 | * |
461 | * This function is not useful in applications, because |
462 | * iterators can be assigned with `GtkTextIter i = j;`. |
463 | * |
464 | * The function is used by language bindings. |
465 | */ |
466 | void |
467 | gtk_text_iter_assign (GtkTextIter *iter, |
468 | const GtkTextIter *other) |
469 | { |
470 | g_return_if_fail (iter != NULL); |
471 | g_return_if_fail (other != NULL); |
472 | |
473 | *iter = *other; |
474 | } |
475 | |
476 | G_DEFINE_BOXED_TYPE (GtkTextIter, gtk_text_iter, |
477 | gtk_text_iter_copy, |
478 | gtk_text_iter_free) |
479 | |
480 | GtkTextLineSegment* |
481 | _gtk_text_iter_get_indexable_segment (const GtkTextIter *iter) |
482 | { |
483 | GtkTextRealIter *real; |
484 | |
485 | g_return_val_if_fail (iter != NULL, NULL); |
486 | |
487 | real = gtk_text_iter_make_real (iter: iter); |
488 | |
489 | if (real == NULL) |
490 | return NULL; |
491 | |
492 | check_invariants (iter); |
493 | |
494 | g_assert (real->segment != NULL); |
495 | |
496 | return real->segment; |
497 | } |
498 | |
499 | GtkTextLineSegment* |
500 | _gtk_text_iter_get_any_segment (const GtkTextIter *iter) |
501 | { |
502 | GtkTextRealIter *real; |
503 | |
504 | g_return_val_if_fail (iter != NULL, NULL); |
505 | |
506 | real = gtk_text_iter_make_real (iter: iter); |
507 | |
508 | if (real == NULL) |
509 | return NULL; |
510 | |
511 | check_invariants (iter); |
512 | |
513 | g_assert (real->any_segment != NULL); |
514 | |
515 | return real->any_segment; |
516 | } |
517 | |
518 | int |
519 | _gtk_text_iter_get_segment_byte (const GtkTextIter *iter) |
520 | { |
521 | GtkTextRealIter *real; |
522 | |
523 | g_return_val_if_fail (iter != NULL, 0); |
524 | |
525 | real = gtk_text_iter_make_real (iter: iter); |
526 | |
527 | if (real == NULL) |
528 | return 0; |
529 | |
530 | ensure_byte_offsets (iter: real); |
531 | |
532 | check_invariants (iter); |
533 | |
534 | return real->segment_byte_offset; |
535 | } |
536 | |
537 | int |
538 | _gtk_text_iter_get_segment_char (const GtkTextIter *iter) |
539 | { |
540 | GtkTextRealIter *real; |
541 | |
542 | g_return_val_if_fail (iter != NULL, 0); |
543 | |
544 | real = gtk_text_iter_make_real (iter: iter); |
545 | |
546 | if (real == NULL) |
547 | return 0; |
548 | |
549 | ensure_char_offsets (iter: real); |
550 | |
551 | check_invariants (iter); |
552 | |
553 | return real->segment_char_offset; |
554 | } |
555 | |
556 | /* This function does not require a still-valid |
557 | iterator */ |
558 | GtkTextLine* |
559 | _gtk_text_iter_get_text_line (const GtkTextIter *iter) |
560 | { |
561 | const GtkTextRealIter *real; |
562 | |
563 | g_return_val_if_fail (iter != NULL, NULL); |
564 | |
565 | real = (const GtkTextRealIter*)iter; |
566 | |
567 | return real->line; |
568 | } |
569 | |
570 | /* This function does not require a still-valid |
571 | iterator */ |
572 | GtkTextBTree* |
573 | _gtk_text_iter_get_btree (const GtkTextIter *iter) |
574 | { |
575 | const GtkTextRealIter *real; |
576 | |
577 | g_return_val_if_fail (iter != NULL, NULL); |
578 | |
579 | real = (const GtkTextRealIter*)iter; |
580 | |
581 | return real->tree; |
582 | } |
583 | |
584 | /* |
585 | * Conversions |
586 | */ |
587 | |
588 | /** |
589 | * gtk_text_iter_get_offset: |
590 | * @iter: an iterator |
591 | * |
592 | * Returns the character offset of an iterator. |
593 | * |
594 | * Each character in a `GtkTextBuffer` has an offset, |
595 | * starting with 0 for the first character in the buffer. |
596 | * Use [method@Gtk,TextBuffer.get_iter_at_offset] to convert |
597 | * an offset back into an iterator. |
598 | * |
599 | * Returns: a character offset |
600 | */ |
601 | int |
602 | gtk_text_iter_get_offset (const GtkTextIter *iter) |
603 | { |
604 | GtkTextRealIter *real; |
605 | |
606 | g_return_val_if_fail (iter != NULL, 0); |
607 | |
608 | real = gtk_text_iter_make_surreal (iter: iter); |
609 | |
610 | if (real == NULL) |
611 | return 0; |
612 | |
613 | check_invariants (iter); |
614 | |
615 | if (real->cached_char_index < 0) |
616 | { |
617 | ensure_char_offsets (iter: real); |
618 | |
619 | real->cached_char_index = |
620 | _gtk_text_line_char_index (line: real->line); |
621 | real->cached_char_index += real->line_char_offset; |
622 | } |
623 | |
624 | check_invariants (iter); |
625 | |
626 | return real->cached_char_index; |
627 | } |
628 | |
629 | /** |
630 | * gtk_text_iter_get_line: |
631 | * @iter: an iterator |
632 | * |
633 | * Returns the line number containing the iterator. |
634 | * |
635 | * Lines in a `GtkTextBuffer` are numbered beginning |
636 | * with 0 for the first line in the buffer. |
637 | * |
638 | * Returns: a line number |
639 | */ |
640 | int |
641 | gtk_text_iter_get_line (const GtkTextIter *iter) |
642 | { |
643 | GtkTextRealIter *real; |
644 | |
645 | g_return_val_if_fail (iter != NULL, 0); |
646 | |
647 | real = gtk_text_iter_make_surreal (iter: iter); |
648 | |
649 | if (real == NULL) |
650 | return 0; |
651 | |
652 | if (real->cached_line_number < 0) |
653 | real->cached_line_number = |
654 | _gtk_text_line_get_number (line: real->line); |
655 | |
656 | check_invariants (iter); |
657 | |
658 | return real->cached_line_number; |
659 | } |
660 | |
661 | /** |
662 | * gtk_text_iter_get_line_offset: |
663 | * @iter: an iterator |
664 | * |
665 | * Returns the character offset of the iterator, |
666 | * counting from the start of a newline-terminated line. |
667 | * |
668 | * The first character on the line has offset 0. |
669 | * |
670 | * Returns: offset from start of line |
671 | */ |
672 | int |
673 | gtk_text_iter_get_line_offset (const GtkTextIter *iter) |
674 | { |
675 | GtkTextRealIter *real; |
676 | |
677 | g_return_val_if_fail (iter != NULL, 0); |
678 | |
679 | real = gtk_text_iter_make_surreal (iter: iter); |
680 | |
681 | if (real == NULL) |
682 | return 0; |
683 | |
684 | ensure_char_offsets (iter: real); |
685 | |
686 | check_invariants (iter); |
687 | |
688 | return real->line_char_offset; |
689 | } |
690 | |
691 | /** |
692 | * gtk_text_iter_get_line_index: |
693 | * @iter: an iterator |
694 | * |
695 | * Returns the byte index of the iterator, counting |
696 | * from the start of a newline-terminated line. |
697 | * |
698 | * Remember that `GtkTextBuffer` encodes text in |
699 | * UTF-8, and that characters can require a variable |
700 | * number of bytes to represent. |
701 | * |
702 | * Returns: distance from start of line, in bytes |
703 | */ |
704 | int |
705 | gtk_text_iter_get_line_index (const GtkTextIter *iter) |
706 | { |
707 | GtkTextRealIter *real; |
708 | |
709 | g_return_val_if_fail (iter != NULL, 0); |
710 | |
711 | real = gtk_text_iter_make_surreal (iter: iter); |
712 | |
713 | if (real == NULL) |
714 | return 0; |
715 | |
716 | ensure_byte_offsets (iter: real); |
717 | |
718 | check_invariants (iter); |
719 | |
720 | return real->line_byte_offset; |
721 | } |
722 | |
723 | /** |
724 | * gtk_text_iter_get_visible_line_offset: |
725 | * @iter: a `GtkTextIter` |
726 | * |
727 | * Returns the offset in characters from the start of the |
728 | * line to the given @iter, not counting characters that |
729 | * are invisible due to tags with the “invisible” flag |
730 | * toggled on. |
731 | * |
732 | * Returns: offset in visible characters from the start of the line |
733 | */ |
734 | int |
735 | gtk_text_iter_get_visible_line_offset (const GtkTextIter *iter) |
736 | { |
737 | GtkTextRealIter *real; |
738 | int vis_offset; |
739 | GtkTextLineSegment *seg; |
740 | GtkTextIter pos; |
741 | |
742 | g_return_val_if_fail (iter != NULL, 0); |
743 | |
744 | real = gtk_text_iter_make_real (iter: iter); |
745 | |
746 | if (real == NULL) |
747 | return 0; |
748 | |
749 | ensure_char_offsets (iter: real); |
750 | |
751 | check_invariants (iter); |
752 | |
753 | vis_offset = real->line_char_offset; |
754 | |
755 | g_assert (vis_offset >= 0); |
756 | |
757 | _gtk_text_btree_get_iter_at_line (tree: real->tree, |
758 | iter: &pos, |
759 | line: real->line, |
760 | byte_offset: 0); |
761 | |
762 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
763 | |
764 | while (seg != real->segment) |
765 | { |
766 | /* This is a pretty expensive call, making the |
767 | * whole function pretty lame; we could keep track |
768 | * of current invisibility state by looking at toggle |
769 | * segments as we loop, and then call this function |
770 | * only once per line, in order to speed up the loop |
771 | * quite a lot. |
772 | */ |
773 | if (_gtk_text_btree_char_is_invisible (iter: &pos)) |
774 | vis_offset -= seg->char_count; |
775 | |
776 | _gtk_text_iter_forward_indexable_segment (iter: &pos); |
777 | |
778 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
779 | } |
780 | |
781 | if (_gtk_text_btree_char_is_invisible (iter: &pos)) |
782 | vis_offset -= real->segment_char_offset; |
783 | |
784 | return vis_offset; |
785 | } |
786 | |
787 | |
788 | /** |
789 | * gtk_text_iter_get_visible_line_index: |
790 | * @iter: a `GtkTextIter` |
791 | * |
792 | * Returns the number of bytes from the start of the |
793 | * line to the given @iter, not counting bytes that |
794 | * are invisible due to tags with the “invisible” flag |
795 | * toggled on. |
796 | * |
797 | * Returns: byte index of @iter with respect to the start of the line |
798 | */ |
799 | int |
800 | gtk_text_iter_get_visible_line_index (const GtkTextIter *iter) |
801 | { |
802 | GtkTextRealIter *real; |
803 | int vis_offset; |
804 | GtkTextLineSegment *seg; |
805 | GtkTextIter pos; |
806 | |
807 | g_return_val_if_fail (iter != NULL, 0); |
808 | |
809 | real = gtk_text_iter_make_real (iter: iter); |
810 | |
811 | if (real == NULL) |
812 | return 0; |
813 | |
814 | ensure_byte_offsets (iter: real); |
815 | |
816 | check_invariants (iter); |
817 | |
818 | vis_offset = real->line_byte_offset; |
819 | |
820 | g_assert (vis_offset >= 0); |
821 | |
822 | _gtk_text_btree_get_iter_at_line (tree: real->tree, |
823 | iter: &pos, |
824 | line: real->line, |
825 | byte_offset: 0); |
826 | |
827 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
828 | |
829 | while (seg != real->segment) |
830 | { |
831 | /* This is a pretty expensive call, making the |
832 | * whole function pretty lame; we could keep track |
833 | * of current invisibility state by looking at toggle |
834 | * segments as we loop, and then call this function |
835 | * only once per line, in order to speed up the loop |
836 | * quite a lot. |
837 | */ |
838 | if (_gtk_text_btree_char_is_invisible (iter: &pos)) |
839 | vis_offset -= seg->byte_count; |
840 | |
841 | _gtk_text_iter_forward_indexable_segment (iter: &pos); |
842 | |
843 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
844 | } |
845 | |
846 | if (_gtk_text_btree_char_is_invisible (iter: &pos)) |
847 | vis_offset -= real->segment_byte_offset; |
848 | |
849 | return vis_offset; |
850 | } |
851 | |
852 | /* |
853 | * Dereferencing |
854 | */ |
855 | |
856 | /** |
857 | * gtk_text_iter_get_char: |
858 | * @iter: an iterator |
859 | * |
860 | * The Unicode character at this iterator is returned. |
861 | * |
862 | * Equivalent to operator* on a C++ iterator. If the element at |
863 | * this iterator is a non-character element, such as an image |
864 | * embedded in the buffer, the Unicode “unknown” character 0xFFFC |
865 | * is returned. If invoked on the end iterator, zero is returned; |
866 | * zero is not a valid Unicode character. |
867 | * |
868 | * So you can write a loop which ends when this function returns 0. |
869 | * |
870 | * Returns: a Unicode character, or 0 if @iter is not dereferenceable |
871 | */ |
872 | gunichar |
873 | gtk_text_iter_get_char (const GtkTextIter *iter) |
874 | { |
875 | GtkTextRealIter *real; |
876 | |
877 | g_return_val_if_fail (iter != NULL, 0); |
878 | |
879 | real = gtk_text_iter_make_real (iter: iter); |
880 | |
881 | if (real == NULL) |
882 | return 0; |
883 | |
884 | check_invariants (iter); |
885 | |
886 | if (gtk_text_iter_is_end (iter)) |
887 | return 0; |
888 | else if (real->segment->type == >k_text_char_type) |
889 | { |
890 | ensure_byte_offsets (iter: real); |
891 | |
892 | return g_utf8_get_char (p: real->segment->body.chars + |
893 | real->segment_byte_offset); |
894 | } |
895 | else if (real->segment->type == >k_text_child_type) |
896 | { |
897 | return g_utf8_get_char (p: gtk_text_child_anchor_get_replacement (anchor: real->segment->body.child.obj)); |
898 | } |
899 | else |
900 | { |
901 | /* Unicode "unknown character" 0xFFFC */ |
902 | return GTK_TEXT_UNKNOWN_CHAR; |
903 | } |
904 | } |
905 | |
906 | /** |
907 | * gtk_text_iter_get_slice: |
908 | * @start: iterator at start of a range |
909 | * @end: iterator at end of a range |
910 | * |
911 | * Returns the text in the given range. |
912 | * |
913 | * A “slice” is an array of characters encoded in UTF-8 format, |
914 | * including the Unicode “unknown” character 0xFFFC for iterable |
915 | * non-character elements in the buffer, such as images. |
916 | * Because images are encoded in the slice, byte and |
917 | * character offsets in the returned array will correspond to byte |
918 | * offsets in the text buffer. Note that 0xFFFC can occur in normal |
919 | * text as well, so it is not a reliable indicator that a paintable or |
920 | * widget is in the buffer. |
921 | * |
922 | * Returns: (transfer full): slice of text from the buffer |
923 | */ |
924 | char * |
925 | gtk_text_iter_get_slice (const GtkTextIter *start, |
926 | const GtkTextIter *end) |
927 | { |
928 | g_return_val_if_fail (start != NULL, NULL); |
929 | g_return_val_if_fail (end != NULL, NULL); |
930 | |
931 | check_invariants (iter: start); |
932 | check_invariants (iter: end); |
933 | |
934 | return _gtk_text_btree_get_text (start, end, TRUE, TRUE); |
935 | } |
936 | |
937 | /** |
938 | * gtk_text_iter_get_text: |
939 | * @start: iterator at start of a range |
940 | * @end: iterator at end of a range |
941 | * |
942 | * Returns text in the given range. |
943 | * |
944 | * If the range |
945 | * contains non-text elements such as images, the character and byte |
946 | * offsets in the returned string will not correspond to character and |
947 | * byte offsets in the buffer. If you want offsets to correspond, see |
948 | * [method@Gtk.TextIter.get_slice]. |
949 | * |
950 | * Returns: (transfer full): array of characters from the buffer |
951 | */ |
952 | char * |
953 | gtk_text_iter_get_text (const GtkTextIter *start, |
954 | const GtkTextIter *end) |
955 | { |
956 | g_return_val_if_fail (start != NULL, NULL); |
957 | g_return_val_if_fail (end != NULL, NULL); |
958 | |
959 | check_invariants (iter: start); |
960 | check_invariants (iter: end); |
961 | |
962 | return _gtk_text_btree_get_text (start, end, TRUE, FALSE); |
963 | } |
964 | |
965 | /** |
966 | * gtk_text_iter_get_visible_slice: |
967 | * @start: iterator at start of range |
968 | * @end: iterator at end of range |
969 | * |
970 | * Returns visible text in the given range. |
971 | * |
972 | * Like [method@Gtk.TextIter.get_slice], but invisible text |
973 | * is not included. Invisible text is usually invisible because |
974 | * a `GtkTextTag` with the “invisible” attribute turned on has |
975 | * been applied to it. |
976 | * |
977 | * Returns: (transfer full): slice of text from the buffer |
978 | */ |
979 | char * |
980 | gtk_text_iter_get_visible_slice (const GtkTextIter *start, |
981 | const GtkTextIter *end) |
982 | { |
983 | g_return_val_if_fail (start != NULL, NULL); |
984 | g_return_val_if_fail (end != NULL, NULL); |
985 | |
986 | check_invariants (iter: start); |
987 | check_invariants (iter: end); |
988 | |
989 | return _gtk_text_btree_get_text (start, end, FALSE, TRUE); |
990 | } |
991 | |
992 | /** |
993 | * gtk_text_iter_get_visible_text: |
994 | * @start: iterator at start of range |
995 | * @end: iterator at end of range |
996 | * |
997 | * Returns visible text in the given range. |
998 | * |
999 | * Like [method@Gtk.TextIter.get_text], but invisible text |
1000 | * is not included. Invisible text is usually invisible because |
1001 | * a `GtkTextTag` with the “invisible” attribute turned on has |
1002 | * been applied to it. |
1003 | * |
1004 | * Returns: (transfer full): string containing visible text in the |
1005 | * range |
1006 | */ |
1007 | char * |
1008 | gtk_text_iter_get_visible_text (const GtkTextIter *start, |
1009 | const GtkTextIter *end) |
1010 | { |
1011 | g_return_val_if_fail (start != NULL, NULL); |
1012 | g_return_val_if_fail (end != NULL, NULL); |
1013 | |
1014 | check_invariants (iter: start); |
1015 | check_invariants (iter: end); |
1016 | |
1017 | return _gtk_text_btree_get_text (start, end, FALSE, FALSE); |
1018 | } |
1019 | |
1020 | /** |
1021 | * gtk_text_iter_get_paintable: |
1022 | * @iter: an iterator |
1023 | * |
1024 | * If the element at @iter is a paintable, the paintable is returned. |
1025 | * |
1026 | * Otherwise, %NULL is returned. |
1027 | * |
1028 | * Returns: (transfer none) (nullable): the paintable at @iter |
1029 | **/ |
1030 | GdkPaintable * |
1031 | gtk_text_iter_get_paintable (const GtkTextIter *iter) |
1032 | { |
1033 | GtkTextRealIter *real; |
1034 | |
1035 | g_return_val_if_fail (iter != NULL, NULL); |
1036 | |
1037 | real = gtk_text_iter_make_real (iter: iter); |
1038 | |
1039 | if (real == NULL) |
1040 | return NULL; |
1041 | |
1042 | check_invariants (iter); |
1043 | |
1044 | if (real->segment->type != >k_text_paintable_type) |
1045 | return NULL; |
1046 | else |
1047 | return real->segment->body.paintable.paintable; |
1048 | } |
1049 | |
1050 | /** |
1051 | * gtk_text_iter_get_child_anchor: |
1052 | * @iter: an iterator |
1053 | * |
1054 | * If the location at @iter contains a child anchor, the |
1055 | * anchor is returned. |
1056 | * |
1057 | * Otherwise, %NULL is returned. |
1058 | * |
1059 | * Returns: (transfer none) (nullable): the anchor at @iter |
1060 | **/ |
1061 | GtkTextChildAnchor* |
1062 | gtk_text_iter_get_child_anchor (const GtkTextIter *iter) |
1063 | { |
1064 | GtkTextRealIter *real; |
1065 | |
1066 | g_return_val_if_fail (iter != NULL, NULL); |
1067 | |
1068 | real = gtk_text_iter_make_real (iter: iter); |
1069 | |
1070 | if (real == NULL) |
1071 | return NULL; |
1072 | |
1073 | check_invariants (iter); |
1074 | |
1075 | if (real->segment->type != >k_text_child_type) |
1076 | return NULL; |
1077 | else |
1078 | return real->segment->body.child.obj; |
1079 | } |
1080 | |
1081 | /** |
1082 | * gtk_text_iter_get_marks: |
1083 | * @iter: an iterator |
1084 | * |
1085 | * Returns a list of all `GtkTextMark` at this location. |
1086 | * |
1087 | * Because marks are not iterable (they don’t take up any "space" |
1088 | * in the buffer, they are just marks in between iterable locations), |
1089 | * multiple marks can exist in the same place. |
1090 | * |
1091 | * The returned list is not in any meaningful order. |
1092 | * |
1093 | * Returns: (element-type GtkTextMark) (transfer container): |
1094 | * list of `GtkTextMark` |
1095 | */ |
1096 | GSList* |
1097 | gtk_text_iter_get_marks (const GtkTextIter *iter) |
1098 | { |
1099 | GtkTextRealIter *real; |
1100 | GtkTextLineSegment *seg; |
1101 | GSList *retval; |
1102 | |
1103 | g_return_val_if_fail (iter != NULL, NULL); |
1104 | |
1105 | real = gtk_text_iter_make_real (iter: iter); |
1106 | |
1107 | if (real == NULL) |
1108 | return NULL; |
1109 | |
1110 | check_invariants (iter); |
1111 | |
1112 | retval = NULL; |
1113 | seg = real->any_segment; |
1114 | while (seg != real->segment) |
1115 | { |
1116 | if (seg->type == >k_text_left_mark_type || |
1117 | seg->type == >k_text_right_mark_type) |
1118 | retval = g_slist_prepend (list: retval, data: seg->body.mark.obj); |
1119 | |
1120 | seg = seg->next; |
1121 | } |
1122 | |
1123 | /* The returned list isn't guaranteed to be in any special order, |
1124 | and it isn't. */ |
1125 | return retval; |
1126 | } |
1127 | |
1128 | /** |
1129 | * gtk_text_iter_get_toggled_tags: |
1130 | * @iter: an iterator |
1131 | * @toggled_on: %TRUE to get toggled-on tags |
1132 | * |
1133 | * Returns a list of `GtkTextTag` that are toggled on or off at this |
1134 | * point. |
1135 | * |
1136 | * If @toggled_on is %TRUE, the list contains tags that are |
1137 | * toggled on. If a tag is toggled on at @iter, then some non-empty |
1138 | * range of characters following @iter has that tag applied to it. If |
1139 | * a tag is toggled off, then some non-empty range following @iter |
1140 | * does not have the tag applied to it. |
1141 | * |
1142 | * Returns: (element-type GtkTextTag) (transfer container): tags |
1143 | * toggled at this point |
1144 | */ |
1145 | GSList* |
1146 | gtk_text_iter_get_toggled_tags (const GtkTextIter *iter, |
1147 | gboolean toggled_on) |
1148 | { |
1149 | GtkTextRealIter *real; |
1150 | GtkTextLineSegment *seg; |
1151 | GSList *retval; |
1152 | |
1153 | g_return_val_if_fail (iter != NULL, NULL); |
1154 | |
1155 | real = gtk_text_iter_make_real (iter: iter); |
1156 | |
1157 | if (real == NULL) |
1158 | return NULL; |
1159 | |
1160 | check_invariants (iter); |
1161 | |
1162 | retval = NULL; |
1163 | seg = real->any_segment; |
1164 | while (seg != real->segment) |
1165 | { |
1166 | if (toggled_on) |
1167 | { |
1168 | if (seg->type == >k_text_toggle_on_type) |
1169 | { |
1170 | retval = g_slist_prepend (list: retval, data: seg->body.toggle.info->tag); |
1171 | } |
1172 | } |
1173 | else |
1174 | { |
1175 | if (seg->type == >k_text_toggle_off_type) |
1176 | { |
1177 | retval = g_slist_prepend (list: retval, data: seg->body.toggle.info->tag); |
1178 | } |
1179 | } |
1180 | |
1181 | seg = seg->next; |
1182 | } |
1183 | |
1184 | /* The returned list isn't guaranteed to be in any special order, |
1185 | and it isn't. */ |
1186 | return retval; |
1187 | } |
1188 | |
1189 | /** |
1190 | * gtk_text_iter_starts_tag: |
1191 | * @iter: an iterator |
1192 | * @tag: (nullable): a `GtkTextTag` |
1193 | * |
1194 | * Returns %TRUE if @tag is toggled on at exactly this point. |
1195 | * |
1196 | * If @tag is %NULL, returns %TRUE if any tag is toggled on at this point. |
1197 | * |
1198 | * Note that if this function returns %TRUE, it means that |
1199 | * @iter is at the beginning of the tagged range, and that the |
1200 | * character at @iter is inside the tagged range. In other |
1201 | * words, unlike [method@Gtk.TextIter.ends_tag], if |
1202 | * this function returns %TRUE, [method@Gtk.TextIter.has_tag |
1203 | * will also return %TRUE for the same parameters. |
1204 | * |
1205 | * Returns: whether @iter is the start of a range tagged with @tag |
1206 | **/ |
1207 | gboolean |
1208 | gtk_text_iter_starts_tag (const GtkTextIter *iter, |
1209 | GtkTextTag *tag) |
1210 | { |
1211 | GtkTextRealIter *real; |
1212 | GtkTextLineSegment *seg; |
1213 | |
1214 | g_return_val_if_fail (iter != NULL, FALSE); |
1215 | |
1216 | real = gtk_text_iter_make_real (iter: iter); |
1217 | |
1218 | if (real == NULL) |
1219 | return FALSE; |
1220 | |
1221 | check_invariants (iter); |
1222 | |
1223 | seg = real->any_segment; |
1224 | while (seg != real->segment) |
1225 | { |
1226 | if (seg->type == >k_text_toggle_on_type) |
1227 | { |
1228 | if (tag == NULL || |
1229 | seg->body.toggle.info->tag == tag) |
1230 | return TRUE; |
1231 | } |
1232 | |
1233 | seg = seg->next; |
1234 | } |
1235 | |
1236 | return FALSE; |
1237 | } |
1238 | |
1239 | /** |
1240 | * gtk_text_iter_ends_tag: |
1241 | * @iter: an iterator |
1242 | * @tag: (nullable): a `GtkTextTag` |
1243 | * |
1244 | * Returns %TRUE if @tag is toggled off at exactly this point. |
1245 | * |
1246 | * If @tag is %NULL, returns %TRUE if any tag is toggled off at this point. |
1247 | * |
1248 | * Note that if this function returns %TRUE, it means that |
1249 | * @iter is at the end of the tagged range, but that the character |
1250 | * at @iter is outside the tagged range. In other words, |
1251 | * unlike [method@Gtk.TextIter.starts_tag], if this function |
1252 | * returns %TRUE, [method@Gtk.TextIter.has_tag] will return |
1253 | * %FALSE for the same parameters. |
1254 | * |
1255 | * Returns: whether @iter is the end of a range tagged with @tag |
1256 | */ |
1257 | gboolean |
1258 | gtk_text_iter_ends_tag (const GtkTextIter *iter, |
1259 | GtkTextTag *tag) |
1260 | { |
1261 | GtkTextRealIter *real; |
1262 | GtkTextLineSegment *seg; |
1263 | |
1264 | g_return_val_if_fail (iter != NULL, FALSE); |
1265 | |
1266 | real = gtk_text_iter_make_real (iter: iter); |
1267 | |
1268 | if (real == NULL) |
1269 | return FALSE; |
1270 | |
1271 | check_invariants (iter); |
1272 | |
1273 | seg = real->any_segment; |
1274 | while (seg != real->segment) |
1275 | { |
1276 | if (seg->type == >k_text_toggle_off_type) |
1277 | { |
1278 | if (tag == NULL || |
1279 | seg->body.toggle.info->tag == tag) |
1280 | return TRUE; |
1281 | } |
1282 | |
1283 | seg = seg->next; |
1284 | } |
1285 | |
1286 | return FALSE; |
1287 | } |
1288 | |
1289 | /** |
1290 | * gtk_text_iter_toggles_tag: |
1291 | * @iter: an iterator |
1292 | * @tag: (nullable): a `GtkTextTag` |
1293 | * |
1294 | * Gets whether a range with @tag applied to it begins |
1295 | * or ends at @iter. |
1296 | * |
1297 | * This is equivalent to (gtk_text_iter_starts_tag() || |
1298 | * gtk_text_iter_ends_tag()) |
1299 | * |
1300 | * Returns: whether @tag is toggled on or off at @iter |
1301 | */ |
1302 | gboolean |
1303 | gtk_text_iter_toggles_tag (const GtkTextIter *iter, |
1304 | GtkTextTag *tag) |
1305 | { |
1306 | GtkTextRealIter *real; |
1307 | GtkTextLineSegment *seg; |
1308 | |
1309 | g_return_val_if_fail (iter != NULL, FALSE); |
1310 | |
1311 | real = gtk_text_iter_make_real (iter: iter); |
1312 | |
1313 | if (real == NULL) |
1314 | return FALSE; |
1315 | |
1316 | check_invariants (iter); |
1317 | |
1318 | seg = real->any_segment; |
1319 | while (seg != real->segment) |
1320 | { |
1321 | if ( (seg->type == >k_text_toggle_off_type || |
1322 | seg->type == >k_text_toggle_on_type) && |
1323 | (tag == NULL || |
1324 | seg->body.toggle.info->tag == tag) ) |
1325 | return TRUE; |
1326 | |
1327 | seg = seg->next; |
1328 | } |
1329 | |
1330 | return FALSE; |
1331 | } |
1332 | |
1333 | /** |
1334 | * gtk_text_iter_has_tag: |
1335 | * @iter: an iterator |
1336 | * @tag: a `GtkTextTag` |
1337 | * |
1338 | * Returns %TRUE if @iter points to a character that is part |
1339 | * of a range tagged with @tag. |
1340 | * |
1341 | * See also [method@Gtk.TextIter.starts_tag] and |
1342 | * [method@Gtk.TextIter.ends_tag]. |
1343 | * |
1344 | * Returns: whether @iter is tagged with @tag |
1345 | */ |
1346 | gboolean |
1347 | gtk_text_iter_has_tag (const GtkTextIter *iter, |
1348 | GtkTextTag *tag) |
1349 | { |
1350 | GtkTextRealIter *real; |
1351 | |
1352 | g_return_val_if_fail (iter != NULL, FALSE); |
1353 | g_return_val_if_fail (GTK_IS_TEXT_TAG (tag), FALSE); |
1354 | |
1355 | real = gtk_text_iter_make_surreal (iter: iter); |
1356 | |
1357 | if (real == NULL) |
1358 | return FALSE; |
1359 | |
1360 | check_invariants (iter); |
1361 | |
1362 | if (real->line_byte_offset >= 0) |
1363 | { |
1364 | return _gtk_text_line_byte_has_tag (line: real->line, tree: real->tree, |
1365 | byte_in_line: real->line_byte_offset, tag); |
1366 | } |
1367 | else |
1368 | { |
1369 | g_assert (real->line_char_offset >= 0); |
1370 | return _gtk_text_line_char_has_tag (line: real->line, tree: real->tree, |
1371 | char_in_line: real->line_char_offset, tag); |
1372 | } |
1373 | } |
1374 | |
1375 | /** |
1376 | * gtk_text_iter_get_tags: |
1377 | * @iter: a `GtkTextIter` |
1378 | * |
1379 | * Returns a list of tags that apply to @iter, in ascending order of |
1380 | * priority. |
1381 | * |
1382 | * The highest-priority tags are last. |
1383 | * |
1384 | * The `GtkTextTag`s in the list don’t have a reference added, |
1385 | * but you have to free the list itself. |
1386 | * |
1387 | * Returns: (element-type GtkTextTag) (transfer container): list of |
1388 | * `GtkTextTag` |
1389 | */ |
1390 | GSList* |
1391 | gtk_text_iter_get_tags (const GtkTextIter *iter) |
1392 | { |
1393 | GPtrArray *tags; |
1394 | GSList *retval; |
1395 | |
1396 | g_return_val_if_fail (iter != NULL, NULL); |
1397 | |
1398 | /* Get the tags at this spot */ |
1399 | tags = _gtk_text_btree_get_tags (iter); |
1400 | |
1401 | /* No tags, use default style */ |
1402 | if (tags == NULL || tags->len == 0) |
1403 | { |
1404 | if (tags) |
1405 | g_ptr_array_unref (array: tags); |
1406 | return NULL; |
1407 | } |
1408 | |
1409 | retval = NULL; |
1410 | |
1411 | for (int i = tags->len - 1; i >= 0; i--) |
1412 | retval = g_slist_prepend (list: retval, g_ptr_array_index (tags, i)); |
1413 | |
1414 | g_ptr_array_unref (array: tags); |
1415 | |
1416 | return retval; |
1417 | } |
1418 | |
1419 | /** |
1420 | * gtk_text_iter_editable: |
1421 | * @iter: an iterator |
1422 | * @default_setting: %TRUE if text is editable by default |
1423 | * |
1424 | * Returns whether the character at @iter is within an editable region |
1425 | * of text. |
1426 | * |
1427 | * Non-editable text is “locked” and can’t be changed by the |
1428 | * user via `GtkTextView`. If no tags applied to this text affect |
1429 | * editability, @default_setting will be returned. |
1430 | * |
1431 | * You don’t want to use this function to decide whether text can be |
1432 | * inserted at @iter, because for insertion you don’t want to know |
1433 | * whether the char at @iter is inside an editable range, you want to |
1434 | * know whether a new character inserted at @iter would be inside an |
1435 | * editable range. Use [method@Gtk.TextIter.can_insert] to handle this |
1436 | * case. |
1437 | * |
1438 | * Returns: whether @iter is inside an editable range |
1439 | */ |
1440 | gboolean |
1441 | gtk_text_iter_editable (const GtkTextIter *iter, |
1442 | gboolean default_setting) |
1443 | { |
1444 | GtkTextAttributes *values; |
1445 | gboolean retval; |
1446 | |
1447 | g_return_val_if_fail (iter != NULL, FALSE); |
1448 | |
1449 | values = gtk_text_attributes_new (); |
1450 | |
1451 | values->editable = default_setting; |
1452 | |
1453 | gtk_text_iter_get_attributes (iter, values); |
1454 | |
1455 | retval = values->editable; |
1456 | |
1457 | gtk_text_attributes_unref (values); |
1458 | |
1459 | return retval; |
1460 | } |
1461 | |
1462 | /** |
1463 | * gtk_text_iter_can_insert: |
1464 | * @iter: an iterator |
1465 | * @default_editability: %TRUE if text is editable by default |
1466 | * |
1467 | * Considering the default editability of the buffer, and tags that |
1468 | * affect editability, determines whether text inserted at @iter would |
1469 | * be editable. |
1470 | * |
1471 | * If text inserted at @iter would be editable then the |
1472 | * user should be allowed to insert text at @iter. |
1473 | * [method@Gtk.TextBuffer.insert_interactive] uses this function |
1474 | * to decide whether insertions are allowed at a given position. |
1475 | * |
1476 | * Returns: whether text inserted at @iter would be editable |
1477 | */ |
1478 | gboolean |
1479 | gtk_text_iter_can_insert (const GtkTextIter *iter, |
1480 | gboolean default_editability) |
1481 | { |
1482 | g_return_val_if_fail (iter != NULL, FALSE); |
1483 | |
1484 | if (gtk_text_iter_editable (iter, default_setting: default_editability)) |
1485 | return TRUE; |
1486 | /* If at start/end of buffer, default editability is used */ |
1487 | else if ((gtk_text_iter_is_start (iter) || |
1488 | gtk_text_iter_is_end (iter)) && |
1489 | default_editability) |
1490 | return TRUE; |
1491 | else |
1492 | { |
1493 | /* if iter isn't editable, and the char before iter is, |
1494 | * then iter is the first char in an editable region |
1495 | * and thus insertion at iter results in editable text. |
1496 | */ |
1497 | GtkTextIter prev = *iter; |
1498 | gtk_text_iter_backward_char (iter: &prev); |
1499 | return gtk_text_iter_editable (iter: &prev, default_setting: default_editability); |
1500 | } |
1501 | } |
1502 | |
1503 | gboolean |
1504 | gtk_text_iter_get_attributes (const GtkTextIter *iter, |
1505 | GtkTextAttributes *values) |
1506 | { |
1507 | GPtrArray *tags; |
1508 | |
1509 | /* Get the tags at this spot */ |
1510 | tags = _gtk_text_btree_get_tags (iter); |
1511 | |
1512 | /* No tags, use default style */ |
1513 | if (tags == NULL || tags->len == 0) |
1514 | { |
1515 | if (tags) |
1516 | g_ptr_array_unref (array: tags); |
1517 | return FALSE; |
1518 | } |
1519 | |
1520 | _gtk_text_attributes_fill_from_tags (values, tags); |
1521 | |
1522 | g_ptr_array_unref (array: tags); |
1523 | |
1524 | return TRUE; |
1525 | } |
1526 | |
1527 | /** |
1528 | * gtk_text_iter_get_language: |
1529 | * @iter: an iterator |
1530 | * |
1531 | * Returns the language in effect at @iter. |
1532 | * |
1533 | * If no tags affecting language apply to @iter, the return |
1534 | * value is identical to that of [func@Gtk.get_default_language]. |
1535 | * |
1536 | * Returns: (transfer full): language in effect at @iter |
1537 | */ |
1538 | PangoLanguage * |
1539 | gtk_text_iter_get_language (const GtkTextIter *iter) |
1540 | { |
1541 | GtkTextAttributes *values; |
1542 | PangoLanguage *retval; |
1543 | |
1544 | values = gtk_text_attributes_new (); |
1545 | |
1546 | gtk_text_iter_get_attributes (iter, values); |
1547 | |
1548 | retval = values->language; |
1549 | |
1550 | gtk_text_attributes_unref (values); |
1551 | |
1552 | return retval; |
1553 | } |
1554 | |
1555 | /** |
1556 | * gtk_text_iter_starts_line: |
1557 | * @iter: an iterator |
1558 | * |
1559 | * Returns %TRUE if @iter begins a paragraph. |
1560 | * |
1561 | * This is the case if [method@Gtk.TextIter.get_line_offset] |
1562 | * would return 0. However this function is potentially more |
1563 | * efficient than [method@Gtk.TextIter.get_line_offset], because |
1564 | * it doesn’t have to compute the offset, it just has to see |
1565 | * whether it’s 0. |
1566 | * |
1567 | * Returns: whether @iter begins a line |
1568 | */ |
1569 | gboolean |
1570 | gtk_text_iter_starts_line (const GtkTextIter *iter) |
1571 | { |
1572 | GtkTextRealIter *real; |
1573 | |
1574 | g_return_val_if_fail (iter != NULL, FALSE); |
1575 | |
1576 | real = gtk_text_iter_make_surreal (iter: iter); |
1577 | |
1578 | if (real == NULL) |
1579 | return FALSE; |
1580 | |
1581 | check_invariants (iter); |
1582 | |
1583 | if (real->line_byte_offset >= 0) |
1584 | { |
1585 | return (real->line_byte_offset == 0); |
1586 | } |
1587 | else |
1588 | { |
1589 | g_assert (real->line_char_offset >= 0); |
1590 | return (real->line_char_offset == 0); |
1591 | } |
1592 | } |
1593 | |
1594 | /** |
1595 | * gtk_text_iter_ends_line: |
1596 | * @iter: an iterator |
1597 | * |
1598 | * Returns %TRUE if @iter points to the start of the paragraph |
1599 | * delimiter characters for a line. |
1600 | * |
1601 | * Delimiters will be either a newline, a carriage return, a carriage |
1602 | * return followed by a newline, or a Unicode paragraph separator |
1603 | * character. |
1604 | * |
1605 | * Note that an iterator pointing to the \n of a \r\n pair will not be |
1606 | * counted as the end of a line, the line ends before the \r. The end |
1607 | * iterator is considered to be at the end of a line, even though there |
1608 | * are no paragraph delimiter chars there. |
1609 | * |
1610 | * Returns: whether @iter is at the end of a line |
1611 | */ |
1612 | gboolean |
1613 | gtk_text_iter_ends_line (const GtkTextIter *iter) |
1614 | { |
1615 | gunichar wc; |
1616 | |
1617 | g_return_val_if_fail (iter != NULL, FALSE); |
1618 | |
1619 | check_invariants (iter); |
1620 | |
1621 | /* Only one character has type G_UNICODE_PARAGRAPH_SEPARATOR in |
1622 | * Unicode 3.0; update this if that changes. |
1623 | */ |
1624 | #define PARAGRAPH_SEPARATOR 0x2029 |
1625 | |
1626 | wc = gtk_text_iter_get_char (iter); |
1627 | |
1628 | if (wc == '\r' || wc == PARAGRAPH_SEPARATOR || wc == 0) /* wc == 0 is end iterator */ |
1629 | return TRUE; |
1630 | else if (wc == '\n') |
1631 | { |
1632 | GtkTextIter tmp = *iter; |
1633 | |
1634 | /* need to determine if a \r precedes the \n, in which case |
1635 | * we aren't the end of the line. |
1636 | * Note however that if \r and \n are on different lines, they |
1637 | * both are terminators. This for instance may happen after |
1638 | * deleting some text: |
1639 | |
1640 | 1 some text\r delete 'a' 1 some text\r |
1641 | 2 a\n ---------> 2 \n |
1642 | 3 ... 3 ... |
1643 | |
1644 | */ |
1645 | |
1646 | if (gtk_text_iter_get_line_offset (iter: &tmp) == 0) |
1647 | return TRUE; |
1648 | |
1649 | if (!gtk_text_iter_backward_char (iter: &tmp)) |
1650 | return TRUE; |
1651 | |
1652 | return gtk_text_iter_get_char (iter: &tmp) != '\r'; |
1653 | } |
1654 | else |
1655 | return FALSE; |
1656 | } |
1657 | |
1658 | /** |
1659 | * gtk_text_iter_is_end: |
1660 | * @iter: an iterator |
1661 | * |
1662 | * Returns %TRUE if @iter is the end iterator. |
1663 | * |
1664 | * This means it is one past the last dereferenceable iterator |
1665 | * in the buffer. gtk_text_iter_is_end() is the most efficient |
1666 | * way to check whether an iterator is the end iterator. |
1667 | * |
1668 | * Returns: whether @iter is the end iterator |
1669 | */ |
1670 | gboolean |
1671 | gtk_text_iter_is_end (const GtkTextIter *iter) |
1672 | { |
1673 | GtkTextRealIter *real; |
1674 | |
1675 | g_return_val_if_fail (iter != NULL, FALSE); |
1676 | |
1677 | real = gtk_text_iter_make_surreal (iter: iter); |
1678 | |
1679 | if (real == NULL) |
1680 | return FALSE; |
1681 | |
1682 | check_invariants (iter); |
1683 | |
1684 | if (!_gtk_text_line_contains_end_iter (line: real->line, tree: real->tree)) |
1685 | return FALSE; |
1686 | |
1687 | /* Now we need the segments validated */ |
1688 | real = gtk_text_iter_make_real (iter: iter); |
1689 | |
1690 | if (real == NULL) |
1691 | return FALSE; |
1692 | |
1693 | return _gtk_text_btree_is_end (tree: real->tree, line: real->line, |
1694 | seg: real->segment, |
1695 | byte_index: real->segment_byte_offset, |
1696 | char_offset: real->segment_char_offset); |
1697 | } |
1698 | |
1699 | /** |
1700 | * gtk_text_iter_is_start: |
1701 | * @iter: an iterator |
1702 | * |
1703 | * Returns %TRUE if @iter is the first iterator in the buffer. |
1704 | * |
1705 | * Returns: whether @iter is the first in the buffer |
1706 | */ |
1707 | gboolean |
1708 | gtk_text_iter_is_start (const GtkTextIter *iter) |
1709 | { |
1710 | return gtk_text_iter_get_offset (iter) == 0; |
1711 | } |
1712 | |
1713 | /** |
1714 | * gtk_text_iter_get_chars_in_line: |
1715 | * @iter: an iterator |
1716 | * |
1717 | * Returns the number of characters in the line containing @iter, |
1718 | * including the paragraph delimiters. |
1719 | * |
1720 | * Returns: number of characters in the line |
1721 | */ |
1722 | int |
1723 | gtk_text_iter_get_chars_in_line (const GtkTextIter *iter) |
1724 | { |
1725 | GtkTextRealIter *real; |
1726 | int count; |
1727 | GtkTextLineSegment *seg; |
1728 | |
1729 | g_return_val_if_fail (iter != NULL, 0); |
1730 | |
1731 | real = gtk_text_iter_make_surreal (iter: iter); |
1732 | |
1733 | if (real == NULL) |
1734 | return 0; |
1735 | |
1736 | check_invariants (iter); |
1737 | |
1738 | if (real->line_char_offset >= 0) |
1739 | { |
1740 | /* We can start at the segments we've already found. */ |
1741 | count = real->line_char_offset - real->segment_char_offset; |
1742 | seg = _gtk_text_iter_get_indexable_segment (iter); |
1743 | } |
1744 | else |
1745 | { |
1746 | /* count whole line. */ |
1747 | seg = real->line->segments; |
1748 | count = 0; |
1749 | } |
1750 | |
1751 | |
1752 | while (seg != NULL) |
1753 | { |
1754 | count += seg->char_count; |
1755 | |
1756 | seg = seg->next; |
1757 | } |
1758 | |
1759 | if (_gtk_text_line_contains_end_iter (line: real->line, tree: real->tree)) |
1760 | count -= 1; /* Dump the newline that was in the last segment of the end iter line */ |
1761 | |
1762 | return count; |
1763 | } |
1764 | |
1765 | /** |
1766 | * gtk_text_iter_get_bytes_in_line: |
1767 | * @iter: an iterator |
1768 | * |
1769 | * Returns the number of bytes in the line containing @iter, |
1770 | * including the paragraph delimiters. |
1771 | * |
1772 | * Returns: number of bytes in the line |
1773 | */ |
1774 | int |
1775 | gtk_text_iter_get_bytes_in_line (const GtkTextIter *iter) |
1776 | { |
1777 | GtkTextRealIter *real; |
1778 | int count; |
1779 | GtkTextLineSegment *seg; |
1780 | |
1781 | g_return_val_if_fail (iter != NULL, 0); |
1782 | |
1783 | real = gtk_text_iter_make_surreal (iter: iter); |
1784 | |
1785 | if (real == NULL) |
1786 | return 0; |
1787 | |
1788 | check_invariants (iter); |
1789 | |
1790 | if (real->line_byte_offset >= 0) |
1791 | { |
1792 | /* We can start at the segments we've already found. */ |
1793 | count = real->line_byte_offset - real->segment_byte_offset; |
1794 | seg = _gtk_text_iter_get_indexable_segment (iter); |
1795 | } |
1796 | else |
1797 | { |
1798 | /* count whole line. */ |
1799 | seg = real->line->segments; |
1800 | count = 0; |
1801 | } |
1802 | |
1803 | while (seg != NULL) |
1804 | { |
1805 | count += seg->byte_count; |
1806 | |
1807 | seg = seg->next; |
1808 | } |
1809 | |
1810 | if (_gtk_text_line_contains_end_iter (line: real->line, tree: real->tree)) |
1811 | count -= 1; /* Dump the newline that was in the last segment of the end iter line */ |
1812 | |
1813 | return count; |
1814 | } |
1815 | |
1816 | /* |
1817 | * Increments/decrements |
1818 | */ |
1819 | |
1820 | /* The return value of this indicates WHETHER WE MOVED. |
1821 | * The return value of public functions indicates |
1822 | * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) |
1823 | * |
1824 | * This function will not change the iterator if |
1825 | * it’s already on the last (end iter) line, i.e. it |
1826 | * won’t move to the end of the last line. |
1827 | */ |
1828 | static gboolean |
1829 | forward_line_leaving_caches_unmodified (GtkTextRealIter *real) |
1830 | { |
1831 | if (!_gtk_text_line_contains_end_iter (line: real->line, tree: real->tree)) |
1832 | { |
1833 | GtkTextLine *new_line; |
1834 | |
1835 | new_line = _gtk_text_line_next (line: real->line); |
1836 | g_assert (new_line); |
1837 | g_assert (new_line != real->line); |
1838 | g_assert (!_gtk_text_line_is_last (new_line, real->tree)); |
1839 | |
1840 | real->line = new_line; |
1841 | |
1842 | real->line_byte_offset = 0; |
1843 | real->line_char_offset = 0; |
1844 | |
1845 | real->segment_byte_offset = 0; |
1846 | real->segment_char_offset = 0; |
1847 | |
1848 | /* Find first segments in new line */ |
1849 | real->any_segment = real->line->segments; |
1850 | real->segment = real->any_segment; |
1851 | while (real->segment->char_count == 0) |
1852 | real->segment = real->segment->next; |
1853 | |
1854 | return TRUE; |
1855 | } |
1856 | else |
1857 | { |
1858 | /* There is no way to move forward a line; we were already at |
1859 | * the line containing the end iterator. |
1860 | * However we may not be at the end iterator itself. |
1861 | */ |
1862 | |
1863 | return FALSE; |
1864 | } |
1865 | } |
1866 | |
1867 | #if 0 |
1868 | /* The return value of this indicates WHETHER WE MOVED. |
1869 | * The return value of public functions indicates |
1870 | * (MOVEMENT OCCURRED && NEW ITER IS DEREFERENCEABLE) |
1871 | * |
1872 | * This function is currently unused, thus it is #if-0-ed. It is |
1873 | * left here, since it’s non-trivial code that might be useful in |
1874 | * the future. |
1875 | */ |
1876 | static gboolean |
1877 | backward_line_leaving_caches_unmodified (GtkTextRealIter *real) |
1878 | { |
1879 | GtkTextLine *new_line; |
1880 | |
1881 | new_line = _gtk_text_line_previous (real->line); |
1882 | |
1883 | g_assert (new_line != real->line); |
1884 | |
1885 | if (new_line != NULL) |
1886 | { |
1887 | real->line = new_line; |
1888 | |
1889 | real->line_byte_offset = 0; |
1890 | real->line_char_offset = 0; |
1891 | |
1892 | real->segment_byte_offset = 0; |
1893 | real->segment_char_offset = 0; |
1894 | |
1895 | /* Find first segments in new line */ |
1896 | real->any_segment = real->line->segments; |
1897 | real->segment = real->any_segment; |
1898 | while (real->segment->char_count == 0) |
1899 | real->segment = real->segment->next; |
1900 | |
1901 | return TRUE; |
1902 | } |
1903 | else |
1904 | { |
1905 | /* There is no way to move backward; we were already |
1906 | at the first line. */ |
1907 | |
1908 | /* We leave real->line as-is */ |
1909 | |
1910 | /* Note that we didn't clamp to the start of the first line. */ |
1911 | |
1912 | return FALSE; |
1913 | } |
1914 | } |
1915 | #endif |
1916 | |
1917 | /* The return value indicates (MOVEMENT OCCURRED && NEW ITER IS |
1918 | * DEREFERENCEABLE) |
1919 | */ |
1920 | static gboolean |
1921 | forward_char (GtkTextRealIter *real) |
1922 | { |
1923 | GtkTextIter *iter = (GtkTextIter*)real; |
1924 | |
1925 | check_invariants (iter: (GtkTextIter*)real); |
1926 | |
1927 | ensure_char_offsets (iter: real); |
1928 | |
1929 | if ( (real->segment_char_offset + 1) == real->segment->char_count) |
1930 | { |
1931 | /* Need to move to the next segment; if no next segment, |
1932 | need to move to next line. */ |
1933 | return _gtk_text_iter_forward_indexable_segment (iter); |
1934 | } |
1935 | else |
1936 | { |
1937 | /* Just moving within a segment. Keep byte count |
1938 | up-to-date, if it was already up-to-date. */ |
1939 | |
1940 | g_assert (real->segment->type == >k_text_char_type); |
1941 | |
1942 | if (real->line_byte_offset >= 0) |
1943 | { |
1944 | int bytes; |
1945 | const char * start = |
1946 | real->segment->body.chars + real->segment_byte_offset; |
1947 | |
1948 | bytes = g_utf8_next_char (start) - start; |
1949 | |
1950 | real->line_byte_offset += bytes; |
1951 | real->segment_byte_offset += bytes; |
1952 | |
1953 | g_assert (real->segment_byte_offset < real->segment->byte_count); |
1954 | } |
1955 | |
1956 | real->line_char_offset += 1; |
1957 | real->segment_char_offset += 1; |
1958 | |
1959 | adjust_char_index (iter: real, count: 1); |
1960 | |
1961 | g_assert (real->segment_char_offset < real->segment->char_count); |
1962 | |
1963 | /* We moved into the middle of a segment, so the any_segment |
1964 | must now be the segment we're in the middle of. */ |
1965 | real->any_segment = real->segment; |
1966 | |
1967 | check_invariants (iter: (GtkTextIter*)real); |
1968 | |
1969 | if (gtk_text_iter_is_end (iter: (GtkTextIter*)real)) |
1970 | return FALSE; |
1971 | else |
1972 | return TRUE; |
1973 | } |
1974 | } |
1975 | |
1976 | gboolean |
1977 | _gtk_text_iter_forward_indexable_segment (GtkTextIter *iter) |
1978 | { |
1979 | /* Need to move to the next segment; if no next segment, |
1980 | need to move to next line. */ |
1981 | GtkTextLineSegment *seg; |
1982 | GtkTextLineSegment *any_seg; |
1983 | GtkTextRealIter *real; |
1984 | int chars_skipped; |
1985 | int bytes_skipped; |
1986 | |
1987 | g_return_val_if_fail (iter != NULL, FALSE); |
1988 | |
1989 | real = gtk_text_iter_make_real (iter: iter); |
1990 | |
1991 | if (real == NULL) |
1992 | return FALSE; |
1993 | |
1994 | check_invariants (iter); |
1995 | |
1996 | if (real->line_char_offset >= 0) |
1997 | { |
1998 | chars_skipped = real->segment->char_count - real->segment_char_offset; |
1999 | g_assert (chars_skipped > 0); |
2000 | } |
2001 | else |
2002 | chars_skipped = 0; |
2003 | |
2004 | if (real->line_byte_offset >= 0) |
2005 | { |
2006 | bytes_skipped = real->segment->byte_count - real->segment_byte_offset; |
2007 | g_assert (bytes_skipped > 0); |
2008 | } |
2009 | else |
2010 | bytes_skipped = 0; |
2011 | |
2012 | /* Get first segment of any kind */ |
2013 | any_seg = real->segment->next; |
2014 | /* skip non-indexable segments, if any */ |
2015 | seg = any_seg; |
2016 | while (seg != NULL && seg->char_count == 0) |
2017 | seg = seg->next; |
2018 | |
2019 | if (seg != NULL) |
2020 | { |
2021 | real->any_segment = any_seg; |
2022 | real->segment = seg; |
2023 | |
2024 | if (real->line_byte_offset >= 0) |
2025 | { |
2026 | g_assert (bytes_skipped > 0); |
2027 | real->segment_byte_offset = 0; |
2028 | real->line_byte_offset += bytes_skipped; |
2029 | } |
2030 | |
2031 | if (real->line_char_offset >= 0) |
2032 | { |
2033 | g_assert (chars_skipped > 0); |
2034 | real->segment_char_offset = 0; |
2035 | real->line_char_offset += chars_skipped; |
2036 | adjust_char_index (iter: real, count: chars_skipped); |
2037 | } |
2038 | |
2039 | check_invariants (iter); |
2040 | |
2041 | return !gtk_text_iter_is_end (iter); |
2042 | } |
2043 | else |
2044 | { |
2045 | /* End of the line */ |
2046 | if (forward_line_leaving_caches_unmodified (real)) |
2047 | { |
2048 | adjust_line_number (iter: real, count: 1); |
2049 | if (real->line_char_offset >= 0) |
2050 | adjust_char_index (iter: real, count: chars_skipped); |
2051 | |
2052 | g_assert (real->line_byte_offset == 0); |
2053 | g_assert (real->line_char_offset == 0); |
2054 | g_assert (real->segment_byte_offset == 0); |
2055 | g_assert (real->segment_char_offset == 0); |
2056 | g_assert (gtk_text_iter_starts_line (iter)); |
2057 | |
2058 | check_invariants (iter); |
2059 | |
2060 | return !gtk_text_iter_is_end (iter); |
2061 | } |
2062 | else |
2063 | { |
2064 | /* End of buffer, but iter is still at start of last segment, |
2065 | * not at the end iterator. We put it on the end iterator. |
2066 | */ |
2067 | |
2068 | check_invariants (iter); |
2069 | |
2070 | g_assert (!_gtk_text_line_is_last (real->line, real->tree)); |
2071 | g_assert (_gtk_text_line_contains_end_iter (real->line, real->tree)); |
2072 | |
2073 | gtk_text_iter_forward_to_line_end (iter); |
2074 | |
2075 | g_assert (gtk_text_iter_is_end (iter)); |
2076 | |
2077 | return FALSE; |
2078 | } |
2079 | } |
2080 | } |
2081 | |
2082 | static gboolean |
2083 | at_last_indexable_segment (GtkTextRealIter *real) |
2084 | { |
2085 | GtkTextLineSegment *seg; |
2086 | |
2087 | /* Return TRUE if there are no indexable segments after |
2088 | * this iterator. |
2089 | */ |
2090 | |
2091 | seg = real->segment->next; |
2092 | while (seg) |
2093 | { |
2094 | if (seg->char_count > 0) |
2095 | return FALSE; |
2096 | seg = seg->next; |
2097 | } |
2098 | return TRUE; |
2099 | } |
2100 | |
2101 | /* Goes back to the start of the next segment, even if |
2102 | * we’re not at the start of the current segment (always |
2103 | * ends up on a different segment if it returns TRUE) |
2104 | */ |
2105 | gboolean |
2106 | _gtk_text_iter_backward_indexable_segment (GtkTextIter *iter) |
2107 | { |
2108 | /* Move to the start of the previous segment; if no previous |
2109 | * segment, to the last segment in the previous line. This is |
2110 | * inherently a bit inefficient due to the singly-linked list and |
2111 | * tree nodes, but we can't afford the RAM for doubly-linked. |
2112 | */ |
2113 | GtkTextRealIter *real; |
2114 | GtkTextLineSegment *seg; |
2115 | GtkTextLineSegment *any_seg; |
2116 | GtkTextLineSegment *prev_seg; |
2117 | GtkTextLineSegment *prev_any_seg; |
2118 | int bytes_skipped; |
2119 | int chars_skipped; |
2120 | |
2121 | g_return_val_if_fail (iter != NULL, FALSE); |
2122 | |
2123 | real = gtk_text_iter_make_real (iter: iter); |
2124 | |
2125 | if (real == NULL) |
2126 | return FALSE; |
2127 | |
2128 | check_invariants (iter); |
2129 | |
2130 | /* Find first segments in line */ |
2131 | any_seg = real->line->segments; |
2132 | seg = any_seg; |
2133 | while (seg->char_count == 0) |
2134 | seg = seg->next; |
2135 | |
2136 | if (seg == real->segment) |
2137 | { |
2138 | /* Could probably do this case faster by hand-coding the |
2139 | * iteration. |
2140 | */ |
2141 | |
2142 | /* We were already at the start of a line; |
2143 | * go back to the previous line. |
2144 | */ |
2145 | if (gtk_text_iter_backward_line (iter)) |
2146 | { |
2147 | /* Go forward to last indexable segment in line. */ |
2148 | while (!at_last_indexable_segment (real)) |
2149 | _gtk_text_iter_forward_indexable_segment (iter); |
2150 | |
2151 | check_invariants (iter); |
2152 | |
2153 | return TRUE; |
2154 | } |
2155 | else |
2156 | return FALSE; /* We were at the start of the first line. */ |
2157 | } |
2158 | |
2159 | /* We must be in the middle of a line; so find the indexable |
2160 | * segment just before our current segment. |
2161 | */ |
2162 | g_assert (seg != real->segment); |
2163 | do |
2164 | { |
2165 | prev_seg = seg; |
2166 | prev_any_seg = any_seg; |
2167 | |
2168 | any_seg = seg->next; |
2169 | seg = any_seg; |
2170 | while (seg->char_count == 0) |
2171 | seg = seg->next; |
2172 | } |
2173 | while (seg != real->segment); |
2174 | |
2175 | g_assert (prev_seg != NULL); |
2176 | g_assert (prev_any_seg != NULL); |
2177 | g_assert (prev_seg->char_count > 0); |
2178 | |
2179 | /* We skipped the entire previous segment, plus any |
2180 | * chars we were into the current segment. |
2181 | */ |
2182 | if (real->segment_byte_offset >= 0) |
2183 | bytes_skipped = prev_seg->byte_count + real->segment_byte_offset; |
2184 | else |
2185 | bytes_skipped = -1; |
2186 | |
2187 | if (real->segment_char_offset >= 0) |
2188 | chars_skipped = prev_seg->char_count + real->segment_char_offset; |
2189 | else |
2190 | chars_skipped = -1; |
2191 | |
2192 | real->segment = prev_seg; |
2193 | real->any_segment = prev_any_seg; |
2194 | real->segment_byte_offset = 0; |
2195 | real->segment_char_offset = 0; |
2196 | |
2197 | if (bytes_skipped >= 0) |
2198 | { |
2199 | if (real->line_byte_offset >= 0) |
2200 | { |
2201 | real->line_byte_offset -= bytes_skipped; |
2202 | g_assert (real->line_byte_offset >= 0); |
2203 | } |
2204 | } |
2205 | else |
2206 | real->line_byte_offset = -1; |
2207 | |
2208 | if (chars_skipped >= 0) |
2209 | { |
2210 | if (real->line_char_offset >= 0) |
2211 | { |
2212 | real->line_char_offset -= chars_skipped; |
2213 | g_assert (real->line_char_offset >= 0); |
2214 | } |
2215 | |
2216 | if (real->cached_char_index >= 0) |
2217 | { |
2218 | real->cached_char_index -= chars_skipped; |
2219 | g_assert (real->cached_char_index >= 0); |
2220 | } |
2221 | } |
2222 | else |
2223 | { |
2224 | real->line_char_offset = -1; |
2225 | real->cached_char_index = -1; |
2226 | } |
2227 | |
2228 | /* line number is unchanged. */ |
2229 | |
2230 | check_invariants (iter); |
2231 | |
2232 | return TRUE; |
2233 | } |
2234 | |
2235 | /** |
2236 | * gtk_text_iter_forward_char: |
2237 | * @iter: an iterator |
2238 | * |
2239 | * Moves @iter forward by one character offset. |
2240 | * |
2241 | * Note that images embedded in the buffer occupy 1 character slot, so |
2242 | * this function may actually move onto an image instead of a character, |
2243 | * if you have images in your buffer. If @iter is the end iterator or |
2244 | * one character before it, @iter will now point at the end iterator, |
2245 | * and this function returns %FALSE for convenience when writing loops. |
2246 | * |
2247 | * Returns: whether @iter moved and is dereferenceable |
2248 | */ |
2249 | gboolean |
2250 | gtk_text_iter_forward_char (GtkTextIter *iter) |
2251 | { |
2252 | GtkTextRealIter *real; |
2253 | |
2254 | g_return_val_if_fail (iter != NULL, FALSE); |
2255 | |
2256 | real = gtk_text_iter_make_real (iter: iter); |
2257 | |
2258 | if (real == NULL) |
2259 | return FALSE; |
2260 | else |
2261 | { |
2262 | check_invariants (iter); |
2263 | return forward_char (real); |
2264 | } |
2265 | } |
2266 | |
2267 | /** |
2268 | * gtk_text_iter_backward_char: |
2269 | * @iter: an iterator |
2270 | * |
2271 | * Moves backward by one character offset. |
2272 | * |
2273 | * Returns %TRUE if movement was possible; if @iter was the first |
2274 | * in the buffer (character offset 0), this function returns %FALSE |
2275 | * for convenience when writing loops. |
2276 | * |
2277 | * Returns: whether movement was possible |
2278 | */ |
2279 | gboolean |
2280 | gtk_text_iter_backward_char (GtkTextIter *iter) |
2281 | { |
2282 | g_return_val_if_fail (iter != NULL, FALSE); |
2283 | |
2284 | check_invariants (iter); |
2285 | |
2286 | return gtk_text_iter_backward_chars (iter, count: 1); |
2287 | } |
2288 | |
2289 | /* |
2290 | Definitely we should try to linear scan as often as possible for |
2291 | movement within a single line, because we can't use the BTree to |
2292 | speed within-line searches up; for movement between lines, we would |
2293 | like to avoid the linear scan probably. |
2294 | |
2295 | Instead of using this constant, it might be nice to cache the line |
2296 | length in the iterator and linear scan if motion is within a single |
2297 | line. |
2298 | |
2299 | I guess you'd have to profile the various approaches. |
2300 | */ |
2301 | #define MAX_LINEAR_SCAN 150 |
2302 | |
2303 | |
2304 | /** |
2305 | * gtk_text_iter_forward_chars: |
2306 | * @iter: an iterator |
2307 | * @count: number of characters to move, may be negative |
2308 | * |
2309 | * Moves @count characters if possible. |
2310 | * |
2311 | * If @count would move past the start or end of the buffer, |
2312 | * moves to the start or end of the buffer. |
2313 | * |
2314 | * The return value indicates whether the new position of |
2315 | * @iter is different from its original position, and dereferenceable |
2316 | * (the last iterator in the buffer is not dereferenceable). If @count |
2317 | * is 0, the function does nothing and returns %FALSE. |
2318 | * |
2319 | * Returns: whether @iter moved and is dereferenceable |
2320 | */ |
2321 | gboolean |
2322 | gtk_text_iter_forward_chars (GtkTextIter *iter, int count) |
2323 | { |
2324 | GtkTextRealIter *real; |
2325 | |
2326 | g_return_val_if_fail (iter != NULL, FALSE); |
2327 | |
2328 | FIX_OVERFLOWS (count); |
2329 | |
2330 | real = gtk_text_iter_make_real (iter: iter); |
2331 | |
2332 | if (real == NULL) |
2333 | return FALSE; |
2334 | else if (count == 0) |
2335 | return FALSE; |
2336 | else if (count < 0) |
2337 | return gtk_text_iter_backward_chars (iter, count: 0 - count); |
2338 | else if (count < MAX_LINEAR_SCAN) |
2339 | { |
2340 | check_invariants (iter); |
2341 | |
2342 | while (count > 1) |
2343 | { |
2344 | if (!forward_char (real)) |
2345 | return FALSE; |
2346 | --count; |
2347 | } |
2348 | |
2349 | return forward_char (real); |
2350 | } |
2351 | else |
2352 | { |
2353 | int current_char_index; |
2354 | int new_char_index; |
2355 | |
2356 | check_invariants (iter); |
2357 | |
2358 | current_char_index = gtk_text_iter_get_offset (iter); |
2359 | |
2360 | if (current_char_index == _gtk_text_btree_char_count (tree: real->tree)) |
2361 | return FALSE; /* can't move forward */ |
2362 | |
2363 | new_char_index = current_char_index + count; |
2364 | gtk_text_iter_set_offset (iter, char_offset: new_char_index); |
2365 | |
2366 | check_invariants (iter); |
2367 | |
2368 | /* Return FALSE if we're on the non-dereferenceable end |
2369 | * iterator. |
2370 | */ |
2371 | if (gtk_text_iter_is_end (iter)) |
2372 | return FALSE; |
2373 | else |
2374 | return TRUE; |
2375 | } |
2376 | } |
2377 | |
2378 | /** |
2379 | * gtk_text_iter_backward_chars: |
2380 | * @iter: an iterator |
2381 | * @count: number of characters to move |
2382 | * |
2383 | * Moves @count characters backward, if possible. |
2384 | * |
2385 | * If @count would move past the start or end of the buffer, moves |
2386 | * to the start or end of the buffer. |
2387 | * |
2388 | * The return value indicates whether the iterator moved |
2389 | * onto a dereferenceable position; if the iterator didn’t move, or |
2390 | * moved onto the end iterator, then %FALSE is returned. If @count is 0, |
2391 | * the function does nothing and returns %FALSE. |
2392 | * |
2393 | * Returns: whether @iter moved and is dereferenceable |
2394 | */ |
2395 | gboolean |
2396 | gtk_text_iter_backward_chars (GtkTextIter *iter, int count) |
2397 | { |
2398 | GtkTextRealIter *real; |
2399 | |
2400 | g_return_val_if_fail (iter != NULL, FALSE); |
2401 | |
2402 | FIX_OVERFLOWS (count); |
2403 | |
2404 | real = gtk_text_iter_make_real (iter: iter); |
2405 | |
2406 | if (real == NULL) |
2407 | return FALSE; |
2408 | else if (count == 0) |
2409 | return FALSE; |
2410 | else if (count < 0) |
2411 | return gtk_text_iter_forward_chars (iter, count: 0 - count); |
2412 | |
2413 | ensure_char_offsets (iter: real); |
2414 | check_invariants (iter); |
2415 | |
2416 | /* <, not <=, because if count == segment_char_offset |
2417 | * we're going to the front of the segment and the any_segment |
2418 | * might change |
2419 | */ |
2420 | if (count < real->segment_char_offset) |
2421 | { |
2422 | /* Optimize the within-segment case */ |
2423 | g_assert (real->segment->char_count > 0); |
2424 | g_assert (real->segment->type == >k_text_char_type); |
2425 | |
2426 | if (real->line_byte_offset >= 0) |
2427 | { |
2428 | const char *p; |
2429 | int new_byte_offset; |
2430 | |
2431 | /* if in the last fourth of the segment walk backwards */ |
2432 | if (count < real->segment_char_offset / 4) |
2433 | p = g_utf8_offset_to_pointer (str: real->segment->body.chars + real->segment_byte_offset, |
2434 | offset: -count); |
2435 | else |
2436 | p = g_utf8_offset_to_pointer (str: real->segment->body.chars, |
2437 | offset: real->segment_char_offset - count); |
2438 | |
2439 | new_byte_offset = p - real->segment->body.chars; |
2440 | real->line_byte_offset -= (real->segment_byte_offset - new_byte_offset); |
2441 | real->segment_byte_offset = new_byte_offset; |
2442 | } |
2443 | |
2444 | real->segment_char_offset -= count; |
2445 | real->line_char_offset -= count; |
2446 | |
2447 | adjust_char_index (iter: real, count: 0 - count); |
2448 | |
2449 | check_invariants (iter); |
2450 | |
2451 | return TRUE; |
2452 | } |
2453 | else |
2454 | { |
2455 | /* We need to go back into previous segments. For now, |
2456 | * just keep this really simple. FIXME |
2457 | * use backward_indexable_segment. |
2458 | */ |
2459 | if (TRUE || count > MAX_LINEAR_SCAN) |
2460 | { |
2461 | int current_char_index; |
2462 | int new_char_index; |
2463 | |
2464 | current_char_index = gtk_text_iter_get_offset (iter); |
2465 | |
2466 | if (current_char_index == 0) |
2467 | return FALSE; /* can't move backward */ |
2468 | |
2469 | new_char_index = current_char_index - count; |
2470 | if (new_char_index < 0) |
2471 | new_char_index = 0; |
2472 | |
2473 | gtk_text_iter_set_offset (iter, char_offset: new_char_index); |
2474 | |
2475 | check_invariants (iter); |
2476 | |
2477 | return TRUE; |
2478 | } |
2479 | else |
2480 | { |
2481 | /* FIXME backward_indexable_segment here */ |
2482 | |
2483 | return FALSE; |
2484 | } |
2485 | } |
2486 | } |
2487 | |
2488 | #if 0 |
2489 | |
2490 | /* These two can't be implemented efficiently (always have to use |
2491 | * a linear scan, since that’s the only way to find all the non-text |
2492 | * segments) |
2493 | */ |
2494 | |
2495 | /** |
2496 | * gtk_text_iter_forward_text_chars: |
2497 | * @iter: a `GtkTextIter` |
2498 | * @count: number of chars to move |
2499 | * |
2500 | * Moves forward by @count text characters. |
2501 | * |
2502 | * Paintables, widgets, etc. do not count as characters for this. |
2503 | * |
2504 | * Equivalent to moving through the results of gtk_text_iter_get_text(), |
2505 | * rather than gtk_text_iter_get_slice(). |
2506 | * |
2507 | * Returns: whether @iter moved and is dereferenceable |
2508 | */ |
2509 | gboolean |
2510 | gtk_text_iter_forward_text_chars (GtkTextIter *iter, |
2511 | int count) |
2512 | { |
2513 | |
2514 | |
2515 | |
2516 | } |
2517 | |
2518 | /** |
2519 | * gtk_text_iter_backward_text_chars: |
2520 | * @iter: a `GtkTextIter` |
2521 | * @count: number of chars to move |
2522 | * |
2523 | * Moves backward by @count text characters (paintables, widgets, |
2524 | * etc. do not count as characters for this). Equivalent to moving |
2525 | * through the results of gtk_text_iter_get_text(), rather than |
2526 | * gtk_text_iter_get_slice(). |
2527 | * |
2528 | * Returns: whether @iter moved and is dereferenceable |
2529 | **/ |
2530 | gboolean |
2531 | gtk_text_iter_backward_text_chars (GtkTextIter *iter, |
2532 | int count) |
2533 | { |
2534 | |
2535 | |
2536 | } |
2537 | #endif |
2538 | |
2539 | /** |
2540 | * gtk_text_iter_forward_line: |
2541 | * @iter: an iterator |
2542 | * |
2543 | * Moves @iter to the start of the next line. |
2544 | * |
2545 | * If the iter is already on the last line of the buffer, |
2546 | * moves the iter to the end of the current line. If after |
2547 | * the operation, the iter is at the end of the buffer and not |
2548 | * dereferenceable, returns %FALSE. Otherwise, returns %TRUE. |
2549 | * |
2550 | * Returns: whether @iter can be dereferenced |
2551 | */ |
2552 | gboolean |
2553 | gtk_text_iter_forward_line (GtkTextIter *iter) |
2554 | { |
2555 | GtkTextRealIter *real; |
2556 | |
2557 | g_return_val_if_fail (iter != NULL, FALSE); |
2558 | |
2559 | real = gtk_text_iter_make_real (iter: iter); |
2560 | |
2561 | if (real == NULL) |
2562 | return FALSE; |
2563 | |
2564 | check_invariants (iter); |
2565 | |
2566 | if (forward_line_leaving_caches_unmodified (real)) |
2567 | { |
2568 | invalidate_char_index (iter: real); |
2569 | adjust_line_number (iter: real, count: 1); |
2570 | |
2571 | check_invariants (iter); |
2572 | |
2573 | if (gtk_text_iter_is_end (iter)) |
2574 | return FALSE; |
2575 | else |
2576 | return TRUE; |
2577 | } |
2578 | else |
2579 | { |
2580 | /* On the last line, move to end of it */ |
2581 | |
2582 | if (!gtk_text_iter_is_end (iter)) |
2583 | gtk_text_iter_forward_to_end (iter); |
2584 | |
2585 | check_invariants (iter); |
2586 | return FALSE; |
2587 | } |
2588 | } |
2589 | |
2590 | /** |
2591 | * gtk_text_iter_backward_line: |
2592 | * @iter: an iterator |
2593 | * |
2594 | * Moves @iter to the start of the previous line. |
2595 | * |
2596 | * Returns %TRUE if @iter could be moved; i.e. if @iter was at |
2597 | * character offset 0, this function returns %FALSE. Therefore, |
2598 | * if @iter was already on line 0, but not at the start of the line, |
2599 | * @iter is snapped to the start of the line and the function returns |
2600 | * %TRUE. (Note that this implies that |
2601 | * in a loop calling this function, the line number may not change on |
2602 | * every iteration, if your first iteration is on line 0.) |
2603 | * |
2604 | * Returns: whether @iter moved |
2605 | */ |
2606 | gboolean |
2607 | gtk_text_iter_backward_line (GtkTextIter *iter) |
2608 | { |
2609 | GtkTextLine *new_line; |
2610 | GtkTextRealIter *real; |
2611 | gboolean offset_will_change; |
2612 | int offset; |
2613 | |
2614 | g_return_val_if_fail (iter != NULL, FALSE); |
2615 | |
2616 | real = gtk_text_iter_make_real (iter: iter); |
2617 | |
2618 | if (real == NULL) |
2619 | return FALSE; |
2620 | |
2621 | ensure_char_offsets (iter: real); |
2622 | |
2623 | check_invariants (iter); |
2624 | |
2625 | new_line = _gtk_text_line_previous (line: real->line); |
2626 | |
2627 | offset_will_change = FALSE; |
2628 | if (real->line_char_offset > 0) |
2629 | offset_will_change = TRUE; |
2630 | |
2631 | if (new_line != NULL) |
2632 | { |
2633 | real->line = new_line; |
2634 | |
2635 | adjust_line_number (iter: real, count: -1); |
2636 | } |
2637 | else |
2638 | { |
2639 | if (!offset_will_change) |
2640 | return FALSE; |
2641 | } |
2642 | |
2643 | invalidate_char_index (iter: real); |
2644 | |
2645 | real->line_byte_offset = 0; |
2646 | real->line_char_offset = 0; |
2647 | |
2648 | real->segment_byte_offset = 0; |
2649 | real->segment_char_offset = 0; |
2650 | |
2651 | /* Find first segment in line */ |
2652 | real->any_segment = real->line->segments; |
2653 | real->segment = _gtk_text_line_byte_to_segment (line: real->line, |
2654 | byte_offset: 0, seg_offset: &offset); |
2655 | |
2656 | g_assert (offset == 0); |
2657 | |
2658 | /* Note that if we are on the first line, we snap to the start of |
2659 | * the first line and return TRUE, so TRUE means the iterator |
2660 | * changed, not that the line changed; this is maybe a bit |
2661 | * weird. I'm not sure there's an obvious right thing to do though. |
2662 | */ |
2663 | |
2664 | check_invariants (iter); |
2665 | |
2666 | return TRUE; |
2667 | } |
2668 | |
2669 | |
2670 | /** |
2671 | * gtk_text_iter_forward_lines: |
2672 | * @iter: a `GtkTextIter` |
2673 | * @count: number of lines to move forward |
2674 | * |
2675 | * Moves @count lines forward, if possible. |
2676 | * |
2677 | * If @count would move past the start or end of the buffer, moves to |
2678 | * the start or end of the buffer. |
2679 | * |
2680 | * The return value indicates whether the iterator moved |
2681 | * onto a dereferenceable position; if the iterator didn’t move, or |
2682 | * moved onto the end iterator, then %FALSE is returned. If @count is 0, |
2683 | * the function does nothing and returns %FALSE. If @count is negative, |
2684 | * moves backward by 0 - @count lines. |
2685 | * |
2686 | * Returns: whether @iter moved and is dereferenceable |
2687 | */ |
2688 | gboolean |
2689 | gtk_text_iter_forward_lines (GtkTextIter *iter, int count) |
2690 | { |
2691 | FIX_OVERFLOWS (count); |
2692 | |
2693 | if (count < 0) |
2694 | return gtk_text_iter_backward_lines (iter, count: 0 - count); |
2695 | else if (count == 0) |
2696 | return FALSE; |
2697 | else if (count == 1) |
2698 | { |
2699 | check_invariants (iter); |
2700 | return gtk_text_iter_forward_line (iter); |
2701 | } |
2702 | else |
2703 | { |
2704 | int old_line; |
2705 | |
2706 | if (gtk_text_iter_is_end (iter)) |
2707 | return FALSE; |
2708 | |
2709 | old_line = gtk_text_iter_get_line (iter); |
2710 | |
2711 | gtk_text_iter_set_line (iter, line_number: old_line + count); |
2712 | |
2713 | if ((gtk_text_iter_get_line (iter) - old_line) < count) |
2714 | { |
2715 | /* count went past the last line, so move to end of last line */ |
2716 | if (!gtk_text_iter_is_end (iter)) |
2717 | gtk_text_iter_forward_to_end (iter); |
2718 | } |
2719 | |
2720 | return !gtk_text_iter_is_end (iter); |
2721 | } |
2722 | } |
2723 | |
2724 | /** |
2725 | * gtk_text_iter_backward_lines: |
2726 | * @iter: a `GtkTextIter` |
2727 | * @count: number of lines to move backward |
2728 | * |
2729 | * Moves @count lines backward, if possible. |
2730 | * |
2731 | * If @count would move past the start or end of the buffer, moves to |
2732 | * the start or end of the buffer. |
2733 | * |
2734 | * The return value indicates whether the iterator moved |
2735 | * onto a dereferenceable position; if the iterator didn’t move, or |
2736 | * moved onto the end iterator, then %FALSE is returned. If @count is 0, |
2737 | * the function does nothing and returns %FALSE. If @count is negative, |
2738 | * moves forward by 0 - @count lines. |
2739 | * |
2740 | * Returns: whether @iter moved and is dereferenceable |
2741 | */ |
2742 | gboolean |
2743 | gtk_text_iter_backward_lines (GtkTextIter *iter, int count) |
2744 | { |
2745 | FIX_OVERFLOWS (count); |
2746 | |
2747 | if (count < 0) |
2748 | return gtk_text_iter_forward_lines (iter, count: 0 - count); |
2749 | else if (count == 0) |
2750 | return FALSE; |
2751 | else if (count == 1) |
2752 | { |
2753 | return gtk_text_iter_backward_line (iter); |
2754 | } |
2755 | else |
2756 | { |
2757 | int old_line; |
2758 | |
2759 | old_line = gtk_text_iter_get_line (iter); |
2760 | |
2761 | gtk_text_iter_set_line (iter, MAX (old_line - count, 0)); |
2762 | |
2763 | return (gtk_text_iter_get_line (iter) != old_line); |
2764 | } |
2765 | } |
2766 | |
2767 | /** |
2768 | * gtk_text_iter_forward_visible_line: |
2769 | * @iter: an iterator |
2770 | * |
2771 | * Moves @iter to the start of the next visible line. |
2772 | * |
2773 | * Returns %TRUE if there |
2774 | * was a next line to move to, and %FALSE if @iter was simply moved to |
2775 | * the end of the buffer and is now not dereferenceable, or if @iter was |
2776 | * already at the end of the buffer. |
2777 | * |
2778 | * Returns: whether @iter can be dereferenced |
2779 | */ |
2780 | gboolean |
2781 | gtk_text_iter_forward_visible_line (GtkTextIter *iter) |
2782 | { |
2783 | while (gtk_text_iter_forward_line (iter)) |
2784 | { |
2785 | if (!_gtk_text_btree_char_is_invisible (iter)) |
2786 | return TRUE; |
2787 | else |
2788 | { |
2789 | do |
2790 | { |
2791 | if (!gtk_text_iter_forward_char (iter)) |
2792 | return FALSE; |
2793 | |
2794 | if (!_gtk_text_btree_char_is_invisible (iter)) |
2795 | return TRUE; |
2796 | } |
2797 | while (!gtk_text_iter_ends_line (iter)); |
2798 | } |
2799 | } |
2800 | |
2801 | return FALSE; |
2802 | } |
2803 | |
2804 | /** |
2805 | * gtk_text_iter_backward_visible_line: |
2806 | * @iter: an iterator |
2807 | * |
2808 | * Moves @iter to the start of the previous visible line. |
2809 | * |
2810 | * Returns %TRUE if |
2811 | * @iter could be moved; i.e. if @iter was at character offset 0, this |
2812 | * function returns %FALSE. Therefore if @iter was already on line 0, |
2813 | * but not at the start of the line, @iter is snapped to the start of |
2814 | * the line and the function returns %TRUE. (Note that this implies that |
2815 | * in a loop calling this function, the line number may not change on |
2816 | * every iteration, if your first iteration is on line 0.) |
2817 | * |
2818 | * Returns: whether @iter moved |
2819 | */ |
2820 | gboolean |
2821 | gtk_text_iter_backward_visible_line (GtkTextIter *iter) |
2822 | { |
2823 | while (gtk_text_iter_backward_line (iter)) |
2824 | { |
2825 | if (!_gtk_text_btree_char_is_invisible (iter)) |
2826 | return TRUE; |
2827 | else |
2828 | { |
2829 | do |
2830 | { |
2831 | if (!gtk_text_iter_backward_char (iter)) |
2832 | return FALSE; |
2833 | |
2834 | if (!_gtk_text_btree_char_is_invisible (iter)) |
2835 | return TRUE; |
2836 | } |
2837 | while (!gtk_text_iter_starts_line (iter)); |
2838 | } |
2839 | } |
2840 | |
2841 | return FALSE; |
2842 | } |
2843 | |
2844 | /** |
2845 | * gtk_text_iter_forward_visible_lines: |
2846 | * @iter: a `GtkTextIter` |
2847 | * @count: number of lines to move forward |
2848 | * |
2849 | * Moves @count visible lines forward, if possible. |
2850 | * |
2851 | * If @count would move past the start or end of the buffer, moves to |
2852 | * the start or end of the buffer. |
2853 | * |
2854 | * The return value indicates whether the iterator moved |
2855 | * onto a dereferenceable position; if the iterator didn’t move, or |
2856 | * moved onto the end iterator, then %FALSE is returned. If @count is 0, |
2857 | * the function does nothing and returns %FALSE. If @count is negative, |
2858 | * moves backward by 0 - @count lines. |
2859 | * |
2860 | * Returns: whether @iter moved and is dereferenceable |
2861 | */ |
2862 | gboolean |
2863 | gtk_text_iter_forward_visible_lines (GtkTextIter *iter, |
2864 | int count) |
2865 | { |
2866 | FIX_OVERFLOWS (count); |
2867 | |
2868 | if (count < 0) |
2869 | return gtk_text_iter_backward_visible_lines (iter, count: 0 - count); |
2870 | else if (count == 0) |
2871 | return FALSE; |
2872 | else if (count == 1) |
2873 | { |
2874 | check_invariants (iter); |
2875 | return gtk_text_iter_forward_visible_line (iter); |
2876 | } |
2877 | else |
2878 | { |
2879 | while (gtk_text_iter_forward_visible_line (iter) && count > 0) |
2880 | count--; |
2881 | return count == 0; |
2882 | } |
2883 | } |
2884 | |
2885 | /** |
2886 | * gtk_text_iter_backward_visible_lines: |
2887 | * @iter: a `GtkTextIter` |
2888 | * @count: number of lines to move backward |
2889 | * |
2890 | * Moves @count visible lines backward, if possible. |
2891 | * |
2892 | * If @count would move past the start or end of the buffer, moves to |
2893 | * the start or end of the buffer. |
2894 | * |
2895 | * The return value indicates whether the iterator moved |
2896 | * onto a dereferenceable position; if the iterator didn’t move, or |
2897 | * moved onto the end iterator, then %FALSE is returned. If @count is 0, |
2898 | * the function does nothing and returns %FALSE. If @count is negative, |
2899 | * moves forward by 0 - @count lines. |
2900 | * |
2901 | * Returns: whether @iter moved and is dereferenceable |
2902 | */ |
2903 | gboolean |
2904 | gtk_text_iter_backward_visible_lines (GtkTextIter *iter, |
2905 | int count) |
2906 | { |
2907 | FIX_OVERFLOWS (count); |
2908 | |
2909 | if (count < 0) |
2910 | return gtk_text_iter_forward_visible_lines (iter, count: 0 - count); |
2911 | else if (count == 0) |
2912 | return FALSE; |
2913 | else if (count == 1) |
2914 | { |
2915 | return gtk_text_iter_backward_visible_line (iter); |
2916 | } |
2917 | else |
2918 | { |
2919 | while (gtk_text_iter_backward_visible_line (iter) && count > 0) |
2920 | count--; |
2921 | return count == 0; |
2922 | } |
2923 | } |
2924 | |
2925 | typedef gboolean (* FindLogAttrFunc) (const PangoLogAttr *attrs, |
2926 | int offset, |
2927 | int len, |
2928 | int *found_offset, |
2929 | gboolean already_moved_initially); |
2930 | |
2931 | typedef gboolean (* TestLogAttrFunc) (const PangoLogAttr *attrs, |
2932 | int offset, |
2933 | int min_offset, |
2934 | int len); |
2935 | |
2936 | /* Word funcs */ |
2937 | |
2938 | static gboolean |
2939 | find_word_end_func (const PangoLogAttr *attrs, |
2940 | int offset, |
2941 | int len, |
2942 | int *found_offset, |
2943 | gboolean already_moved_initially) |
2944 | { |
2945 | if (!already_moved_initially) |
2946 | ++offset; |
2947 | |
2948 | /* Find end of next word */ |
2949 | while (offset <= len) |
2950 | { |
2951 | if (attrs[offset].is_word_end) |
2952 | { |
2953 | *found_offset = offset; |
2954 | return TRUE; |
2955 | } |
2956 | |
2957 | ++offset; |
2958 | } |
2959 | |
2960 | return FALSE; |
2961 | } |
2962 | |
2963 | static gboolean |
2964 | is_word_end_func (const PangoLogAttr *attrs, |
2965 | int offset, |
2966 | int min_offset, |
2967 | int len) |
2968 | { |
2969 | return attrs[offset].is_word_end; |
2970 | } |
2971 | |
2972 | static gboolean |
2973 | find_word_start_func (const PangoLogAttr *attrs, |
2974 | int offset, |
2975 | int len, |
2976 | int *found_offset, |
2977 | gboolean already_moved_initially) |
2978 | { |
2979 | if (!already_moved_initially) |
2980 | --offset; |
2981 | |
2982 | /* Find start of prev word */ |
2983 | while (offset >= 0) |
2984 | { |
2985 | if (attrs[offset].is_word_start) |
2986 | { |
2987 | *found_offset = offset; |
2988 | return TRUE; |
2989 | } |
2990 | |
2991 | --offset; |
2992 | } |
2993 | |
2994 | return FALSE; |
2995 | } |
2996 | |
2997 | static gboolean |
2998 | is_word_start_func (const PangoLogAttr *attrs, |
2999 | int offset, |
3000 | int min_offset, |
3001 | int len) |
3002 | { |
3003 | return attrs[offset].is_word_start; |
3004 | } |
3005 | |
3006 | static gboolean |
3007 | inside_word_func (const PangoLogAttr *attrs, |
3008 | int offset, |
3009 | int min_offset, |
3010 | int len) |
3011 | { |
3012 | /* Find next word start or end */ |
3013 | while (offset >= min_offset && |
3014 | !(attrs[offset].is_word_start || attrs[offset].is_word_end)) |
3015 | --offset; |
3016 | |
3017 | if (offset >= 0) |
3018 | return attrs[offset].is_word_start; |
3019 | else |
3020 | return FALSE; |
3021 | } |
3022 | |
3023 | /* Sentence funcs */ |
3024 | |
3025 | static gboolean |
3026 | find_sentence_end_func (const PangoLogAttr *attrs, |
3027 | int offset, |
3028 | int len, |
3029 | int *found_offset, |
3030 | gboolean already_moved_initially) |
3031 | { |
3032 | if (!already_moved_initially) |
3033 | ++offset; |
3034 | |
3035 | /* Find end of next sentence */ |
3036 | while (offset <= len) |
3037 | { |
3038 | if (attrs[offset].is_sentence_end) |
3039 | { |
3040 | *found_offset = offset; |
3041 | return TRUE; |
3042 | } |
3043 | |
3044 | ++offset; |
3045 | } |
3046 | |
3047 | return FALSE; |
3048 | } |
3049 | |
3050 | static gboolean |
3051 | is_sentence_end_func (const PangoLogAttr *attrs, |
3052 | int offset, |
3053 | int min_offset, |
3054 | int len) |
3055 | { |
3056 | return attrs[offset].is_sentence_end; |
3057 | } |
3058 | |
3059 | static gboolean |
3060 | find_sentence_start_func (const PangoLogAttr *attrs, |
3061 | int offset, |
3062 | int len, |
3063 | int *found_offset, |
3064 | gboolean already_moved_initially) |
3065 | { |
3066 | if (!already_moved_initially) |
3067 | --offset; |
3068 | |
3069 | /* Find start of prev sentence */ |
3070 | while (offset >= 0) |
3071 | { |
3072 | if (attrs[offset].is_sentence_start) |
3073 | { |
3074 | *found_offset = offset; |
3075 | return TRUE; |
3076 | } |
3077 | |
3078 | --offset; |
3079 | } |
3080 | |
3081 | return FALSE; |
3082 | } |
3083 | |
3084 | static gboolean |
3085 | is_sentence_start_func (const PangoLogAttr *attrs, |
3086 | int offset, |
3087 | int min_offset, |
3088 | int len) |
3089 | { |
3090 | return attrs[offset].is_sentence_start; |
3091 | } |
3092 | |
3093 | static gboolean |
3094 | inside_sentence_func (const PangoLogAttr *attrs, |
3095 | int offset, |
3096 | int min_offset, |
3097 | int len) |
3098 | { |
3099 | /* Find next sentence start or end */ |
3100 | while (!(attrs[offset].is_sentence_start || attrs[offset].is_sentence_end)) |
3101 | { |
3102 | --offset; |
3103 | if (offset < min_offset) |
3104 | return FALSE; |
3105 | } |
3106 | |
3107 | return attrs[offset].is_sentence_start; |
3108 | } |
3109 | |
3110 | static gboolean |
3111 | test_log_attrs (const GtkTextIter *iter, |
3112 | TestLogAttrFunc func) |
3113 | { |
3114 | int char_len; |
3115 | const PangoLogAttr *attrs; |
3116 | int offset; |
3117 | |
3118 | g_return_val_if_fail (iter != NULL, FALSE); |
3119 | |
3120 | attrs = _gtk_text_buffer_get_line_log_attrs (buffer: gtk_text_iter_get_buffer (iter), |
3121 | anywhere_in_line: iter, char_len: &char_len); |
3122 | |
3123 | offset = gtk_text_iter_get_line_offset (iter); |
3124 | |
3125 | g_assert (offset <= char_len); |
3126 | |
3127 | return (* func) (attrs, offset, 0, char_len); |
3128 | } |
3129 | |
3130 | static gboolean |
3131 | find_line_log_attrs (const GtkTextIter *iter, |
3132 | FindLogAttrFunc func, |
3133 | int *found_offset, |
3134 | gboolean already_moved_initially) |
3135 | { |
3136 | int char_len; |
3137 | const PangoLogAttr *attrs; |
3138 | int offset; |
3139 | |
3140 | g_return_val_if_fail (iter != NULL, FALSE); |
3141 | |
3142 | attrs = _gtk_text_buffer_get_line_log_attrs (buffer: gtk_text_iter_get_buffer (iter), |
3143 | anywhere_in_line: iter, char_len: &char_len); |
3144 | |
3145 | offset = gtk_text_iter_get_line_offset (iter); |
3146 | |
3147 | return (* func) (attrs, |
3148 | offset, |
3149 | char_len, |
3150 | found_offset, |
3151 | already_moved_initially); |
3152 | } |
3153 | |
3154 | static gboolean |
3155 | find_by_log_attrs (GtkTextIter *arg_iter, |
3156 | FindLogAttrFunc func, |
3157 | gboolean forward) |
3158 | { |
3159 | GtkTextIter iter; |
3160 | gboolean already_moved_initially = FALSE; |
3161 | |
3162 | g_return_val_if_fail (arg_iter != NULL, FALSE); |
3163 | |
3164 | iter = *arg_iter; |
3165 | |
3166 | while (TRUE) |
3167 | { |
3168 | int offset = 0; |
3169 | gboolean found; |
3170 | |
3171 | found = find_line_log_attrs (iter: &iter, func, found_offset: &offset, already_moved_initially); |
3172 | |
3173 | if (found) |
3174 | { |
3175 | gboolean moved; |
3176 | |
3177 | gtk_text_iter_set_line_offset (iter: &iter, char_on_line: offset); |
3178 | |
3179 | moved = !gtk_text_iter_equal (lhs: &iter, rhs: arg_iter); |
3180 | |
3181 | *arg_iter = iter; |
3182 | return moved && !gtk_text_iter_is_end (iter: arg_iter); |
3183 | } |
3184 | |
3185 | if (forward) |
3186 | { |
3187 | if (!gtk_text_iter_forward_line (iter: &iter)) |
3188 | return FALSE; |
3189 | |
3190 | already_moved_initially = TRUE; |
3191 | } |
3192 | else |
3193 | { |
3194 | /* Go to end of previous line. First go to the current line offset 0, |
3195 | * because backward_line() snaps to start of line 0 if iter is already |
3196 | * on line 0. |
3197 | */ |
3198 | gtk_text_iter_set_line_offset (iter: &iter, char_on_line: 0); |
3199 | |
3200 | if (gtk_text_iter_backward_line (iter: &iter)) |
3201 | { |
3202 | if (!gtk_text_iter_ends_line (iter: &iter)) |
3203 | gtk_text_iter_forward_to_line_end (iter: &iter); |
3204 | |
3205 | already_moved_initially = TRUE; |
3206 | } |
3207 | else |
3208 | { |
3209 | return FALSE; |
3210 | } |
3211 | } |
3212 | } |
3213 | } |
3214 | |
3215 | static gboolean |
3216 | find_visible_by_log_attrs (GtkTextIter *iter, |
3217 | FindLogAttrFunc func, |
3218 | gboolean forward) |
3219 | { |
3220 | GtkTextIter pos; |
3221 | |
3222 | g_return_val_if_fail (iter != NULL, FALSE); |
3223 | |
3224 | pos = *iter; |
3225 | |
3226 | while (TRUE) |
3227 | { |
3228 | GtkTextIter pos_before = pos; |
3229 | |
3230 | find_by_log_attrs (arg_iter: &pos, func, forward); |
3231 | |
3232 | if (gtk_text_iter_equal (lhs: &pos_before, rhs: &pos)) |
3233 | break; |
3234 | |
3235 | if (!_gtk_text_btree_char_is_invisible (iter: &pos)) |
3236 | { |
3237 | *iter = pos; |
3238 | return !gtk_text_iter_is_end (iter); |
3239 | } |
3240 | } |
3241 | |
3242 | return FALSE; |
3243 | } |
3244 | |
3245 | typedef gboolean (* OneStepFunc) (GtkTextIter *iter); |
3246 | typedef gboolean (* MultipleStepFunc) (GtkTextIter *iter, int count); |
3247 | |
3248 | static gboolean |
3249 | move_multiple_steps (GtkTextIter *iter, |
3250 | int count, |
3251 | OneStepFunc step_forward, |
3252 | MultipleStepFunc n_steps_backward) |
3253 | { |
3254 | g_return_val_if_fail (iter != NULL, FALSE); |
3255 | |
3256 | FIX_OVERFLOWS (count); |
3257 | |
3258 | if (count == 0) |
3259 | return FALSE; |
3260 | |
3261 | if (count < 0) |
3262 | return n_steps_backward (iter, -count); |
3263 | |
3264 | if (!step_forward (iter)) |
3265 | return FALSE; |
3266 | --count; |
3267 | |
3268 | while (count > 0) |
3269 | { |
3270 | if (!step_forward (iter)) |
3271 | break; |
3272 | --count; |
3273 | } |
3274 | |
3275 | return !gtk_text_iter_is_end (iter); |
3276 | } |
3277 | |
3278 | |
3279 | /** |
3280 | * gtk_text_iter_forward_word_end: |
3281 | * @iter: a `GtkTextIter` |
3282 | * |
3283 | * Moves forward to the next word end. |
3284 | * |
3285 | * If @iter is currently on a word end, moves forward to the |
3286 | * next one after that. |
3287 | * |
3288 | * Word breaks are determined by Pango and should be correct |
3289 | * for nearly any language. |
3290 | * |
3291 | * Returns: %TRUE if @iter moved and is not the end iterator |
3292 | */ |
3293 | gboolean |
3294 | gtk_text_iter_forward_word_end (GtkTextIter *iter) |
3295 | { |
3296 | return find_by_log_attrs (arg_iter: iter, func: find_word_end_func, TRUE); |
3297 | } |
3298 | |
3299 | /** |
3300 | * gtk_text_iter_backward_word_start: |
3301 | * @iter: a `GtkTextIter` |
3302 | * |
3303 | * Moves backward to the previous word start. |
3304 | * |
3305 | * If @iter is currently on a word start, moves backward to the |
3306 | * next one after that. |
3307 | * |
3308 | * Word breaks are determined by Pango and should be correct |
3309 | * for nearly any language |
3310 | * |
3311 | * Returns: %TRUE if @iter moved and is not the end iterator |
3312 | */ |
3313 | gboolean |
3314 | gtk_text_iter_backward_word_start (GtkTextIter *iter) |
3315 | { |
3316 | return find_by_log_attrs (arg_iter: iter, func: find_word_start_func, FALSE); |
3317 | } |
3318 | |
3319 | /* FIXME a loop around a truly slow function means |
3320 | * a truly spectacularly slow function. |
3321 | */ |
3322 | |
3323 | /** |
3324 | * gtk_text_iter_forward_word_ends: |
3325 | * @iter: a `GtkTextIter` |
3326 | * @count: number of times to move |
3327 | * |
3328 | * Calls gtk_text_iter_forward_word_end() up to @count times. |
3329 | * |
3330 | * Returns: %TRUE if @iter moved and is not the end iterator |
3331 | */ |
3332 | gboolean |
3333 | gtk_text_iter_forward_word_ends (GtkTextIter *iter, |
3334 | int count) |
3335 | { |
3336 | return move_multiple_steps (iter, count, |
3337 | step_forward: gtk_text_iter_forward_word_end, |
3338 | n_steps_backward: gtk_text_iter_backward_word_starts); |
3339 | } |
3340 | |
3341 | /** |
3342 | * gtk_text_iter_backward_word_starts: |
3343 | * @iter: a `GtkTextIter` |
3344 | * @count: number of times to move |
3345 | * |
3346 | * Calls gtk_text_iter_backward_word_start() up to @count times. |
3347 | * |
3348 | * Returns: %TRUE if @iter moved and is not the end iterator |
3349 | */ |
3350 | gboolean |
3351 | gtk_text_iter_backward_word_starts (GtkTextIter *iter, |
3352 | int count) |
3353 | { |
3354 | return move_multiple_steps (iter, count, |
3355 | step_forward: gtk_text_iter_backward_word_start, |
3356 | n_steps_backward: gtk_text_iter_forward_word_ends); |
3357 | } |
3358 | |
3359 | /** |
3360 | * gtk_text_iter_forward_visible_word_end: |
3361 | * @iter: a `GtkTextIter` |
3362 | * |
3363 | * Moves forward to the next visible word end. |
3364 | * |
3365 | * If @iter is currently on a word end, moves forward to the |
3366 | * next one after that. |
3367 | * |
3368 | * Word breaks are determined by Pango and should be correct |
3369 | * for nearly any language |
3370 | * |
3371 | * Returns: %TRUE if @iter moved and is not the end iterator |
3372 | */ |
3373 | gboolean |
3374 | gtk_text_iter_forward_visible_word_end (GtkTextIter *iter) |
3375 | { |
3376 | return find_visible_by_log_attrs (iter, func: find_word_end_func, TRUE); |
3377 | } |
3378 | |
3379 | /** |
3380 | * gtk_text_iter_backward_visible_word_start: |
3381 | * @iter: a `GtkTextIter` |
3382 | * |
3383 | * Moves backward to the previous visible word start. |
3384 | * |
3385 | * If @iter is currently on a word start, moves backward to the |
3386 | * next one after that. |
3387 | * |
3388 | * Word breaks are determined by Pango and should be correct |
3389 | * for nearly any language. |
3390 | * |
3391 | * Returns: %TRUE if @iter moved and is not the end iterator |
3392 | */ |
3393 | gboolean |
3394 | gtk_text_iter_backward_visible_word_start (GtkTextIter *iter) |
3395 | { |
3396 | return find_visible_by_log_attrs (iter, func: find_word_start_func, FALSE); |
3397 | } |
3398 | |
3399 | /** |
3400 | * gtk_text_iter_forward_visible_word_ends: |
3401 | * @iter: a `GtkTextIter` |
3402 | * @count: number of times to move |
3403 | * |
3404 | * Calls gtk_text_iter_forward_visible_word_end() up to @count times. |
3405 | * |
3406 | * Returns: %TRUE if @iter moved and is not the end iterator |
3407 | */ |
3408 | gboolean |
3409 | gtk_text_iter_forward_visible_word_ends (GtkTextIter *iter, |
3410 | int count) |
3411 | { |
3412 | return move_multiple_steps (iter, count, |
3413 | step_forward: gtk_text_iter_forward_visible_word_end, |
3414 | n_steps_backward: gtk_text_iter_backward_visible_word_starts); |
3415 | } |
3416 | |
3417 | /** |
3418 | * gtk_text_iter_backward_visible_word_starts: |
3419 | * @iter: a `GtkTextIter` |
3420 | * @count: number of times to move |
3421 | * |
3422 | * Calls gtk_text_iter_backward_visible_word_start() up to @count times. |
3423 | * |
3424 | * Returns: %TRUE if @iter moved and is not the end iterator |
3425 | */ |
3426 | gboolean |
3427 | gtk_text_iter_backward_visible_word_starts (GtkTextIter *iter, |
3428 | int count) |
3429 | { |
3430 | return move_multiple_steps (iter, count, |
3431 | step_forward: gtk_text_iter_backward_visible_word_start, |
3432 | n_steps_backward: gtk_text_iter_forward_visible_word_ends); |
3433 | } |
3434 | |
3435 | /** |
3436 | * gtk_text_iter_starts_word: |
3437 | * @iter: a `GtkTextIter` |
3438 | * |
3439 | * Determines whether @iter begins a natural-language word. |
3440 | * |
3441 | * Word breaks are determined by Pango and should be correct |
3442 | * for nearly any language. |
3443 | * |
3444 | * Returns: %TRUE if @iter is at the start of a word |
3445 | */ |
3446 | gboolean |
3447 | gtk_text_iter_starts_word (const GtkTextIter *iter) |
3448 | { |
3449 | return test_log_attrs (iter, func: is_word_start_func); |
3450 | } |
3451 | |
3452 | /** |
3453 | * gtk_text_iter_ends_word: |
3454 | * @iter: a `GtkTextIter` |
3455 | * |
3456 | * Determines whether @iter ends a natural-language word. |
3457 | * |
3458 | * Word breaks are determined by Pango and should be correct |
3459 | * for nearly any language. |
3460 | * |
3461 | * Returns: %TRUE if @iter is at the end of a word |
3462 | */ |
3463 | gboolean |
3464 | gtk_text_iter_ends_word (const GtkTextIter *iter) |
3465 | { |
3466 | return test_log_attrs (iter, func: is_word_end_func); |
3467 | } |
3468 | |
3469 | /** |
3470 | * gtk_text_iter_inside_word: |
3471 | * @iter: a `GtkTextIter` |
3472 | * |
3473 | * Determines whether the character pointed by @iter is part of a |
3474 | * natural-language word (as opposed to say inside some whitespace). |
3475 | * |
3476 | * Word breaks are determined by Pango and should be correct |
3477 | * for nearly any language. |
3478 | * |
3479 | * Note that if [method@Gtk.TextIter.starts_word] returns %TRUE, |
3480 | * then this function returns %TRUE too, since @iter points to |
3481 | * the first character of the word. |
3482 | * |
3483 | * Returns: %TRUE if @iter is inside a word |
3484 | */ |
3485 | gboolean |
3486 | gtk_text_iter_inside_word (const GtkTextIter *iter) |
3487 | { |
3488 | return test_log_attrs (iter, func: inside_word_func); |
3489 | } |
3490 | |
3491 | /** |
3492 | * gtk_text_iter_starts_sentence: |
3493 | * @iter: a `GtkTextIter` |
3494 | * |
3495 | * Determines whether @iter begins a sentence. |
3496 | * |
3497 | * Sentence boundaries are determined by Pango and |
3498 | * should be correct for nearly any language. |
3499 | * |
3500 | * Returns: %TRUE if @iter is at the start of a sentence. |
3501 | */ |
3502 | gboolean |
3503 | gtk_text_iter_starts_sentence (const GtkTextIter *iter) |
3504 | { |
3505 | return test_log_attrs (iter, func: is_sentence_start_func); |
3506 | } |
3507 | |
3508 | /** |
3509 | * gtk_text_iter_ends_sentence: |
3510 | * @iter: a `GtkTextIter` |
3511 | * |
3512 | * Determines whether @iter ends a sentence. |
3513 | * |
3514 | * Sentence boundaries are determined by Pango and should |
3515 | * be correct for nearly any language. |
3516 | * |
3517 | * Returns: %TRUE if @iter is at the end of a sentence. |
3518 | */ |
3519 | gboolean |
3520 | gtk_text_iter_ends_sentence (const GtkTextIter *iter) |
3521 | { |
3522 | return test_log_attrs (iter, func: is_sentence_end_func); |
3523 | } |
3524 | |
3525 | /** |
3526 | * gtk_text_iter_inside_sentence: |
3527 | * @iter: a `GtkTextIter` |
3528 | * |
3529 | * Determines whether @iter is inside a sentence (as opposed to in |
3530 | * between two sentences, e.g. after a period and before the first |
3531 | * letter of the next sentence). |
3532 | * |
3533 | * Sentence boundaries are determined by Pango and should be correct |
3534 | * for nearly any language. |
3535 | * |
3536 | * Returns: %TRUE if @iter is inside a sentence. |
3537 | */ |
3538 | gboolean |
3539 | gtk_text_iter_inside_sentence (const GtkTextIter *iter) |
3540 | { |
3541 | return test_log_attrs (iter, func: inside_sentence_func); |
3542 | } |
3543 | |
3544 | /** |
3545 | * gtk_text_iter_forward_sentence_end: |
3546 | * @iter: a `GtkTextIter` |
3547 | * |
3548 | * Moves forward to the next sentence end. |
3549 | * |
3550 | * If @iter is at the end of a sentence, moves to the next |
3551 | * end of sentence. |
3552 | * |
3553 | * Sentence boundaries are determined by Pango and should |
3554 | * be correct for nearly any language. |
3555 | * |
3556 | * Returns: %TRUE if @iter moved and is not the end iterator |
3557 | */ |
3558 | gboolean |
3559 | gtk_text_iter_forward_sentence_end (GtkTextIter *iter) |
3560 | { |
3561 | return find_by_log_attrs (arg_iter: iter, func: find_sentence_end_func, TRUE); |
3562 | } |
3563 | |
3564 | /** |
3565 | * gtk_text_iter_backward_sentence_start: |
3566 | * @iter: a `GtkTextIter` |
3567 | * |
3568 | * Moves backward to the previous sentence start. |
3569 | * |
3570 | * If @iter is already at the start of a sentence, moves backward |
3571 | * to the next one. |
3572 | * |
3573 | * Sentence boundaries are determined by Pango and should |
3574 | * be correct for nearly any language. |
3575 | * |
3576 | * Returns: %TRUE if @iter moved and is not the end iterator |
3577 | */ |
3578 | gboolean |
3579 | gtk_text_iter_backward_sentence_start (GtkTextIter *iter) |
3580 | { |
3581 | return find_by_log_attrs (arg_iter: iter, func: find_sentence_start_func, FALSE); |
3582 | } |
3583 | |
3584 | /* FIXME a loop around a truly slow function means |
3585 | * a truly spectacularly slow function. |
3586 | */ |
3587 | /** |
3588 | * gtk_text_iter_forward_sentence_ends: |
3589 | * @iter: a `GtkTextIter` |
3590 | * @count: number of sentences to move |
3591 | * |
3592 | * Calls gtk_text_iter_forward_sentence_end() @count times. |
3593 | * |
3594 | * If @count is negative, moves backward instead of forward. |
3595 | * |
3596 | * Returns: %TRUE if @iter moved and is not the end iterator |
3597 | */ |
3598 | gboolean |
3599 | gtk_text_iter_forward_sentence_ends (GtkTextIter *iter, |
3600 | int count) |
3601 | { |
3602 | return move_multiple_steps (iter, count, |
3603 | step_forward: gtk_text_iter_forward_sentence_end, |
3604 | n_steps_backward: gtk_text_iter_backward_sentence_starts); |
3605 | } |
3606 | |
3607 | /** |
3608 | * gtk_text_iter_backward_sentence_starts: |
3609 | * @iter: a `GtkTextIter` |
3610 | * @count: number of sentences to move |
3611 | * |
3612 | * Calls gtk_text_iter_backward_sentence_start() up to @count times. |
3613 | * |
3614 | * If @count is negative, moves forward instead of backward. |
3615 | * |
3616 | * Returns: %TRUE if @iter moved and is not the end iterator |
3617 | */ |
3618 | gboolean |
3619 | gtk_text_iter_backward_sentence_starts (GtkTextIter *iter, |
3620 | int count) |
3621 | { |
3622 | return move_multiple_steps (iter, count, |
3623 | step_forward: gtk_text_iter_backward_sentence_start, |
3624 | n_steps_backward: gtk_text_iter_forward_sentence_ends); |
3625 | } |
3626 | |
3627 | static gboolean |
3628 | find_forward_cursor_pos_func (const PangoLogAttr *attrs, |
3629 | int offset, |
3630 | int len, |
3631 | int *found_offset, |
3632 | gboolean already_moved_initially) |
3633 | { |
3634 | if (!already_moved_initially) |
3635 | ++offset; |
3636 | |
3637 | while (offset <= len) |
3638 | { |
3639 | if (attrs[offset].is_cursor_position) |
3640 | { |
3641 | *found_offset = offset; |
3642 | return TRUE; |
3643 | } |
3644 | |
3645 | ++offset; |
3646 | } |
3647 | |
3648 | return FALSE; |
3649 | } |
3650 | |
3651 | static gboolean |
3652 | find_backward_cursor_pos_func (const PangoLogAttr *attrs, |
3653 | int offset, |
3654 | int len, |
3655 | int *found_offset, |
3656 | gboolean already_moved_initially) |
3657 | { |
3658 | if (!already_moved_initially) |
3659 | --offset; |
3660 | |
3661 | while (offset >= 0) |
3662 | { |
3663 | if (attrs[offset].is_cursor_position) |
3664 | { |
3665 | *found_offset = offset; |
3666 | return TRUE; |
3667 | } |
3668 | |
3669 | --offset; |
3670 | } |
3671 | |
3672 | return FALSE; |
3673 | } |
3674 | |
3675 | static gboolean |
3676 | is_cursor_pos_func (const PangoLogAttr *attrs, |
3677 | int offset, |
3678 | int min_offset, |
3679 | int len) |
3680 | { |
3681 | return attrs[offset].is_cursor_position; |
3682 | } |
3683 | |
3684 | /** |
3685 | * gtk_text_iter_forward_cursor_position: |
3686 | * @iter: a `GtkTextIter` |
3687 | * |
3688 | * Moves @iter forward by a single cursor position. |
3689 | * |
3690 | * Cursor positions are (unsurprisingly) positions where the |
3691 | * cursor can appear. Perhaps surprisingly, there may not be |
3692 | * a cursor position between all characters. The most common |
3693 | * example for European languages would be a carriage return/newline |
3694 | * sequence. |
3695 | * |
3696 | * For some Unicode characters, the equivalent of say the letter “a” |
3697 | * with an accent mark will be represented as two characters, first |
3698 | * the letter then a "combining mark" that causes the accent to be |
3699 | * rendered; so the cursor can’t go between those two characters. |
3700 | * |
3701 | * See also the [struct@Pango.LogAttr] struct and the [func@Pango.break] |
3702 | * function. |
3703 | * |
3704 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3705 | */ |
3706 | gboolean |
3707 | gtk_text_iter_forward_cursor_position (GtkTextIter *iter) |
3708 | { |
3709 | return find_by_log_attrs (arg_iter: iter, func: find_forward_cursor_pos_func, TRUE); |
3710 | } |
3711 | |
3712 | /** |
3713 | * gtk_text_iter_backward_cursor_position: |
3714 | * @iter: a `GtkTextIter` |
3715 | * |
3716 | * Like gtk_text_iter_forward_cursor_position(), but moves backward. |
3717 | * |
3718 | * Returns: %TRUE if we moved |
3719 | */ |
3720 | gboolean |
3721 | gtk_text_iter_backward_cursor_position (GtkTextIter *iter) |
3722 | { |
3723 | return find_by_log_attrs (arg_iter: iter, func: find_backward_cursor_pos_func, FALSE); |
3724 | } |
3725 | |
3726 | /** |
3727 | * gtk_text_iter_forward_cursor_positions: |
3728 | * @iter: a `GtkTextIter` |
3729 | * @count: number of positions to move |
3730 | * |
3731 | * Moves up to @count cursor positions. |
3732 | * |
3733 | * See [method@Gtk.TextIter.forward_cursor_position] for details. |
3734 | * |
3735 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3736 | */ |
3737 | gboolean |
3738 | gtk_text_iter_forward_cursor_positions (GtkTextIter *iter, |
3739 | int count) |
3740 | { |
3741 | return move_multiple_steps (iter, count, |
3742 | step_forward: gtk_text_iter_forward_cursor_position, |
3743 | n_steps_backward: gtk_text_iter_backward_cursor_positions); |
3744 | } |
3745 | |
3746 | /** |
3747 | * gtk_text_iter_backward_cursor_positions: |
3748 | * @iter: a `GtkTextIter` |
3749 | * @count: number of positions to move |
3750 | * |
3751 | * Moves up to @count cursor positions. |
3752 | * |
3753 | * See [method@Gtk.TextIter.forward_cursor_position] for details. |
3754 | * |
3755 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3756 | */ |
3757 | gboolean |
3758 | gtk_text_iter_backward_cursor_positions (GtkTextIter *iter, |
3759 | int count) |
3760 | { |
3761 | return move_multiple_steps (iter, count, |
3762 | step_forward: gtk_text_iter_backward_cursor_position, |
3763 | n_steps_backward: gtk_text_iter_forward_cursor_positions); |
3764 | } |
3765 | |
3766 | /** |
3767 | * gtk_text_iter_forward_visible_cursor_position: |
3768 | * @iter: a `GtkTextIter` |
3769 | * |
3770 | * Moves @iter forward to the next visible cursor position. |
3771 | * |
3772 | * See [method@Gtk.TextIter.forward_cursor_position] for details. |
3773 | * |
3774 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3775 | */ |
3776 | gboolean |
3777 | gtk_text_iter_forward_visible_cursor_position (GtkTextIter *iter) |
3778 | { |
3779 | return find_visible_by_log_attrs (iter, func: find_forward_cursor_pos_func, TRUE); |
3780 | } |
3781 | |
3782 | /** |
3783 | * gtk_text_iter_backward_visible_cursor_position: |
3784 | * @iter: a `GtkTextIter` |
3785 | * |
3786 | * Moves @iter forward to the previous visible cursor position. |
3787 | * |
3788 | * See [method@Gtk.TextIter.backward_cursor_position] for details. |
3789 | * |
3790 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3791 | */ |
3792 | gboolean |
3793 | gtk_text_iter_backward_visible_cursor_position (GtkTextIter *iter) |
3794 | { |
3795 | return find_visible_by_log_attrs (iter, func: find_backward_cursor_pos_func, FALSE); |
3796 | } |
3797 | |
3798 | /** |
3799 | * gtk_text_iter_forward_visible_cursor_positions: |
3800 | * @iter: a `GtkTextIter` |
3801 | * @count: number of positions to move |
3802 | * |
3803 | * Moves up to @count visible cursor positions. |
3804 | * |
3805 | * See [method@Gtk.TextIter.forward_cursor_position] for details. |
3806 | * |
3807 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3808 | */ |
3809 | gboolean |
3810 | gtk_text_iter_forward_visible_cursor_positions (GtkTextIter *iter, |
3811 | int count) |
3812 | { |
3813 | return move_multiple_steps (iter, count, |
3814 | step_forward: gtk_text_iter_forward_visible_cursor_position, |
3815 | n_steps_backward: gtk_text_iter_backward_visible_cursor_positions); |
3816 | } |
3817 | |
3818 | /** |
3819 | * gtk_text_iter_backward_visible_cursor_positions: |
3820 | * @iter: a `GtkTextIter` |
3821 | * @count: number of positions to move |
3822 | * |
3823 | * Moves up to @count visible cursor positions. |
3824 | * |
3825 | * See [method@Gtk.TextIter.backward_cursor_position] for details. |
3826 | * |
3827 | * Returns: %TRUE if we moved and the new position is dereferenceable |
3828 | */ |
3829 | gboolean |
3830 | gtk_text_iter_backward_visible_cursor_positions (GtkTextIter *iter, |
3831 | int count) |
3832 | { |
3833 | return move_multiple_steps (iter, count, |
3834 | step_forward: gtk_text_iter_backward_visible_cursor_position, |
3835 | n_steps_backward: gtk_text_iter_forward_visible_cursor_positions); |
3836 | } |
3837 | |
3838 | /** |
3839 | * gtk_text_iter_is_cursor_position: |
3840 | * @iter: a `GtkTextIter` |
3841 | * |
3842 | * Determine if @iter is at a cursor position. |
3843 | * |
3844 | * See [method@Gtk.TextIter.forward_cursor_position] or |
3845 | * [struct@Pango.LogAttr] or [func@Pango.break] for details |
3846 | * on what a cursor position is. |
3847 | * |
3848 | * Returns: %TRUE if the cursor can be placed at @iter |
3849 | */ |
3850 | gboolean |
3851 | gtk_text_iter_is_cursor_position (const GtkTextIter *iter) |
3852 | { |
3853 | return test_log_attrs (iter, func: is_cursor_pos_func); |
3854 | } |
3855 | |
3856 | /** |
3857 | * gtk_text_iter_set_line_offset: |
3858 | * @iter: a `GtkTextIter` |
3859 | * @char_on_line: a character offset relative to the start of @iter’s current line |
3860 | * |
3861 | * Moves @iter within a line, to a new character (not byte) offset. |
3862 | * |
3863 | * The given character offset must be less than or equal to the number |
3864 | * of characters in the line; if equal, @iter moves to the start of the |
3865 | * next line. See [method@Gtk.TextIter.set_line_index] if you have a byte |
3866 | * index rather than a character offset. |
3867 | */ |
3868 | void |
3869 | gtk_text_iter_set_line_offset (GtkTextIter *iter, |
3870 | int char_on_line) |
3871 | { |
3872 | GtkTextRealIter *real; |
3873 | int chars_in_line; |
3874 | |
3875 | g_return_if_fail (iter != NULL); |
3876 | |
3877 | real = gtk_text_iter_make_surreal (iter: iter); |
3878 | |
3879 | if (real == NULL) |
3880 | return; |
3881 | |
3882 | check_invariants (iter); |
3883 | |
3884 | chars_in_line = gtk_text_iter_get_chars_in_line (iter); |
3885 | |
3886 | g_return_if_fail (char_on_line <= chars_in_line); |
3887 | |
3888 | if (char_on_line < chars_in_line) |
3889 | iter_set_from_char_offset (iter: real, line: real->line, char_offset: char_on_line); |
3890 | else |
3891 | gtk_text_iter_forward_line (iter); /* set to start of next line */ |
3892 | |
3893 | check_invariants (iter); |
3894 | } |
3895 | |
3896 | /** |
3897 | * gtk_text_iter_set_line_index: |
3898 | * @iter: a `GtkTextIter` |
3899 | * @byte_on_line: a byte index relative to the start of @iter’s current line |
3900 | * |
3901 | * Same as gtk_text_iter_set_line_offset(), but works with a |
3902 | * byte index. The given byte index must be at |
3903 | * the start of a character, it can’t be in the middle of a UTF-8 |
3904 | * encoded character. |
3905 | */ |
3906 | void |
3907 | gtk_text_iter_set_line_index (GtkTextIter *iter, |
3908 | int byte_on_line) |
3909 | { |
3910 | GtkTextRealIter *real; |
3911 | int bytes_in_line; |
3912 | |
3913 | g_return_if_fail (iter != NULL); |
3914 | |
3915 | real = gtk_text_iter_make_surreal (iter: iter); |
3916 | |
3917 | if (real == NULL) |
3918 | return; |
3919 | |
3920 | check_invariants (iter); |
3921 | |
3922 | bytes_in_line = gtk_text_iter_get_bytes_in_line (iter); |
3923 | |
3924 | g_return_if_fail (byte_on_line <= bytes_in_line); |
3925 | |
3926 | if (byte_on_line < bytes_in_line) |
3927 | iter_set_from_byte_offset (iter: real, line: real->line, byte_offset: byte_on_line); |
3928 | else |
3929 | gtk_text_iter_forward_line (iter); |
3930 | |
3931 | if (real->segment->type == >k_text_char_type && |
3932 | (real->segment->body.chars[real->segment_byte_offset] & 0xc0) == 0x80) |
3933 | g_warning ("%s: Incorrect byte offset %d falls in the middle of a UTF-8 " |
3934 | "character; this will crash the text buffer. " |
3935 | "Byte indexes must refer to the start of a character." , |
3936 | G_STRLOC, byte_on_line); |
3937 | |
3938 | check_invariants (iter); |
3939 | } |
3940 | |
3941 | |
3942 | /** |
3943 | * gtk_text_iter_set_visible_line_offset: |
3944 | * @iter: a `GtkTextIter` |
3945 | * @char_on_line: a character offset |
3946 | * |
3947 | * Like gtk_text_iter_set_line_offset(), but the offset is in visible |
3948 | * characters, i.e. text with a tag making it invisible is not |
3949 | * counted in the offset. |
3950 | */ |
3951 | void |
3952 | gtk_text_iter_set_visible_line_offset (GtkTextIter *iter, |
3953 | int char_on_line) |
3954 | { |
3955 | int chars_seen = 0; |
3956 | GtkTextIter pos; |
3957 | |
3958 | g_return_if_fail (iter != NULL); |
3959 | |
3960 | gtk_text_iter_set_line_offset (iter, char_on_line: 0); |
3961 | |
3962 | pos = *iter; |
3963 | |
3964 | /* For now we use a ludicrously slow implementation */ |
3965 | while (chars_seen < char_on_line) |
3966 | { |
3967 | if (!_gtk_text_btree_char_is_invisible (iter: &pos)) |
3968 | ++chars_seen; |
3969 | |
3970 | if (!gtk_text_iter_forward_char (iter: &pos)) |
3971 | break; |
3972 | |
3973 | if (chars_seen == char_on_line) |
3974 | break; |
3975 | } |
3976 | |
3977 | if (_gtk_text_iter_get_text_line (iter: &pos) == _gtk_text_iter_get_text_line (iter)) |
3978 | *iter = pos; |
3979 | else |
3980 | gtk_text_iter_forward_line (iter); |
3981 | } |
3982 | |
3983 | /** |
3984 | * gtk_text_iter_set_visible_line_index: |
3985 | * @iter: a `GtkTextIter` |
3986 | * @byte_on_line: a byte index |
3987 | * |
3988 | * Like gtk_text_iter_set_line_index(), but the index is in visible |
3989 | * bytes, i.e. text with a tag making it invisible is not counted |
3990 | * in the index. |
3991 | */ |
3992 | void |
3993 | gtk_text_iter_set_visible_line_index (GtkTextIter *iter, |
3994 | int byte_on_line) |
3995 | { |
3996 | GtkTextRealIter *real; |
3997 | int offset = 0; |
3998 | GtkTextIter pos; |
3999 | GtkTextLineSegment *seg; |
4000 | |
4001 | g_return_if_fail (iter != NULL); |
4002 | |
4003 | gtk_text_iter_set_line_offset (iter, char_on_line: 0); |
4004 | |
4005 | pos = *iter; |
4006 | |
4007 | real = gtk_text_iter_make_real (iter: &pos); |
4008 | |
4009 | if (real == NULL) |
4010 | return; |
4011 | |
4012 | ensure_byte_offsets (iter: real); |
4013 | |
4014 | check_invariants (iter: &pos); |
4015 | |
4016 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
4017 | |
4018 | while (seg != NULL && byte_on_line > 0) |
4019 | { |
4020 | if (!_gtk_text_btree_char_is_invisible (iter: &pos)) |
4021 | { |
4022 | if (byte_on_line < seg->byte_count) |
4023 | { |
4024 | iter_set_from_byte_offset (iter: real, line: real->line, byte_offset: offset + byte_on_line); |
4025 | byte_on_line = 0; |
4026 | break; |
4027 | } |
4028 | else |
4029 | byte_on_line -= seg->byte_count; |
4030 | } |
4031 | |
4032 | offset += seg->byte_count; |
4033 | _gtk_text_iter_forward_indexable_segment (iter: &pos); |
4034 | seg = _gtk_text_iter_get_indexable_segment (iter: &pos); |
4035 | } |
4036 | |
4037 | if (byte_on_line == 0) |
4038 | *iter = pos; |
4039 | else |
4040 | gtk_text_iter_forward_line (iter); |
4041 | } |
4042 | |
4043 | /** |
4044 | * gtk_text_iter_set_line: |
4045 | * @iter: a `GtkTextIter` |
4046 | * @line_number: line number (counted from 0) |
4047 | * |
4048 | * Moves iterator @iter to the start of the line @line_number. |
4049 | * |
4050 | * If @line_number is negative or larger than or equal to the number of lines |
4051 | * in the buffer, moves @iter to the start of the last line in the buffer. |
4052 | */ |
4053 | void |
4054 | gtk_text_iter_set_line (GtkTextIter *iter, |
4055 | int line_number) |
4056 | { |
4057 | GtkTextLine *line; |
4058 | int real_line; |
4059 | GtkTextRealIter *real; |
4060 | |
4061 | g_return_if_fail (iter != NULL); |
4062 | |
4063 | real = gtk_text_iter_make_surreal (iter: iter); |
4064 | |
4065 | if (real == NULL) |
4066 | return; |
4067 | |
4068 | check_invariants (iter); |
4069 | |
4070 | line = _gtk_text_btree_get_line_no_last (tree: real->tree, line_number, real_line_number: &real_line); |
4071 | |
4072 | iter_set_from_char_offset (iter: real, line, char_offset: 0); |
4073 | |
4074 | /* We might as well cache this, since we know it. */ |
4075 | real->cached_line_number = real_line; |
4076 | |
4077 | check_invariants (iter); |
4078 | } |
4079 | |
4080 | /** |
4081 | * gtk_text_iter_set_offset: |
4082 | * @iter: a `GtkTextIter` |
4083 | * @char_offset: a character number |
4084 | * |
4085 | * Sets @iter to point to @char_offset. |
4086 | * |
4087 | * @char_offset counts from the start |
4088 | * of the entire text buffer, starting with 0. |
4089 | */ |
4090 | void |
4091 | gtk_text_iter_set_offset (GtkTextIter *iter, |
4092 | int char_offset) |
4093 | { |
4094 | GtkTextLine *line; |
4095 | GtkTextRealIter *real; |
4096 | int line_start; |
4097 | int real_char_index; |
4098 | |
4099 | g_return_if_fail (iter != NULL); |
4100 | |
4101 | real = gtk_text_iter_make_surreal (iter: iter); |
4102 | |
4103 | if (real == NULL) |
4104 | return; |
4105 | |
4106 | check_invariants (iter); |
4107 | |
4108 | if (real->cached_char_index >= 0 && |
4109 | real->cached_char_index == char_offset) |
4110 | return; |
4111 | |
4112 | line = _gtk_text_btree_get_line_at_char (tree: real->tree, |
4113 | char_index: char_offset, |
4114 | line_start_index: &line_start, |
4115 | real_char_index: &real_char_index); |
4116 | |
4117 | iter_set_from_char_offset (iter: real, line, char_offset: real_char_index - line_start); |
4118 | |
4119 | /* Go ahead and cache this since we have it. */ |
4120 | real->cached_char_index = real_char_index; |
4121 | |
4122 | check_invariants (iter); |
4123 | } |
4124 | |
4125 | /** |
4126 | * gtk_text_iter_forward_to_end: |
4127 | * @iter: a `GtkTextIter` |
4128 | * |
4129 | * Moves @iter forward to the “end iterator”, which points |
4130 | * one past the last valid character in the buffer. |
4131 | * |
4132 | * gtk_text_iter_get_char() called on the end iterator |
4133 | * returns 0, which is convenient for writing loops. |
4134 | */ |
4135 | void |
4136 | gtk_text_iter_forward_to_end (GtkTextIter *iter) |
4137 | { |
4138 | GtkTextBuffer *buffer; |
4139 | GtkTextRealIter *real; |
4140 | |
4141 | g_return_if_fail (iter != NULL); |
4142 | |
4143 | real = gtk_text_iter_make_surreal (iter: iter); |
4144 | |
4145 | if (real == NULL) |
4146 | return; |
4147 | |
4148 | buffer = _gtk_text_btree_get_buffer (tree: real->tree); |
4149 | |
4150 | gtk_text_buffer_get_end_iter (buffer, iter); |
4151 | } |
4152 | |
4153 | /* FIXME this and gtk_text_iter_forward_to_line_end() could be cleaned up |
4154 | * and made faster. Look at iter_ends_line() for inspiration, perhaps. |
4155 | * If all else fails we could cache the para delimiter pos in the iter. |
4156 | * I think forward_to_line_end() actually gets called fairly often. |
4157 | */ |
4158 | static int |
4159 | find_paragraph_delimiter_for_line (GtkTextIter *iter) |
4160 | { |
4161 | GtkTextIter end; |
4162 | end = *iter; |
4163 | |
4164 | if (_gtk_text_line_contains_end_iter (line: _gtk_text_iter_get_text_line (iter: &end), |
4165 | tree: _gtk_text_iter_get_btree (iter: &end))) |
4166 | { |
4167 | gtk_text_iter_forward_to_end (iter: &end); |
4168 | } |
4169 | else |
4170 | { |
4171 | /* if we aren't on the last line, go forward to start of next line, then scan |
4172 | * back for the delimiters on the previous line |
4173 | */ |
4174 | gtk_text_iter_forward_line (iter: &end); |
4175 | gtk_text_iter_backward_char (iter: &end); |
4176 | while (!gtk_text_iter_ends_line (iter: &end)) |
4177 | gtk_text_iter_backward_char (iter: &end); |
4178 | } |
4179 | |
4180 | return gtk_text_iter_get_line_offset (iter: &end); |
4181 | } |
4182 | |
4183 | /** |
4184 | * gtk_text_iter_forward_to_line_end: |
4185 | * @iter: a `GtkTextIter` |
4186 | * |
4187 | * Moves the iterator to point to the paragraph delimiter characters. |
4188 | * |
4189 | * The possible characters are either a newline, a carriage return, |
4190 | * a carriage return/newline in sequence, or the Unicode paragraph |
4191 | * separator character. |
4192 | * |
4193 | * If the iterator is already at the paragraph delimiter |
4194 | * characters, moves to the paragraph delimiter characters for the |
4195 | * next line. If @iter is on the last line in the buffer, which does |
4196 | * not end in paragraph delimiters, moves to the end iterator (end of |
4197 | * the last line), and returns %FALSE. |
4198 | * |
4199 | * Returns: %TRUE if we moved and the new location is not the end iterator |
4200 | */ |
4201 | gboolean |
4202 | gtk_text_iter_forward_to_line_end (GtkTextIter *iter) |
4203 | { |
4204 | int current_offset; |
4205 | int new_offset; |
4206 | |
4207 | |
4208 | g_return_val_if_fail (iter != NULL, FALSE); |
4209 | |
4210 | current_offset = gtk_text_iter_get_line_offset (iter); |
4211 | new_offset = find_paragraph_delimiter_for_line (iter); |
4212 | |
4213 | if (current_offset < new_offset) |
4214 | { |
4215 | /* Move to end of this line. */ |
4216 | gtk_text_iter_set_line_offset (iter, char_on_line: new_offset); |
4217 | return !gtk_text_iter_is_end (iter); |
4218 | } |
4219 | else |
4220 | { |
4221 | /* Move to end of next line. */ |
4222 | if (gtk_text_iter_forward_line (iter)) |
4223 | { |
4224 | /* We don't want to move past all |
4225 | * empty lines. |
4226 | */ |
4227 | if (!gtk_text_iter_ends_line (iter)) |
4228 | gtk_text_iter_forward_to_line_end (iter); |
4229 | return !gtk_text_iter_is_end (iter); |
4230 | } |
4231 | else |
4232 | return FALSE; |
4233 | } |
4234 | } |
4235 | |
4236 | /** |
4237 | * gtk_text_iter_forward_to_tag_toggle: |
4238 | * @iter: a `GtkTextIter` |
4239 | * @tag: (nullable): a `GtkTextTag` |
4240 | * |
4241 | * Moves forward to the next toggle (on or off) of the |
4242 | * @tag, or to the next toggle of any tag if |
4243 | * @tag is %NULL. |
4244 | * |
4245 | * If no matching tag toggles are found, |
4246 | * returns %FALSE, otherwise %TRUE. Does not return toggles |
4247 | * located at @iter, only toggles after @iter. Sets @iter to |
4248 | * the location of the toggle, or to the end of the buffer |
4249 | * if no toggle is found. |
4250 | * |
4251 | * Returns: whether we found a tag toggle after @iter |
4252 | */ |
4253 | gboolean |
4254 | gtk_text_iter_forward_to_tag_toggle (GtkTextIter *iter, |
4255 | GtkTextTag *tag) |
4256 | { |
4257 | GtkTextLine *next_line; |
4258 | GtkTextLine *current_line; |
4259 | GtkTextRealIter *real; |
4260 | |
4261 | g_return_val_if_fail (iter != NULL, FALSE); |
4262 | |
4263 | real = gtk_text_iter_make_real (iter: iter); |
4264 | |
4265 | if (real == NULL) |
4266 | return FALSE; |
4267 | |
4268 | check_invariants (iter); |
4269 | |
4270 | if (gtk_text_iter_is_end (iter)) |
4271 | return FALSE; |
4272 | |
4273 | current_line = real->line; |
4274 | next_line = _gtk_text_line_next_could_contain_tag (line: current_line, |
4275 | tree: real->tree, tag); |
4276 | |
4277 | while (_gtk_text_iter_forward_indexable_segment (iter)) |
4278 | { |
4279 | /* If we went forward to a line that couldn't contain a toggle |
4280 | for the tag, then skip forward to a line that could contain |
4281 | it. This potentially skips huge hunks of the tree, so we |
4282 | aren't a purely linear search. */ |
4283 | if (real->line != current_line) |
4284 | { |
4285 | if (next_line == NULL) |
4286 | { |
4287 | /* End of search. Set to end of buffer. */ |
4288 | _gtk_text_btree_get_end_iter (tree: real->tree, iter); |
4289 | return FALSE; |
4290 | } |
4291 | |
4292 | if (real->line != next_line) |
4293 | iter_set_from_byte_offset (iter: real, line: next_line, byte_offset: 0); |
4294 | |
4295 | current_line = real->line; |
4296 | next_line = _gtk_text_line_next_could_contain_tag (line: current_line, |
4297 | tree: real->tree, |
4298 | tag); |
4299 | } |
4300 | |
4301 | if (gtk_text_iter_toggles_tag (iter, tag)) |
4302 | { |
4303 | /* If there's a toggle here, it isn't indexable so |
4304 | any_segment can't be the indexable segment. */ |
4305 | g_assert (real->any_segment != real->segment); |
4306 | return TRUE; |
4307 | } |
4308 | } |
4309 | |
4310 | /* Check end iterator for tags */ |
4311 | if (gtk_text_iter_toggles_tag (iter, tag)) |
4312 | { |
4313 | /* If there's a toggle here, it isn't indexable so |
4314 | any_segment can't be the indexable segment. */ |
4315 | g_assert (real->any_segment != real->segment); |
4316 | return TRUE; |
4317 | } |
4318 | |
4319 | /* Reached end of buffer */ |
4320 | return FALSE; |
4321 | } |
4322 | |
4323 | /** |
4324 | * gtk_text_iter_backward_to_tag_toggle: |
4325 | * @iter: a `GtkTextIter` |
4326 | * @tag: (nullable): a `GtkTextTag` |
4327 | * |
4328 | * Moves backward to the next toggle (on or off) of the |
4329 | * @tag, or to the next toggle of any tag if |
4330 | * @tag is %NULL. |
4331 | * |
4332 | * If no matching tag toggles are found, |
4333 | * returns %FALSE, otherwise %TRUE. Does not return toggles |
4334 | * located at @iter, only toggles before @iter. Sets @iter |
4335 | * to the location of the toggle, or the start of the buffer |
4336 | * if no toggle is found. |
4337 | * |
4338 | * Returns: whether we found a tag toggle before @iter |
4339 | */ |
4340 | gboolean |
4341 | gtk_text_iter_backward_to_tag_toggle (GtkTextIter *iter, |
4342 | GtkTextTag *tag) |
4343 | { |
4344 | GtkTextLine *prev_line; |
4345 | GtkTextLine *current_line; |
4346 | GtkTextRealIter *real; |
4347 | |
4348 | g_return_val_if_fail (iter != NULL, FALSE); |
4349 | |
4350 | real = gtk_text_iter_make_real (iter: iter); |
4351 | |
4352 | if (real == NULL) |
4353 | return FALSE; |
4354 | |
4355 | check_invariants (iter); |
4356 | |
4357 | current_line = real->line; |
4358 | prev_line = _gtk_text_line_previous_could_contain_tag (line: current_line, |
4359 | tree: real->tree, tag); |
4360 | |
4361 | |
4362 | /* If we're at segment start, go to the previous segment; |
4363 | * if mid-segment, snap to start of current segment. |
4364 | */ |
4365 | if (is_segment_start (real)) |
4366 | { |
4367 | if (!_gtk_text_iter_backward_indexable_segment (iter)) |
4368 | return FALSE; |
4369 | } |
4370 | else |
4371 | { |
4372 | ensure_char_offsets (iter: real); |
4373 | |
4374 | if (!gtk_text_iter_backward_chars (iter, count: real->segment_char_offset)) |
4375 | return FALSE; |
4376 | } |
4377 | |
4378 | do |
4379 | { |
4380 | /* If we went backward to a line that couldn't contain a toggle |
4381 | * for the tag, then skip backward further to a line that |
4382 | * could contain it. This potentially skips huge hunks of the |
4383 | * tree, so we aren't a purely linear search. |
4384 | */ |
4385 | if (real->line != current_line) |
4386 | { |
4387 | if (prev_line == NULL) |
4388 | { |
4389 | /* End of search. Set to start of buffer. */ |
4390 | _gtk_text_btree_get_iter_at_char (tree: real->tree, iter, char_index: 0); |
4391 | return FALSE; |
4392 | } |
4393 | |
4394 | if (real->line != prev_line) |
4395 | { |
4396 | /* Set to last segment in prev_line (could do this |
4397 | * more quickly) |
4398 | */ |
4399 | iter_set_from_byte_offset (iter: real, line: prev_line, byte_offset: 0); |
4400 | |
4401 | while (!at_last_indexable_segment (real)) |
4402 | _gtk_text_iter_forward_indexable_segment (iter); |
4403 | } |
4404 | |
4405 | current_line = real->line; |
4406 | prev_line = _gtk_text_line_previous_could_contain_tag (line: current_line, |
4407 | tree: real->tree, |
4408 | tag); |
4409 | } |
4410 | |
4411 | if (gtk_text_iter_toggles_tag (iter, tag)) |
4412 | { |
4413 | /* If there's a toggle here, it isn't indexable so |
4414 | * any_segment can't be the indexable segment. |
4415 | */ |
4416 | g_assert (real->any_segment != real->segment); |
4417 | return TRUE; |
4418 | } |
4419 | } |
4420 | while (_gtk_text_iter_backward_indexable_segment (iter)); |
4421 | |
4422 | /* Reached front of buffer */ |
4423 | return FALSE; |
4424 | } |
4425 | |
4426 | static gboolean |
4427 | matches_pred (GtkTextIter *iter, |
4428 | GtkTextCharPredicate pred, |
4429 | gpointer user_data) |
4430 | { |
4431 | int ch; |
4432 | |
4433 | ch = gtk_text_iter_get_char (iter); |
4434 | |
4435 | return (*pred) (ch, user_data); |
4436 | } |
4437 | |
4438 | /** |
4439 | * gtk_text_iter_forward_find_char: |
4440 | * @iter: a `GtkTextIter` |
4441 | * @pred: (scope call): a function to be called on each character |
4442 | * @user_data: (closure): user data for @pred |
4443 | * @limit: (nullable): search limit |
4444 | * |
4445 | * Advances @iter, calling @pred on each character. |
4446 | * |
4447 | * If @pred returns %TRUE, returns %TRUE and stops scanning. |
4448 | * If @pred never returns %TRUE, @iter is set to @limit if |
4449 | * @limit is non-%NULL, otherwise to the end iterator. |
4450 | * |
4451 | * Returns: whether a match was found |
4452 | */ |
4453 | gboolean |
4454 | gtk_text_iter_forward_find_char (GtkTextIter *iter, |
4455 | GtkTextCharPredicate pred, |
4456 | gpointer user_data, |
4457 | const GtkTextIter *limit) |
4458 | { |
4459 | g_return_val_if_fail (iter != NULL, FALSE); |
4460 | g_return_val_if_fail (pred != NULL, FALSE); |
4461 | |
4462 | if (limit && |
4463 | gtk_text_iter_compare (lhs: iter, rhs: limit) >= 0) |
4464 | return FALSE; |
4465 | |
4466 | while ((limit == NULL || |
4467 | !gtk_text_iter_equal (lhs: limit, rhs: iter)) && |
4468 | gtk_text_iter_forward_char (iter)) |
4469 | { |
4470 | if (matches_pred (iter, pred, user_data)) |
4471 | return TRUE; |
4472 | } |
4473 | |
4474 | return FALSE; |
4475 | } |
4476 | |
4477 | /** |
4478 | * gtk_text_iter_backward_find_char: |
4479 | * @iter: a `GtkTextIter` |
4480 | * @pred: (scope call): function to be called on each character |
4481 | * @user_data: (closure): user data for @pred |
4482 | * @limit: (nullable): search limit |
4483 | * |
4484 | * Same as gtk_text_iter_forward_find_char(), |
4485 | * but goes backward from @iter. |
4486 | * |
4487 | * Returns: whether a match was found |
4488 | */ |
4489 | gboolean |
4490 | gtk_text_iter_backward_find_char (GtkTextIter *iter, |
4491 | GtkTextCharPredicate pred, |
4492 | gpointer user_data, |
4493 | const GtkTextIter *limit) |
4494 | { |
4495 | g_return_val_if_fail (iter != NULL, FALSE); |
4496 | g_return_val_if_fail (pred != NULL, FALSE); |
4497 | |
4498 | if (limit && |
4499 | gtk_text_iter_compare (lhs: iter, rhs: limit) <= 0) |
4500 | return FALSE; |
4501 | |
4502 | while ((limit == NULL || |
4503 | !gtk_text_iter_equal (lhs: limit, rhs: iter)) && |
4504 | gtk_text_iter_backward_char (iter)) |
4505 | { |
4506 | if (matches_pred (iter, pred, user_data)) |
4507 | return TRUE; |
4508 | } |
4509 | |
4510 | return FALSE; |
4511 | } |
4512 | |
4513 | static void |
4514 | forward_chars_with_skipping (GtkTextIter *iter, |
4515 | int count, |
4516 | gboolean skip_invisible, |
4517 | gboolean skip_nontext, |
4518 | gboolean skip_decomp) |
4519 | { |
4520 | int i; |
4521 | |
4522 | g_return_if_fail (count >= 0); |
4523 | |
4524 | i = count; |
4525 | |
4526 | while (i > 0) |
4527 | { |
4528 | gboolean ignored = FALSE; |
4529 | |
4530 | /* minimal workaround to avoid the infinite loop of bug #168247. */ |
4531 | if (gtk_text_iter_is_end (iter)) |
4532 | return; |
4533 | |
4534 | if (skip_nontext && |
4535 | gtk_text_iter_get_char (iter) == GTK_TEXT_UNKNOWN_CHAR) |
4536 | ignored = TRUE; |
4537 | |
4538 | if (!ignored && |
4539 | skip_invisible && |
4540 | _gtk_text_btree_char_is_invisible (iter)) |
4541 | ignored = TRUE; |
4542 | |
4543 | if (!ignored && skip_decomp) |
4544 | { |
4545 | /* being UTF8 correct sucks: this accounts for extra |
4546 | offsets coming from canonical decompositions of |
4547 | UTF8 characters (e.g. accented characters) which |
4548 | g_utf8_normalize() performs */ |
4549 | char *normal; |
4550 | char *casefold; |
4551 | char buffer[6]; |
4552 | int buffer_len; |
4553 | |
4554 | buffer_len = g_unichar_to_utf8 (c: gtk_text_iter_get_char (iter), outbuf: buffer); |
4555 | casefold = g_utf8_casefold (str: buffer, len: buffer_len); |
4556 | normal = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4557 | i -= (g_utf8_strlen (p: normal, max: -1) - 1); |
4558 | g_free (mem: normal); |
4559 | g_free (mem: casefold); |
4560 | } |
4561 | |
4562 | gtk_text_iter_forward_char (iter); |
4563 | |
4564 | if (!ignored) |
4565 | --i; |
4566 | } |
4567 | } |
4568 | |
4569 | static const char * |
4570 | pointer_from_offset_skipping_decomp (const char *str, |
4571 | int offset) |
4572 | { |
4573 | char *casefold, *normal; |
4574 | const char *p, *q; |
4575 | |
4576 | p = str; |
4577 | |
4578 | while (offset > 0) |
4579 | { |
4580 | q = g_utf8_next_char (p); |
4581 | casefold = g_utf8_casefold (str: p, len: q - p); |
4582 | normal = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4583 | offset -= g_utf8_strlen (p: normal, max: -1); |
4584 | g_free (mem: casefold); |
4585 | g_free (mem: normal); |
4586 | p = q; |
4587 | } |
4588 | |
4589 | return p; |
4590 | } |
4591 | |
4592 | static gboolean |
4593 | exact_prefix_cmp (const char *string, |
4594 | const char *prefix, |
4595 | guint prefix_len) |
4596 | { |
4597 | GUnicodeType type; |
4598 | |
4599 | if (strncmp (s1: string, s2: prefix, n: prefix_len) != 0) |
4600 | return FALSE; |
4601 | if (string[prefix_len] == '\0') |
4602 | return TRUE; |
4603 | |
4604 | type = g_unichar_type (c: g_utf8_get_char (p: string + prefix_len)); |
4605 | |
4606 | /* If string contains prefix, check that prefix is not followed |
4607 | * by a unicode mark symbol, e.g. that trailing 'a' in prefix |
4608 | * is not part of two-char a-with-hat symbol in string. */ |
4609 | return type != G_UNICODE_SPACING_MARK && |
4610 | type != G_UNICODE_ENCLOSING_MARK && |
4611 | type != G_UNICODE_NON_SPACING_MARK; |
4612 | } |
4613 | |
4614 | static const char * |
4615 | utf8_strcasestr (const char *haystack, |
4616 | const char *needle) |
4617 | { |
4618 | gsize needle_len; |
4619 | gsize haystack_len; |
4620 | const char *ret = NULL; |
4621 | char *p; |
4622 | char *casefold; |
4623 | char *caseless_haystack; |
4624 | int i; |
4625 | |
4626 | g_return_val_if_fail (haystack != NULL, NULL); |
4627 | g_return_val_if_fail (needle != NULL, NULL); |
4628 | |
4629 | casefold = g_utf8_casefold (str: haystack, len: -1); |
4630 | caseless_haystack = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4631 | g_free (mem: casefold); |
4632 | |
4633 | needle_len = g_utf8_strlen (p: needle, max: -1); |
4634 | haystack_len = g_utf8_strlen (p: caseless_haystack, max: -1); |
4635 | |
4636 | if (needle_len == 0) |
4637 | { |
4638 | ret = (char *)haystack; |
4639 | goto finally; |
4640 | } |
4641 | |
4642 | if (haystack_len < needle_len) |
4643 | { |
4644 | ret = NULL; |
4645 | goto finally; |
4646 | } |
4647 | |
4648 | p = (char *)caseless_haystack; |
4649 | needle_len = strlen (s: needle); |
4650 | i = 0; |
4651 | |
4652 | while (*p) |
4653 | { |
4654 | if (exact_prefix_cmp (string: p, prefix: needle, prefix_len: needle_len)) |
4655 | { |
4656 | ret = pointer_from_offset_skipping_decomp (str: haystack, offset: i); |
4657 | goto finally; |
4658 | } |
4659 | |
4660 | p = g_utf8_next_char (p); |
4661 | i++; |
4662 | } |
4663 | |
4664 | finally: |
4665 | g_free (mem: caseless_haystack); |
4666 | |
4667 | return ret; |
4668 | } |
4669 | |
4670 | static const char * |
4671 | utf8_strrcasestr (const char *haystack, |
4672 | const char *needle) |
4673 | { |
4674 | gsize needle_len; |
4675 | gsize haystack_len; |
4676 | const char *ret = NULL; |
4677 | char *p; |
4678 | char *casefold; |
4679 | char *caseless_haystack; |
4680 | int i; |
4681 | |
4682 | g_return_val_if_fail (haystack != NULL, NULL); |
4683 | g_return_val_if_fail (needle != NULL, NULL); |
4684 | |
4685 | casefold = g_utf8_casefold (str: haystack, len: -1); |
4686 | caseless_haystack = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4687 | g_free (mem: casefold); |
4688 | |
4689 | needle_len = g_utf8_strlen (p: needle, max: -1); |
4690 | haystack_len = g_utf8_strlen (p: caseless_haystack, max: -1); |
4691 | |
4692 | if (needle_len == 0) |
4693 | { |
4694 | ret = (char *)haystack; |
4695 | goto finally; |
4696 | } |
4697 | |
4698 | if (haystack_len < needle_len) |
4699 | { |
4700 | ret = NULL; |
4701 | goto finally; |
4702 | } |
4703 | |
4704 | i = haystack_len - needle_len; |
4705 | p = g_utf8_offset_to_pointer (str: caseless_haystack, offset: i); |
4706 | needle_len = strlen (s: needle); |
4707 | |
4708 | while (TRUE) |
4709 | { |
4710 | if (exact_prefix_cmp (string: p, prefix: needle, prefix_len: needle_len)) |
4711 | { |
4712 | ret = pointer_from_offset_skipping_decomp (str: haystack, offset: i); |
4713 | break; |
4714 | } |
4715 | |
4716 | if (p == caseless_haystack) |
4717 | break; |
4718 | |
4719 | p = g_utf8_prev_char (p); |
4720 | i--; |
4721 | } |
4722 | |
4723 | finally: |
4724 | g_free (mem: caseless_haystack); |
4725 | |
4726 | return ret; |
4727 | } |
4728 | |
4729 | /* normalizes caseless strings and returns true if @s2 matches |
4730 | the start of @s1 */ |
4731 | static gboolean |
4732 | utf8_caselessnmatch (const char *s1, |
4733 | const char *s2, |
4734 | gssize n1, |
4735 | gssize n2) |
4736 | { |
4737 | char *casefold; |
4738 | char *normalized_s1; |
4739 | char *normalized_s2; |
4740 | int len_s1; |
4741 | int len_s2; |
4742 | gboolean ret = FALSE; |
4743 | |
4744 | g_return_val_if_fail (s1 != NULL, FALSE); |
4745 | g_return_val_if_fail (s2 != NULL, FALSE); |
4746 | g_return_val_if_fail (n1 > 0, FALSE); |
4747 | g_return_val_if_fail (n2 > 0, FALSE); |
4748 | |
4749 | casefold = g_utf8_casefold (str: s1, len: n1); |
4750 | normalized_s1 = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4751 | g_free (mem: casefold); |
4752 | |
4753 | casefold = g_utf8_casefold (str: s2, len: n2); |
4754 | normalized_s2 = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4755 | g_free (mem: casefold); |
4756 | |
4757 | len_s1 = strlen (s: normalized_s1); |
4758 | len_s2 = strlen (s: normalized_s2); |
4759 | |
4760 | if (len_s1 >= len_s2) |
4761 | ret = (strncmp (s1: normalized_s1, s2: normalized_s2, n: len_s2) == 0); |
4762 | |
4763 | g_free (mem: normalized_s1); |
4764 | g_free (mem: normalized_s2); |
4765 | |
4766 | return ret; |
4767 | } |
4768 | |
4769 | static gboolean |
4770 | lines_match (const GtkTextIter *start, |
4771 | const char **lines, |
4772 | gboolean visible_only, |
4773 | gboolean slice, |
4774 | gboolean case_insensitive, |
4775 | GtkTextIter *match_start, |
4776 | GtkTextIter *match_end) |
4777 | { |
4778 | GtkTextIter next; |
4779 | char *line_text; |
4780 | const char *found; |
4781 | int offset; |
4782 | |
4783 | if (*lines == NULL || **lines == '\0') |
4784 | { |
4785 | if (match_start) |
4786 | *match_start = *start; |
4787 | |
4788 | if (match_end) |
4789 | *match_end = *start; |
4790 | return TRUE; |
4791 | } |
4792 | |
4793 | next = *start; |
4794 | gtk_text_iter_forward_line (iter: &next); |
4795 | |
4796 | /* No more text in buffer, but *lines is nonempty */ |
4797 | if (gtk_text_iter_equal (lhs: start, rhs: &next)) |
4798 | { |
4799 | return FALSE; |
4800 | } |
4801 | |
4802 | if (slice) |
4803 | { |
4804 | if (visible_only) |
4805 | line_text = gtk_text_iter_get_visible_slice (start, end: &next); |
4806 | else |
4807 | line_text = gtk_text_iter_get_slice (start, end: &next); |
4808 | } |
4809 | else |
4810 | { |
4811 | if (visible_only) |
4812 | line_text = gtk_text_iter_get_visible_text (start, end: &next); |
4813 | else |
4814 | line_text = gtk_text_iter_get_text (start, end: &next); |
4815 | } |
4816 | |
4817 | if (match_start) /* if this is the first line we're matching */ |
4818 | { |
4819 | if (!case_insensitive) |
4820 | found = strstr (haystack: line_text, needle: *lines); |
4821 | else |
4822 | found = utf8_strcasestr (haystack: line_text, needle: *lines); |
4823 | } |
4824 | else |
4825 | { |
4826 | /* If it's not the first line, we have to match from the |
4827 | * start of the line. |
4828 | */ |
4829 | if ((!case_insensitive && |
4830 | (strncmp (s1: line_text, s2: *lines, n: strlen (s: *lines)) == 0)) || |
4831 | (case_insensitive && |
4832 | utf8_caselessnmatch (s1: line_text, s2: *lines, n1: strlen (s: line_text), |
4833 | n2: strlen (s: *lines)))) |
4834 | { |
4835 | found = line_text; |
4836 | } |
4837 | else |
4838 | found = NULL; |
4839 | } |
4840 | |
4841 | if (found == NULL) |
4842 | { |
4843 | g_free (mem: line_text); |
4844 | return FALSE; |
4845 | } |
4846 | |
4847 | /* Get offset to start of search string */ |
4848 | offset = g_utf8_strlen (p: line_text, max: found - line_text); |
4849 | |
4850 | next = *start; |
4851 | |
4852 | /* If match start needs to be returned, set it to the |
4853 | * start of the search string. |
4854 | */ |
4855 | forward_chars_with_skipping (iter: &next, count: offset, |
4856 | skip_invisible: visible_only, skip_nontext: !slice, FALSE); |
4857 | if (match_start) |
4858 | *match_start = next; |
4859 | |
4860 | /* Go to end of search string */ |
4861 | forward_chars_with_skipping (iter: &next, count: g_utf8_strlen (p: *lines, max: -1), |
4862 | skip_invisible: visible_only, skip_nontext: !slice, skip_decomp: case_insensitive); |
4863 | |
4864 | g_free (mem: line_text); |
4865 | |
4866 | ++lines; |
4867 | |
4868 | if (match_end) |
4869 | *match_end = next; |
4870 | |
4871 | /* pass NULL for match_start, since we don't need to find the |
4872 | * start again. |
4873 | */ |
4874 | return lines_match (start: &next, lines, visible_only, slice, case_insensitive, NULL, match_end); |
4875 | } |
4876 | |
4877 | /* strsplit() that retains the delimiter as part of the string. */ |
4878 | static char ** |
4879 | strbreakup (const char *string, |
4880 | const char *delimiter, |
4881 | int max_tokens, |
4882 | int *num_strings, |
4883 | gboolean case_insensitive) |
4884 | { |
4885 | GSList *string_list = NULL, *slist; |
4886 | char **str_array, *s; |
4887 | char *casefold, *new_string; |
4888 | guint i, n = 1; |
4889 | |
4890 | g_return_val_if_fail (string != NULL, NULL); |
4891 | g_return_val_if_fail (delimiter != NULL, NULL); |
4892 | |
4893 | if (max_tokens < 1) |
4894 | max_tokens = G_MAXINT; |
4895 | |
4896 | s = strstr (haystack: string, needle: delimiter); |
4897 | if (s) |
4898 | { |
4899 | guint delimiter_len = strlen (s: delimiter); |
4900 | |
4901 | do |
4902 | { |
4903 | guint len; |
4904 | |
4905 | len = s - string + delimiter_len; |
4906 | new_string = g_new (char, len + 1); |
4907 | strncpy (dest: new_string, src: string, n: len); |
4908 | new_string[len] = 0; |
4909 | |
4910 | if (case_insensitive) |
4911 | { |
4912 | casefold = g_utf8_casefold (str: new_string, len: -1); |
4913 | g_free (mem: new_string); |
4914 | new_string = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4915 | g_free (mem: casefold); |
4916 | } |
4917 | |
4918 | string_list = g_slist_prepend (list: string_list, data: new_string); |
4919 | n++; |
4920 | string = s + delimiter_len; |
4921 | s = strstr (haystack: string, needle: delimiter); |
4922 | } |
4923 | while (--max_tokens && s); |
4924 | } |
4925 | if (*string) |
4926 | { |
4927 | n++; |
4928 | |
4929 | if (case_insensitive) |
4930 | { |
4931 | casefold = g_utf8_casefold (str: string, len: -1); |
4932 | new_string = g_utf8_normalize (str: casefold, len: -1, mode: G_NORMALIZE_NFD); |
4933 | g_free (mem: casefold); |
4934 | } |
4935 | else |
4936 | new_string = g_strdup (str: string); |
4937 | |
4938 | string_list = g_slist_prepend (list: string_list, data: new_string); |
4939 | } |
4940 | |
4941 | str_array = g_new (char *, n); |
4942 | |
4943 | i = n - 1; |
4944 | |
4945 | str_array[i--] = NULL; |
4946 | for (slist = string_list; slist; slist = slist->next) |
4947 | str_array[i--] = slist->data; |
4948 | |
4949 | g_slist_free (list: string_list); |
4950 | |
4951 | if (num_strings != NULL) |
4952 | *num_strings = n - 1; |
4953 | |
4954 | return str_array; |
4955 | } |
4956 | |
4957 | /** |
4958 | * gtk_text_iter_forward_search: |
4959 | * @iter: start of search |
4960 | * @str: a search string |
4961 | * @flags: flags affecting how the search is done |
4962 | * @match_start: (out caller-allocates) (optional): return location for start of match |
4963 | * @match_end: (out caller-allocates) (optional): return location for end of match |
4964 | * @limit: (nullable): location of last possible @match_end, or %NULL for the end of the buffer |
4965 | * |
4966 | * Searches forward for @str. |
4967 | * |
4968 | * Any match is returned by setting @match_start to the first character |
4969 | * of the match and @match_end to the first character after the match. |
4970 | * The search will not continue past @limit. Note that a search is a |
4971 | * linear or O(n) operation, so you may wish to use @limit to avoid |
4972 | * locking up your UI on large buffers. |
4973 | * |
4974 | * @match_start will never be set to a `GtkTextIter` located before @iter, |
4975 | * even if there is a possible @match_end after or at @iter. |
4976 | * |
4977 | * Returns: whether a match was found |
4978 | */ |
4979 | gboolean |
4980 | gtk_text_iter_forward_search (const GtkTextIter *iter, |
4981 | const char *str, |
4982 | GtkTextSearchFlags flags, |
4983 | GtkTextIter *match_start, |
4984 | GtkTextIter *match_end, |
4985 | const GtkTextIter *limit) |
4986 | { |
4987 | char **lines = NULL; |
4988 | GtkTextIter match; |
4989 | gboolean retval = FALSE; |
4990 | GtkTextIter search; |
4991 | gboolean visible_only; |
4992 | gboolean slice; |
4993 | gboolean case_insensitive; |
4994 | |
4995 | g_return_val_if_fail (iter != NULL, FALSE); |
4996 | g_return_val_if_fail (str != NULL, FALSE); |
4997 | |
4998 | if (limit && |
4999 | gtk_text_iter_compare (lhs: iter, rhs: limit) >= 0) |
5000 | return FALSE; |
5001 | |
5002 | if (*str == '\0') |
5003 | { |
5004 | /* If we can move one char, return the empty string there */ |
5005 | match = *iter; |
5006 | |
5007 | if (gtk_text_iter_forward_char (iter: &match)) |
5008 | { |
5009 | if (limit && |
5010 | gtk_text_iter_equal (lhs: &match, rhs: limit)) |
5011 | return FALSE; |
5012 | |
5013 | if (match_start) |
5014 | *match_start = match; |
5015 | if (match_end) |
5016 | *match_end = match; |
5017 | return TRUE; |
5018 | } |
5019 | else |
5020 | return FALSE; |
5021 | } |
5022 | |
5023 | visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; |
5024 | slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; |
5025 | case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; |
5026 | |
5027 | /* locate all lines */ |
5028 | |
5029 | lines = strbreakup (string: str, delimiter: "\n" , max_tokens: -1, NULL, case_insensitive); |
5030 | |
5031 | search = *iter; |
5032 | |
5033 | do |
5034 | { |
5035 | /* This loop has an inefficient worst-case, where |
5036 | * gtk_text_iter_get_text() is called repeatedly on |
5037 | * a single line. |
5038 | */ |
5039 | GtkTextIter end; |
5040 | |
5041 | if (limit && |
5042 | gtk_text_iter_compare (lhs: &search, rhs: limit) >= 0) |
5043 | break; |
5044 | |
5045 | if (lines_match (start: &search, lines: (const char **)lines, |
5046 | visible_only, slice, case_insensitive, match_start: &match, match_end: &end)) |
5047 | { |
5048 | if (limit == NULL || |
5049 | (limit && |
5050 | gtk_text_iter_compare (lhs: &end, rhs: limit) <= 0)) |
5051 | { |
5052 | retval = TRUE; |
5053 | |
5054 | if (match_start) |
5055 | *match_start = match; |
5056 | |
5057 | if (match_end) |
5058 | *match_end = end; |
5059 | } |
5060 | |
5061 | break; |
5062 | } |
5063 | } |
5064 | while (gtk_text_iter_forward_line (iter: &search)); |
5065 | |
5066 | g_strfreev (str_array: (char **)lines); |
5067 | |
5068 | return retval; |
5069 | } |
5070 | |
5071 | static gboolean |
5072 | vectors_equal_ignoring_trailing (char **vec1, |
5073 | char **vec2, |
5074 | gboolean case_insensitive) |
5075 | { |
5076 | /* Ignores trailing chars in vec2's last line */ |
5077 | |
5078 | char **i1, **i2; |
5079 | |
5080 | i1 = vec1; |
5081 | i2 = vec2; |
5082 | |
5083 | while (*i1 && *i2) |
5084 | { |
5085 | int len1; |
5086 | int len2; |
5087 | |
5088 | if (!case_insensitive) |
5089 | { |
5090 | if (strcmp (s1: *i1, s2: *i2) != 0) |
5091 | { |
5092 | if (*(i2 + 1) == NULL) /* if this is the last line */ |
5093 | { |
5094 | len1 = strlen (s: *i1); |
5095 | len2 = strlen (s: *i2); |
5096 | |
5097 | if (len2 >= len1 && |
5098 | strncmp (s1: *i1, s2: *i2, n: len1) == 0) |
5099 | { |
5100 | /* We matched ignoring the trailing stuff in vec2 */ |
5101 | return TRUE; |
5102 | } |
5103 | else |
5104 | { |
5105 | return FALSE; |
5106 | } |
5107 | } |
5108 | else |
5109 | { |
5110 | return FALSE; |
5111 | } |
5112 | } |
5113 | } |
5114 | else |
5115 | { |
5116 | len1 = strlen (s: *i1); |
5117 | len2 = strlen (s: *i2); |
5118 | |
5119 | if (!utf8_caselessnmatch (s1: *i1, s2: *i2, n1: len1, n2: len2)) |
5120 | { |
5121 | if (*(i2 + 1) == NULL) /* if this is the last line */ |
5122 | { |
5123 | if (utf8_caselessnmatch (s1: *i2, s2: *i1, n1: len2, n2: len1)) |
5124 | { |
5125 | /* We matched ignoring the trailing stuff in vec2 */ |
5126 | return TRUE; |
5127 | } |
5128 | else |
5129 | { |
5130 | return FALSE; |
5131 | } |
5132 | } |
5133 | else |
5134 | { |
5135 | return FALSE; |
5136 | } |
5137 | } |
5138 | } |
5139 | |
5140 | ++i1; |
5141 | ++i2; |
5142 | } |
5143 | |
5144 | if (*i1 || *i2) |
5145 | return FALSE; |
5146 | else |
5147 | return TRUE; |
5148 | } |
5149 | |
5150 | typedef struct _LinesWindow LinesWindow; |
5151 | |
5152 | struct _LinesWindow |
5153 | { |
5154 | int n_lines; |
5155 | char **lines; |
5156 | |
5157 | GtkTextIter first_line_start; |
5158 | GtkTextIter first_line_end; |
5159 | |
5160 | guint slice : 1; |
5161 | guint visible_only : 1; |
5162 | }; |
5163 | |
5164 | static void |
5165 | lines_window_init (LinesWindow *win, |
5166 | const GtkTextIter *start) |
5167 | { |
5168 | int i; |
5169 | GtkTextIter line_start; |
5170 | GtkTextIter line_end; |
5171 | |
5172 | /* If we start on line 1, there are 2 lines to search (0 and 1), so |
5173 | * n_lines can be 2. |
5174 | */ |
5175 | if (gtk_text_iter_is_start (iter: start) || |
5176 | gtk_text_iter_get_line (iter: start) + 1 < win->n_lines) |
5177 | { |
5178 | /* Already at the end, or not enough lines to match */ |
5179 | win->lines = g_new0 (char *, 1); |
5180 | *win->lines = NULL; |
5181 | return; |
5182 | } |
5183 | |
5184 | line_start = *start; |
5185 | line_end = *start; |
5186 | |
5187 | /* Move to start iter to start of line */ |
5188 | gtk_text_iter_set_line_offset (iter: &line_start, char_on_line: 0); |
5189 | |
5190 | if (gtk_text_iter_equal (lhs: &line_start, rhs: &line_end)) |
5191 | { |
5192 | /* we were already at the start; so go back one line */ |
5193 | gtk_text_iter_backward_line (iter: &line_start); |
5194 | } |
5195 | |
5196 | win->first_line_start = line_start; |
5197 | win->first_line_end = line_end; |
5198 | |
5199 | win->lines = g_new0 (char *, win->n_lines + 1); |
5200 | |
5201 | i = win->n_lines - 1; |
5202 | while (i >= 0) |
5203 | { |
5204 | char *line_text; |
5205 | |
5206 | if (win->slice) |
5207 | { |
5208 | if (win->visible_only) |
5209 | line_text = gtk_text_iter_get_visible_slice (start: &line_start, end: &line_end); |
5210 | else |
5211 | line_text = gtk_text_iter_get_slice (start: &line_start, end: &line_end); |
5212 | } |
5213 | else |
5214 | { |
5215 | if (win->visible_only) |
5216 | line_text = gtk_text_iter_get_visible_text (start: &line_start, end: &line_end); |
5217 | else |
5218 | line_text = gtk_text_iter_get_text (start: &line_start, end: &line_end); |
5219 | } |
5220 | |
5221 | win->lines[i] = line_text; |
5222 | win->first_line_start = line_start; |
5223 | win->first_line_end = line_end; |
5224 | |
5225 | line_end = line_start; |
5226 | gtk_text_iter_backward_line (iter: &line_start); |
5227 | |
5228 | --i; |
5229 | } |
5230 | } |
5231 | |
5232 | static gboolean |
5233 | lines_window_back (LinesWindow *win) |
5234 | { |
5235 | GtkTextIter new_start; |
5236 | char *line_text; |
5237 | |
5238 | new_start = win->first_line_start; |
5239 | |
5240 | if (!gtk_text_iter_backward_line (iter: &new_start)) |
5241 | return FALSE; |
5242 | else |
5243 | { |
5244 | win->first_line_start = new_start; |
5245 | win->first_line_end = new_start; |
5246 | |
5247 | gtk_text_iter_forward_line (iter: &win->first_line_end); |
5248 | } |
5249 | |
5250 | if (win->slice) |
5251 | { |
5252 | if (win->visible_only) |
5253 | line_text = gtk_text_iter_get_visible_slice (start: &win->first_line_start, |
5254 | end: &win->first_line_end); |
5255 | else |
5256 | line_text = gtk_text_iter_get_slice (start: &win->first_line_start, |
5257 | end: &win->first_line_end); |
5258 | } |
5259 | else |
5260 | { |
5261 | if (win->visible_only) |
5262 | line_text = gtk_text_iter_get_visible_text (start: &win->first_line_start, |
5263 | end: &win->first_line_end); |
5264 | else |
5265 | line_text = gtk_text_iter_get_text (start: &win->first_line_start, |
5266 | end: &win->first_line_end); |
5267 | } |
5268 | |
5269 | /* Move lines to make room for first line. */ |
5270 | memmove (dest: win->lines + 1, src: win->lines, n: win->n_lines * sizeof (char *)); |
5271 | |
5272 | *win->lines = line_text; |
5273 | |
5274 | /* Free old last line and NULL-terminate */ |
5275 | g_free (mem: win->lines[win->n_lines]); |
5276 | win->lines[win->n_lines] = NULL; |
5277 | |
5278 | return TRUE; |
5279 | } |
5280 | |
5281 | static void |
5282 | lines_window_free (LinesWindow *win) |
5283 | { |
5284 | g_strfreev (str_array: win->lines); |
5285 | } |
5286 | |
5287 | /** |
5288 | * gtk_text_iter_backward_search: |
5289 | * @iter: a `GtkTextIter` where the search begins |
5290 | * @str: search string |
5291 | * @flags: bitmask of flags affecting the search |
5292 | * @match_start: (out caller-allocates) (optional): return location for start of match |
5293 | * @match_end: (out caller-allocates) (optional): return location for end of match |
5294 | * @limit: (nullable): location of last possible @match_start, or %NULL for start of buffer |
5295 | * |
5296 | * Same as gtk_text_iter_forward_search(), but moves backward. |
5297 | * |
5298 | * @match_end will never be set to a `GtkTextIter` located after @iter, |
5299 | * even if there is a possible @match_start before or at @iter. |
5300 | * |
5301 | * Returns: whether a match was found |
5302 | */ |
5303 | gboolean |
5304 | gtk_text_iter_backward_search (const GtkTextIter *iter, |
5305 | const char *str, |
5306 | GtkTextSearchFlags flags, |
5307 | GtkTextIter *match_start, |
5308 | GtkTextIter *match_end, |
5309 | const GtkTextIter *limit) |
5310 | { |
5311 | char **lines = NULL; |
5312 | char **l; |
5313 | int n_lines; |
5314 | LinesWindow win; |
5315 | gboolean retval = FALSE; |
5316 | gboolean visible_only; |
5317 | gboolean slice; |
5318 | gboolean case_insensitive; |
5319 | |
5320 | g_return_val_if_fail (iter != NULL, FALSE); |
5321 | g_return_val_if_fail (str != NULL, FALSE); |
5322 | |
5323 | if (limit && |
5324 | gtk_text_iter_compare (lhs: limit, rhs: iter) > 0) |
5325 | return FALSE; |
5326 | |
5327 | if (*str == '\0') |
5328 | { |
5329 | /* If we can move one char, return the empty string there */ |
5330 | GtkTextIter match = *iter; |
5331 | |
5332 | if (limit && gtk_text_iter_equal (lhs: limit, rhs: &match)) |
5333 | return FALSE; |
5334 | |
5335 | if (gtk_text_iter_backward_char (iter: &match)) |
5336 | { |
5337 | if (match_start) |
5338 | *match_start = match; |
5339 | if (match_end) |
5340 | *match_end = match; |
5341 | return TRUE; |
5342 | } |
5343 | else |
5344 | return FALSE; |
5345 | } |
5346 | |
5347 | visible_only = (flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0; |
5348 | slice = (flags & GTK_TEXT_SEARCH_TEXT_ONLY) == 0; |
5349 | case_insensitive = (flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0; |
5350 | |
5351 | /* locate all lines */ |
5352 | |
5353 | lines = strbreakup (string: str, delimiter: "\n" , max_tokens: -1, num_strings: &n_lines, case_insensitive); |
5354 | |
5355 | win.n_lines = n_lines; |
5356 | win.slice = slice; |
5357 | win.visible_only = visible_only; |
5358 | |
5359 | lines_window_init (win: &win, start: iter); |
5360 | |
5361 | if (*win.lines == NULL) |
5362 | goto out; |
5363 | |
5364 | do |
5365 | { |
5366 | const char *first_line_match; |
5367 | |
5368 | if (limit && |
5369 | gtk_text_iter_compare (lhs: limit, rhs: &win.first_line_end) > 0) |
5370 | { |
5371 | /* We're now before the search limit, abort. */ |
5372 | goto out; |
5373 | } |
5374 | |
5375 | /* If there are multiple lines, the first line will |
5376 | * end in '\n', so this will only match at the |
5377 | * end of the first line, which is correct. |
5378 | */ |
5379 | if (!case_insensitive) |
5380 | first_line_match = g_strrstr (haystack: *win.lines, needle: *lines); |
5381 | else |
5382 | first_line_match = utf8_strrcasestr (haystack: *win.lines, needle: *lines); |
5383 | |
5384 | if (first_line_match && |
5385 | vectors_equal_ignoring_trailing (vec1: lines + 1, vec2: win.lines + 1, |
5386 | case_insensitive)) |
5387 | { |
5388 | /* Match! */ |
5389 | int offset; |
5390 | GtkTextIter start_tmp; |
5391 | GtkTextIter end_tmp; |
5392 | |
5393 | /* Offset to start of search string */ |
5394 | offset = g_utf8_strlen (p: *win.lines, max: first_line_match - *win.lines); |
5395 | |
5396 | start_tmp = win.first_line_start; |
5397 | forward_chars_with_skipping (iter: &start_tmp, count: offset, |
5398 | skip_invisible: visible_only, skip_nontext: !slice, FALSE); |
5399 | |
5400 | if (limit && |
5401 | gtk_text_iter_compare (lhs: limit, rhs: &start_tmp) > 0) |
5402 | goto out; /* match was bogus */ |
5403 | |
5404 | if (match_start) |
5405 | *match_start = start_tmp; |
5406 | |
5407 | /* Go to end of search string */ |
5408 | offset = 0; |
5409 | for (l = lines; *l != NULL; l++) |
5410 | offset += g_utf8_strlen (p: *l, max: -1); |
5411 | |
5412 | end_tmp = start_tmp; |
5413 | forward_chars_with_skipping (iter: &end_tmp, count: offset, |
5414 | skip_invisible: visible_only, skip_nontext: !slice, skip_decomp: case_insensitive); |
5415 | |
5416 | if (match_end) |
5417 | *match_end = end_tmp; |
5418 | |
5419 | retval = TRUE; |
5420 | goto out; |
5421 | } |
5422 | } |
5423 | while (lines_window_back (win: &win)); |
5424 | |
5425 | out: |
5426 | lines_window_free (win: &win); |
5427 | g_strfreev (str_array: lines); |
5428 | |
5429 | return retval; |
5430 | } |
5431 | |
5432 | /* |
5433 | * Comparisons |
5434 | */ |
5435 | |
5436 | /** |
5437 | * gtk_text_iter_equal: |
5438 | * @lhs: a `GtkTextIter` |
5439 | * @rhs: another `GtkTextIter` |
5440 | * |
5441 | * Tests whether two iterators are equal, using the fastest possible |
5442 | * mechanism. |
5443 | * |
5444 | * This function is very fast; you can expect it to perform |
5445 | * better than e.g. getting the character offset for each |
5446 | * iterator and comparing the offsets yourself. Also, it’s a |
5447 | * bit faster than [method@Gtk.TextIter.compare]. |
5448 | * |
5449 | * Returns: %TRUE if the iterators point to the same place in the buffer |
5450 | **/ |
5451 | gboolean |
5452 | gtk_text_iter_equal (const GtkTextIter *lhs, |
5453 | const GtkTextIter *rhs) |
5454 | { |
5455 | GtkTextRealIter *real_lhs; |
5456 | GtkTextRealIter *real_rhs; |
5457 | |
5458 | real_lhs = (GtkTextRealIter*)lhs; |
5459 | real_rhs = (GtkTextRealIter*)rhs; |
5460 | |
5461 | check_invariants (iter: lhs); |
5462 | check_invariants (iter: rhs); |
5463 | |
5464 | if (real_lhs->line != real_rhs->line) |
5465 | return FALSE; |
5466 | else if (real_lhs->line_byte_offset >= 0 && |
5467 | real_rhs->line_byte_offset >= 0) |
5468 | return real_lhs->line_byte_offset == real_rhs->line_byte_offset; |
5469 | else |
5470 | { |
5471 | /* the ensure_char_offsets() calls do nothing if the char offsets |
5472 | are already up-to-date. */ |
5473 | ensure_char_offsets (iter: real_lhs); |
5474 | ensure_char_offsets (iter: real_rhs); |
5475 | return real_lhs->line_char_offset == real_rhs->line_char_offset; |
5476 | } |
5477 | } |
5478 | |
5479 | gboolean |
5480 | _gtk_text_iter_same_line (const GtkTextIter *lhs, |
5481 | const GtkTextIter *rhs) |
5482 | { |
5483 | GtkTextRealIter *real_lhs; |
5484 | GtkTextRealIter *real_rhs; |
5485 | |
5486 | real_lhs = gtk_text_iter_make_surreal (iter: lhs); |
5487 | real_rhs = gtk_text_iter_make_surreal (iter: rhs); |
5488 | |
5489 | if (real_lhs == NULL || real_rhs == NULL) |
5490 | return FALSE; |
5491 | |
5492 | check_invariants (iter: lhs); |
5493 | check_invariants (iter: rhs); |
5494 | |
5495 | return real_lhs->line == real_rhs->line; |
5496 | } |
5497 | |
5498 | /** |
5499 | * gtk_text_iter_compare: |
5500 | * @lhs: a `GtkTextIter` |
5501 | * @rhs: another `GtkTextIter` |
5502 | * |
5503 | * A qsort()-style function that returns negative if @lhs is less than |
5504 | * @rhs, positive if @lhs is greater than @rhs, and 0 if they’re equal. |
5505 | * |
5506 | * Ordering is in character offset order, i.e. the first character |
5507 | * in the buffer is less than the second character in the buffer. |
5508 | * |
5509 | * Returns: -1 if @lhs is less than @rhs, 1 if @lhs is greater, 0 if they are equal |
5510 | */ |
5511 | int |
5512 | gtk_text_iter_compare (const GtkTextIter *lhs, |
5513 | const GtkTextIter *rhs) |
5514 | { |
5515 | GtkTextRealIter *real_lhs; |
5516 | GtkTextRealIter *real_rhs; |
5517 | |
5518 | real_lhs = gtk_text_iter_make_surreal (iter: lhs); |
5519 | real_rhs = gtk_text_iter_make_surreal (iter: rhs); |
5520 | |
5521 | if (real_lhs == NULL || |
5522 | real_rhs == NULL) |
5523 | return -1; /* why not */ |
5524 | |
5525 | check_invariants (iter: lhs); |
5526 | check_invariants (iter: rhs); |
5527 | |
5528 | if (real_lhs->line == real_rhs->line) |
5529 | { |
5530 | int left_index, right_index; |
5531 | |
5532 | if (real_lhs->line_byte_offset >= 0 && |
5533 | real_rhs->line_byte_offset >= 0) |
5534 | { |
5535 | left_index = real_lhs->line_byte_offset; |
5536 | right_index = real_rhs->line_byte_offset; |
5537 | } |
5538 | else |
5539 | { |
5540 | /* the ensure_char_offsets() calls do nothing if |
5541 | the offsets are already up-to-date. */ |
5542 | ensure_char_offsets (iter: real_lhs); |
5543 | ensure_char_offsets (iter: real_rhs); |
5544 | left_index = real_lhs->line_char_offset; |
5545 | right_index = real_rhs->line_char_offset; |
5546 | } |
5547 | |
5548 | if (left_index < right_index) |
5549 | return -1; |
5550 | else if (left_index > right_index) |
5551 | return 1; |
5552 | else |
5553 | return 0; |
5554 | } |
5555 | else |
5556 | { |
5557 | int line1, line2; |
5558 | |
5559 | line1 = gtk_text_iter_get_line (iter: lhs); |
5560 | line2 = gtk_text_iter_get_line (iter: rhs); |
5561 | if (line1 < line2) |
5562 | return -1; |
5563 | else if (line1 > line2) |
5564 | return 1; |
5565 | else |
5566 | return 0; |
5567 | } |
5568 | } |
5569 | |
5570 | /** |
5571 | * gtk_text_iter_in_range: |
5572 | * @iter: a `GtkTextIter` |
5573 | * @start: start of range |
5574 | * @end: end of range |
5575 | * |
5576 | * Checks whether @iter falls in the range [@start, @end). |
5577 | * |
5578 | * @start and @end must be in ascending order. |
5579 | * |
5580 | * Returns: %TRUE if @iter is in the range |
5581 | */ |
5582 | gboolean |
5583 | gtk_text_iter_in_range (const GtkTextIter *iter, |
5584 | const GtkTextIter *start, |
5585 | const GtkTextIter *end) |
5586 | { |
5587 | g_return_val_if_fail (iter != NULL, FALSE); |
5588 | g_return_val_if_fail (start != NULL, FALSE); |
5589 | g_return_val_if_fail (end != NULL, FALSE); |
5590 | g_return_val_if_fail (gtk_text_iter_compare (start, end) <= 0, FALSE); |
5591 | |
5592 | return gtk_text_iter_compare (lhs: iter, rhs: start) >= 0 && |
5593 | gtk_text_iter_compare (lhs: iter, rhs: end) < 0; |
5594 | } |
5595 | |
5596 | /** |
5597 | * gtk_text_iter_order: |
5598 | * @first: a `GtkTextIter` |
5599 | * @second: another `GtkTextIter` |
5600 | * |
5601 | * Swaps the value of @first and @second if @second comes before |
5602 | * @first in the buffer. |
5603 | * |
5604 | * That is, ensures that @first and @second are in sequence. |
5605 | * Most text buffer functions that take a range call this |
5606 | * automatically on your behalf, so there’s no real reason to |
5607 | * call it yourself in those cases. There are some exceptions, |
5608 | * such as [method@Gtk.TextIter.in_range], that expect a |
5609 | * pre-sorted range. |
5610 | */ |
5611 | void |
5612 | gtk_text_iter_order (GtkTextIter *first, |
5613 | GtkTextIter *second) |
5614 | { |
5615 | g_return_if_fail (first != NULL); |
5616 | g_return_if_fail (second != NULL); |
5617 | |
5618 | if (gtk_text_iter_compare (lhs: first, rhs: second) > 0) |
5619 | { |
5620 | GtkTextIter tmp; |
5621 | |
5622 | tmp = *first; |
5623 | *first = *second; |
5624 | *second = tmp; |
5625 | } |
5626 | } |
5627 | |
5628 | /* |
5629 | * Init iterators from the BTree |
5630 | */ |
5631 | |
5632 | void |
5633 | _gtk_text_btree_get_iter_at_char (GtkTextBTree *tree, |
5634 | GtkTextIter *iter, |
5635 | int char_index) |
5636 | { |
5637 | GtkTextRealIter *real = (GtkTextRealIter*)iter; |
5638 | int real_char_index; |
5639 | int line_start; |
5640 | GtkTextLine *line; |
5641 | |
5642 | g_return_if_fail (iter != NULL); |
5643 | g_return_if_fail (tree != NULL); |
5644 | |
5645 | line = _gtk_text_btree_get_line_at_char (tree, char_index, |
5646 | line_start_index: &line_start, real_char_index: &real_char_index); |
5647 | |
5648 | iter_init_from_char_offset (iter, tree, line, line_char_offset: real_char_index - line_start); |
5649 | |
5650 | real->cached_char_index = real_char_index; |
5651 | |
5652 | check_invariants (iter); |
5653 | } |
5654 | |
5655 | void |
5656 | _gtk_text_btree_get_iter_at_line_char (GtkTextBTree *tree, |
5657 | GtkTextIter *iter, |
5658 | int line_number, |
5659 | int char_on_line) |
5660 | { |
5661 | GtkTextRealIter *real = (GtkTextRealIter*)iter; |
5662 | GtkTextLine *line; |
5663 | int real_line; |
5664 | |
5665 | g_return_if_fail (iter != NULL); |
5666 | g_return_if_fail (tree != NULL); |
5667 | |
5668 | line = _gtk_text_btree_get_line_no_last (tree, line_number, real_line_number: &real_line); |
5669 | |
5670 | iter_init_from_char_offset (iter, tree, line, line_char_offset: char_on_line); |
5671 | |
5672 | /* We might as well cache this, since we know it. */ |
5673 | real->cached_line_number = real_line; |
5674 | |
5675 | check_invariants (iter); |
5676 | } |
5677 | |
5678 | void |
5679 | _gtk_text_btree_get_iter_at_line_byte (GtkTextBTree *tree, |
5680 | GtkTextIter *iter, |
5681 | int line_number, |
5682 | int byte_index) |
5683 | { |
5684 | GtkTextRealIter *real = (GtkTextRealIter*)iter; |
5685 | GtkTextLine *line; |
5686 | int real_line; |
5687 | |
5688 | g_return_if_fail (iter != NULL); |
5689 | g_return_if_fail (tree != NULL); |
5690 | |
5691 | line = _gtk_text_btree_get_line_no_last (tree, line_number, real_line_number: &real_line); |
5692 | |
5693 | iter_init_from_byte_offset (iter, tree, line, line_byte_offset: byte_index); |
5694 | |
5695 | /* We might as well cache this, since we know it. */ |
5696 | real->cached_line_number = real_line; |
5697 | |
5698 | check_invariants (iter); |
5699 | } |
5700 | |
5701 | void |
5702 | _gtk_text_btree_get_iter_at_line (GtkTextBTree *tree, |
5703 | GtkTextIter *iter, |
5704 | GtkTextLine *line, |
5705 | int byte_offset) |
5706 | { |
5707 | g_return_if_fail (iter != NULL); |
5708 | g_return_if_fail (tree != NULL); |
5709 | g_return_if_fail (line != NULL); |
5710 | |
5711 | iter_init_from_byte_offset (iter, tree, line, line_byte_offset: byte_offset); |
5712 | |
5713 | check_invariants (iter); |
5714 | } |
5715 | |
5716 | gboolean |
5717 | _gtk_text_btree_get_iter_at_first_toggle (GtkTextBTree *tree, |
5718 | GtkTextIter *iter, |
5719 | GtkTextTag *tag) |
5720 | { |
5721 | GtkTextLine *line; |
5722 | |
5723 | g_return_val_if_fail (iter != NULL, FALSE); |
5724 | g_return_val_if_fail (tree != NULL, FALSE); |
5725 | |
5726 | line = _gtk_text_btree_first_could_contain_tag (tree, tag); |
5727 | |
5728 | if (line == NULL) |
5729 | { |
5730 | /* Set iter to last in tree */ |
5731 | _gtk_text_btree_get_end_iter (tree, iter); |
5732 | check_invariants (iter); |
5733 | return FALSE; |
5734 | } |
5735 | else |
5736 | { |
5737 | iter_init_from_byte_offset (iter, tree, line, line_byte_offset: 0); |
5738 | |
5739 | if (!gtk_text_iter_toggles_tag (iter, tag)) |
5740 | gtk_text_iter_forward_to_tag_toggle (iter, tag); |
5741 | |
5742 | check_invariants (iter); |
5743 | return TRUE; |
5744 | } |
5745 | } |
5746 | |
5747 | gboolean |
5748 | _gtk_text_btree_get_iter_at_last_toggle (GtkTextBTree *tree, |
5749 | GtkTextIter *iter, |
5750 | GtkTextTag *tag) |
5751 | { |
5752 | gboolean found; |
5753 | |
5754 | g_return_val_if_fail (iter != NULL, FALSE); |
5755 | g_return_val_if_fail (tree != NULL, FALSE); |
5756 | |
5757 | _gtk_text_btree_get_end_iter (tree, iter); |
5758 | |
5759 | if (gtk_text_iter_toggles_tag (iter, tag)) |
5760 | found = TRUE; |
5761 | else |
5762 | found = gtk_text_iter_backward_to_tag_toggle (iter, tag); |
5763 | |
5764 | check_invariants (iter); |
5765 | |
5766 | return found; |
5767 | } |
5768 | |
5769 | gboolean |
5770 | _gtk_text_btree_get_iter_at_mark_name (GtkTextBTree *tree, |
5771 | GtkTextIter *iter, |
5772 | const char *mark_name) |
5773 | { |
5774 | GtkTextMark *mark; |
5775 | |
5776 | g_return_val_if_fail (iter != NULL, FALSE); |
5777 | g_return_val_if_fail (tree != NULL, FALSE); |
5778 | |
5779 | mark = _gtk_text_btree_get_mark_by_name (tree, name: mark_name); |
5780 | |
5781 | if (mark == NULL) |
5782 | return FALSE; |
5783 | else |
5784 | { |
5785 | _gtk_text_btree_get_iter_at_mark (tree, iter, mark); |
5786 | check_invariants (iter); |
5787 | return TRUE; |
5788 | } |
5789 | } |
5790 | |
5791 | void |
5792 | _gtk_text_btree_get_iter_at_paintable (GtkTextBTree *tree, |
5793 | GtkTextIter *iter, |
5794 | GtkTextLineSegment *seg) |
5795 | { |
5796 | g_return_if_fail (iter != NULL); |
5797 | g_return_if_fail (tree != NULL); |
5798 | |
5799 | iter_init_from_segment (iter, tree, |
5800 | line: seg->body.paintable.line, segment: seg); |
5801 | g_assert (seg->body.paintable.line == _gtk_text_iter_get_text_line (iter)); |
5802 | check_invariants (iter); |
5803 | } |
5804 | |
5805 | void |
5806 | _gtk_text_btree_get_iter_at_mark (GtkTextBTree *tree, |
5807 | GtkTextIter *iter, |
5808 | GtkTextMark *mark) |
5809 | { |
5810 | GtkTextLineSegment *seg; |
5811 | |
5812 | g_return_if_fail (iter != NULL); |
5813 | g_return_if_fail (tree != NULL); |
5814 | g_return_if_fail (GTK_IS_TEXT_MARK (mark)); |
5815 | |
5816 | seg = mark->segment; |
5817 | |
5818 | iter_init_from_segment (iter, tree, |
5819 | line: seg->body.mark.line, segment: seg); |
5820 | g_assert (seg->body.mark.line == _gtk_text_iter_get_text_line (iter)); |
5821 | check_invariants (iter); |
5822 | } |
5823 | |
5824 | void |
5825 | _gtk_text_btree_get_iter_at_child_anchor (GtkTextBTree *tree, |
5826 | GtkTextIter *iter, |
5827 | GtkTextChildAnchor *anchor) |
5828 | { |
5829 | GtkTextLineSegment *seg; |
5830 | |
5831 | g_return_if_fail (iter != NULL); |
5832 | g_return_if_fail (tree != NULL); |
5833 | g_return_if_fail (GTK_IS_TEXT_CHILD_ANCHOR (anchor)); |
5834 | |
5835 | seg = anchor->segment; |
5836 | |
5837 | g_assert (seg->body.child.line != NULL); |
5838 | |
5839 | iter_init_from_segment (iter, tree, |
5840 | line: seg->body.child.line, segment: seg); |
5841 | g_assert (seg->body.child.line == _gtk_text_iter_get_text_line (iter)); |
5842 | check_invariants (iter); |
5843 | } |
5844 | |
5845 | void |
5846 | _gtk_text_btree_get_end_iter (GtkTextBTree *tree, |
5847 | GtkTextIter *iter) |
5848 | { |
5849 | g_return_if_fail (iter != NULL); |
5850 | g_return_if_fail (tree != NULL); |
5851 | |
5852 | _gtk_text_btree_get_iter_at_char (tree, |
5853 | iter, |
5854 | char_index: _gtk_text_btree_char_count (tree)); |
5855 | check_invariants (iter); |
5856 | } |
5857 | |
5858 | void |
5859 | _gtk_text_iter_check (const GtkTextIter *iter) |
5860 | { |
5861 | const GtkTextRealIter *real = (const GtkTextRealIter*)iter; |
5862 | int line_char_offset, line_byte_offset, seg_char_offset, seg_byte_offset; |
5863 | GtkTextLineSegment *byte_segment = NULL; |
5864 | GtkTextLineSegment *byte_any_segment = NULL; |
5865 | GtkTextLineSegment *char_segment = NULL; |
5866 | GtkTextLineSegment *char_any_segment = NULL; |
5867 | gboolean segments_updated; |
5868 | |
5869 | /* This function checks our class invariants for the Iter class. */ |
5870 | |
5871 | g_assert (sizeof (GtkTextIter) == sizeof (GtkTextRealIter)); |
5872 | |
5873 | if (real->chars_changed_stamp != |
5874 | _gtk_text_btree_get_chars_changed_stamp (tree: real->tree)) |
5875 | g_error ("iterator check failed: invalid iterator" ); |
5876 | |
5877 | if (real->line_char_offset < 0 && real->line_byte_offset < 0) |
5878 | g_error ("iterator check failed: both char and byte offsets are invalid" ); |
5879 | |
5880 | segments_updated = (real->segments_changed_stamp == |
5881 | _gtk_text_btree_get_segments_changed_stamp (tree: real->tree)); |
5882 | |
5883 | #if 0 |
5884 | printf ("checking iter, segments %s updated, byte %d char %d\n" , |
5885 | segments_updated ? "are" : "aren't" , |
5886 | real->line_byte_offset, |
5887 | real->line_char_offset); |
5888 | #endif |
5889 | |
5890 | if (segments_updated) |
5891 | { |
5892 | if (real->segment_char_offset < 0 && real->segment_byte_offset < 0) |
5893 | g_error ("iterator check failed: both char and byte segment offsets are invalid" ); |
5894 | |
5895 | if (real->segment->char_count == 0) |
5896 | g_error ("iterator check failed: segment is not indexable." ); |
5897 | |
5898 | if (real->line_char_offset >= 0 && real->segment_char_offset < 0) |
5899 | g_error ("segment char offset is not properly up-to-date" ); |
5900 | |
5901 | if (real->line_byte_offset >= 0 && real->segment_byte_offset < 0) |
5902 | g_error ("segment byte offset is not properly up-to-date" ); |
5903 | |
5904 | if (real->segment_byte_offset >= 0 && |
5905 | real->segment_byte_offset >= real->segment->byte_count) |
5906 | g_error ("segment byte offset is too large." ); |
5907 | |
5908 | if (real->segment_char_offset >= 0 && |
5909 | real->segment_char_offset >= real->segment->char_count) |
5910 | g_error ("segment char offset is too large." ); |
5911 | } |
5912 | |
5913 | if (real->line_byte_offset >= 0) |
5914 | { |
5915 | _gtk_text_line_byte_locate (line: real->line, byte_offset: real->line_byte_offset, |
5916 | segment: &byte_segment, any_segment: &byte_any_segment, |
5917 | seg_byte_offset: &seg_byte_offset, line_byte_offset: &line_byte_offset); |
5918 | |
5919 | if (line_byte_offset != real->line_byte_offset) |
5920 | g_error ("wrong byte offset was stored in iterator" ); |
5921 | |
5922 | if (segments_updated) |
5923 | { |
5924 | if (real->segment != byte_segment) |
5925 | g_error ("wrong segment was stored in iterator" ); |
5926 | |
5927 | if (real->any_segment != byte_any_segment) |
5928 | g_error ("wrong any_segment was stored in iterator" ); |
5929 | |
5930 | if (seg_byte_offset != real->segment_byte_offset) |
5931 | g_error ("wrong segment byte offset was stored in iterator" ); |
5932 | |
5933 | if (byte_segment->type == >k_text_char_type) |
5934 | { |
5935 | const char *p; |
5936 | p = byte_segment->body.chars + seg_byte_offset; |
5937 | |
5938 | if (!gtk_text_byte_begins_utf8_char (byte: p)) |
5939 | g_error ("broken iterator byte index pointed into the middle of a character" ); |
5940 | } |
5941 | } |
5942 | } |
5943 | |
5944 | if (real->line_char_offset >= 0) |
5945 | { |
5946 | _gtk_text_line_char_locate (line: real->line, char_offset: real->line_char_offset, |
5947 | segment: &char_segment, any_segment: &char_any_segment, |
5948 | seg_char_offset: &seg_char_offset, line_char_offset: &line_char_offset); |
5949 | |
5950 | if (line_char_offset != real->line_char_offset) |
5951 | g_error ("wrong char offset was stored in iterator" ); |
5952 | |
5953 | if (segments_updated) |
5954 | { |
5955 | if (real->segment != char_segment) |
5956 | g_error ("wrong segment was stored in iterator" ); |
5957 | |
5958 | if (real->any_segment != char_any_segment) |
5959 | g_error ("wrong any_segment was stored in iterator" ); |
5960 | |
5961 | if (seg_char_offset != real->segment_char_offset) |
5962 | g_error ("wrong segment char offset was stored in iterator" ); |
5963 | |
5964 | if (char_segment->type == >k_text_char_type) |
5965 | { |
5966 | const char *p; |
5967 | p = g_utf8_offset_to_pointer (str: char_segment->body.chars, |
5968 | offset: seg_char_offset); |
5969 | |
5970 | /* hmm, not likely to happen eh */ |
5971 | if (!gtk_text_byte_begins_utf8_char (byte: p)) |
5972 | g_error ("broken iterator char offset pointed into the middle of a character" ); |
5973 | } |
5974 | } |
5975 | } |
5976 | |
5977 | if (real->line_char_offset >= 0 && real->line_byte_offset >= 0) |
5978 | { |
5979 | if (byte_segment != char_segment) |
5980 | g_error ("char and byte offsets did not point to the same segment" ); |
5981 | |
5982 | if (byte_any_segment != char_any_segment) |
5983 | g_error ("char and byte offsets did not point to the same any segment" ); |
5984 | |
5985 | /* Make sure the segment offsets are equivalent, if it's a char |
5986 | segment. */ |
5987 | if (char_segment->type == >k_text_char_type) |
5988 | { |
5989 | int byte_offset = 0; |
5990 | int char_offset = 0; |
5991 | while (char_offset < seg_char_offset) |
5992 | { |
5993 | const char * start = char_segment->body.chars + byte_offset; |
5994 | byte_offset += g_utf8_next_char (start) - start; |
5995 | char_offset += 1; |
5996 | } |
5997 | |
5998 | if (byte_offset != seg_byte_offset) |
5999 | g_error ("byte offset did not correspond to char offset" ); |
6000 | |
6001 | char_offset = |
6002 | g_utf8_strlen (p: char_segment->body.chars, max: seg_byte_offset); |
6003 | |
6004 | if (char_offset != seg_char_offset) |
6005 | g_error ("char offset did not correspond to byte offset" ); |
6006 | |
6007 | if (!gtk_text_byte_begins_utf8_char (byte: char_segment->body.chars + seg_byte_offset)) |
6008 | g_error ("byte index for iterator does not index the start of a character" ); |
6009 | } |
6010 | } |
6011 | |
6012 | if (real->cached_line_number >= 0) |
6013 | { |
6014 | int should_be; |
6015 | |
6016 | should_be = _gtk_text_line_get_number (line: real->line); |
6017 | if (real->cached_line_number != should_be) |
6018 | g_error ("wrong line number was cached" ); |
6019 | } |
6020 | |
6021 | if (real->cached_char_index >= 0) |
6022 | { |
6023 | if (real->line_char_offset >= 0) /* only way we can check it |
6024 | efficiently, not a real |
6025 | invariant. */ |
6026 | { |
6027 | int char_index; |
6028 | |
6029 | char_index = _gtk_text_line_char_index (line: real->line); |
6030 | char_index += real->line_char_offset; |
6031 | |
6032 | if (real->cached_char_index != char_index) |
6033 | g_error ("wrong char index was cached" ); |
6034 | } |
6035 | } |
6036 | |
6037 | if (_gtk_text_line_is_last (line: real->line, tree: real->tree)) |
6038 | g_error ("Iterator was on last line (past the end iterator)" ); |
6039 | } |
6040 | |