2 // "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $"
4 // Copyright 2001-2010 by Bill Spitzak and others.
5 // Original code Copyright Mark Edel. Permission to distribute under
6 // the LGPL for the FLTK library granted by Mark Edel.
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // Library General Public License for more details.
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23 // Please report all bugs and problems on the following page:
25 // http://www.fltk.org/str.php
30 #include <FL/fl_utf8.h>
34 #include <FL/Fl_Text_Buffer.H>
35 #include <FL/fl_ask.H>
39 This file is based on a port of NEdit to FLTK many years ago. NEdit at that
40 point was already stretched beyond the task it was designed for which explains
41 why the source code is sometimes pretty convoluted. It still is a very useful
42 widget for FLTK, and we are thankful that the nedit team allowed us to
45 With the introduction of Unicode and UTF-8, Fl_Text_... has to go into a whole
46 new generation of code. Originally designed for monospaced fonts only, many
47 features make less sense in the multibyte and multiwidth world of UTF-8.
49 Columns are a good example. There is simply no such thing. The new Fl_Text_...
50 widget converts columns to pixels by multiplying them with the average
51 character width for a given font.
53 Rectangular selections were rarely used (if at all) and make little sense when
54 using variable width fonts. They have been removed.
56 Using multiple spaces to emulate tab stops has been replaced by pixel counting
57 routines. They are slower, but give the expected result for proportional fonts.
59 And constantly recalculating character widths is just much too expensive. Lines
60 of text are now subdivided into blocks of text which are measured at once
61 instead of individual characters.
67 static int max(int i1
, int i2
)
69 return i1
>= i2
? i1
: i2
;
72 static int min(int i1
, int i2
)
74 return i1
<= i2
? i1
: i2
;
80 static char *undobuffer
;
81 static int undobufferlength
;
82 static Fl_Text_Buffer
*undowidget
;
83 static int undoat
; // points after insertion
84 static int undocut
; // number of characters deleted there
85 static int undoinsert
; // number of characters inserted
86 static int undoyankcut
; // length of valid contents of buffer, even if undocut=0
89 Resize the undo buffer to match at least the requested size.
91 static void undobuffersize(int n
)
93 if (n
> undobufferlength
) {
96 undobufferlength
*= 2;
97 } while (undobufferlength
< n
);
98 undobuffer
= (char *) realloc(undobuffer
, undobufferlength
);
100 undobufferlength
= n
+ 9;
101 undobuffer
= (char *) malloc(undobufferlength
);
106 static void def_transcoding_warning_action(Fl_Text_Buffer
*text
)
108 fl_alert("%s", text
->file_encoding_warning_message
);
112 Initialize all variables.
114 Fl_Text_Buffer::Fl_Text_Buffer(int requestedSize
, int preferredGapSize
)
117 mPreferredGapSize
= preferredGapSize
;
118 mBuf
= (char *) malloc(requestedSize
+ mPreferredGapSize
);
120 mGapEnd
= mPreferredGapSize
;
122 mPrimary
.mSelected
= 0;
123 mPrimary
.mStart
= mPrimary
.mEnd
= 0;
124 mSecondary
.mSelected
= 0;
125 mSecondary
.mStart
= mSecondary
.mEnd
= 0;
126 mHighlight
.mSelected
= 0;
127 mHighlight
.mStart
= mHighlight
.mEnd
= 0;
131 mNPredeleteProcs
= 0;
132 mPredeleteProcs
= NULL
;
133 mPredeleteCbArgs
= NULL
;
136 input_file_was_transcoded
= 0;
137 transcoding_warning_action
= def_transcoding_warning_action
;
144 Fl_Text_Buffer::~Fl_Text_Buffer()
147 if (mNModifyProcs
!= 0) {
148 delete[]mModifyProcs
;
151 if (mNPredeleteProcs
!= 0) {
152 delete[]mPredeleteProcs
;
153 delete[]mPredeleteCbArgs
;
159 This function copies verbose whatever is in front and after the gap into a
162 char *Fl_Text_Buffer::text() const {
163 char *t
= (char *) malloc(mLength
+ 1);
164 memcpy(t
, mBuf
, mGapStart
);
165 memcpy(t
+mGapStart
, mBuf
+mGapEnd
, mLength
- mGapStart
);
172 Set the text buffer to a new string.
174 void Fl_Text_Buffer::text(const char *t
)
178 call_predelete_callbacks(0, length());
180 /* Save information for redisplay, and get rid of the old buffer */
181 const char *deletedText
= text();
182 int deletedLength
= mLength
;
185 /* Start a new buffer with a gap of mPreferredGapSize at the end */
186 int insertedLength
= strlen(t
);
187 mBuf
= (char *) malloc(insertedLength
+ mPreferredGapSize
);
188 mLength
= insertedLength
;
189 mGapStart
= insertedLength
;
190 mGapEnd
= mGapStart
+ mPreferredGapSize
;
191 memcpy(mBuf
, t
, insertedLength
);
193 /* Zero all of the existing selections */
194 update_selections(0, deletedLength
, 0);
196 /* Call the saved display routine(s) to update the screen */
197 call_modify_callbacks(0, deletedLength
, insertedLength
, 0, deletedText
);
198 free((void *) deletedText
);
203 Creates a range of text to a new buffer and copies verbose from around the gap.
205 char *Fl_Text_Buffer::text_range(int start
, int end
) const {
206 IS_UTF8_ALIGNED2(this, (start
))
207 IS_UTF8_ALIGNED2(this, (end
))
211 /* Make sure start and end are ok, and allocate memory for returned string.
212 If start is bad, return "", if end is bad, adjust it. */
213 if (start
< 0 || start
> mLength
)
215 s
= (char *) malloc(1);
226 int copiedLength
= end
- start
;
227 s
= (char *) malloc(copiedLength
+ 1);
229 /* Copy the text from the buffer to the returned string */
230 if (end
<= mGapStart
) {
231 memcpy(s
, mBuf
+ start
, copiedLength
);
232 } else if (start
>= mGapStart
) {
233 memcpy(s
, mBuf
+ start
+ (mGapEnd
- mGapStart
), copiedLength
);
235 int part1Length
= mGapStart
- start
;
236 memcpy(s
, mBuf
+ start
, part1Length
);
237 memcpy(s
+ part1Length
, mBuf
+ mGapEnd
, copiedLength
- part1Length
);
239 s
[copiedLength
] = '\0';
244 Return a UCS-4 character at the given index.
245 Pos must be at a character boundary.
247 unsigned int Fl_Text_Buffer::char_at(int pos
) const {
248 if (pos
< 0 || pos
>= mLength
)
251 IS_UTF8_ALIGNED2(this, (pos
))
253 const char *src
= address(pos
);
254 return fl_utf8decode(src
, 0, 0);
259 Return the raw byte at the given index.
260 This function ignores all unicode encoding.
262 char Fl_Text_Buffer::byte_at(int pos
) const {
263 if (pos
< 0 || pos
>= mLength
)
265 const char *src
= address(pos
);
271 Insert some text at the given index.
272 Pos must be at a character boundary.
274 void Fl_Text_Buffer::insert(int pos
, const char *text
)
276 IS_UTF8_ALIGNED2(this, (pos
))
277 IS_UTF8_ALIGNED(text
)
279 /* check if there is actually any text */
283 /* if pos is not contiguous to existing text, make it */
289 /* Even if nothing is deleted, we must call these callbacks */
290 call_predelete_callbacks(pos
, 0);
292 /* insert and redisplay */
293 int nInserted
= insert_(pos
, text
);
294 mCursorPosHint
= pos
+ nInserted
;
295 IS_UTF8_ALIGNED2(this, (mCursorPosHint
))
296 call_modify_callbacks(pos
, 0, nInserted
, 0, NULL
);
301 Replace a range of text with new text.
302 Start and end must be at a character boundary.
304 void Fl_Text_Buffer::replace(int start
, int end
, const char *text
)
314 IS_UTF8_ALIGNED2(this, (start
))
315 IS_UTF8_ALIGNED2(this, (end
))
316 IS_UTF8_ALIGNED(text
)
318 call_predelete_callbacks(start
, end
- start
);
319 const char *deletedText
= text_range(start
, end
);
321 int nInserted
= insert_(start
, text
);
322 mCursorPosHint
= start
+ nInserted
;
323 call_modify_callbacks(start
, end
- start
, nInserted
, 0, deletedText
);
324 free((void *) deletedText
);
329 Remove a range of text.
330 Start and End must be at a character boundary.
332 void Fl_Text_Buffer::remove(int start
, int end
)
334 /* Make sure the arguments make sense */
349 IS_UTF8_ALIGNED2(this, (start
))
350 IS_UTF8_ALIGNED2(this, (end
))
355 call_predelete_callbacks(start
, end
- start
);
356 /* Remove and redisplay */
357 const char *deletedText
= text_range(start
, end
);
359 mCursorPosHint
= start
;
360 call_modify_callbacks(start
, end
- start
, 0, 0, deletedText
);
361 free((void *) deletedText
);
366 Copy a range of text from another text buffer.
367 fromStart, fromEnd, and toPos must be at a character boundary.
369 void Fl_Text_Buffer::copy(Fl_Text_Buffer
* fromBuf
, int fromStart
,
370 int fromEnd
, int toPos
)
372 IS_UTF8_ALIGNED2(fromBuf
, fromStart
)
373 IS_UTF8_ALIGNED2(fromBuf
, fromEnd
)
374 IS_UTF8_ALIGNED2(this, (toPos
))
376 int copiedLength
= fromEnd
- fromStart
;
378 /* Prepare the buffer to receive the new text. If the new text fits in
379 the current buffer, just move the gap (if necessary) to where
380 the text should be inserted. If the new text is too large, reallocate
381 the buffer with a gap large enough to accomodate the new text and a
382 gap of mPreferredGapSize */
383 if (copiedLength
> mGapEnd
- mGapStart
)
384 reallocate_with_gap(toPos
, copiedLength
+ mPreferredGapSize
);
385 else if (toPos
!= mGapStart
)
388 /* Insert the new text (toPos now corresponds to the start of the gap) */
389 if (fromEnd
<= fromBuf
->mGapStart
) {
390 memcpy(&mBuf
[toPos
], &fromBuf
->mBuf
[fromStart
], copiedLength
);
391 } else if (fromStart
>= fromBuf
->mGapStart
) {
393 &fromBuf
->mBuf
[fromStart
+ (fromBuf
->mGapEnd
- fromBuf
->mGapStart
)],
396 int part1Length
= fromBuf
->mGapStart
- fromStart
;
397 memcpy(&mBuf
[toPos
], &fromBuf
->mBuf
[fromStart
], part1Length
);
398 memcpy(&mBuf
[toPos
+ part1Length
],
399 &fromBuf
->mBuf
[fromBuf
->mGapEnd
], copiedLength
- part1Length
);
401 mGapStart
+= copiedLength
;
402 mLength
+= copiedLength
;
403 update_selections(toPos
, 0, copiedLength
);
408 Take the previous changes and undo them. Return the previous
409 cursor position in cursorPos. Returns 1 if the undo was applied.
410 CursorPos will be at a character boundary.
412 int Fl_Text_Buffer::undo(int *cursorPos
)
414 if (undowidget
!= this || (!undocut
&& !undoinsert
&& !mCanUndo
))
418 int xlen
= undoinsert
;
419 int b
= undoat
- xlen
;
421 if (xlen
&& undoyankcut
&& !ilen
) {
426 undobuffersize(ilen
+ 1);
427 undobuffer
[ilen
] = 0;
428 char *tmp
= strdup(undobuffer
);
429 replace(b
, undoat
, tmp
);
431 *cursorPos
= mCursorPosHint
;
436 *cursorPos
= mCursorPosHint
;
438 undobuffersize(ilen
+ 1);
439 undobuffer
[ilen
] = 0;
440 insert(undoat
, undobuffer
);
442 *cursorPos
= mCursorPosHint
;
451 Set a flag if undo function will work.
453 void Fl_Text_Buffer::canUndo(char flag
)
456 // disabling undo also clears the last undo operation!
457 if (!mCanUndo
&& undowidget
==this)
463 Change the tab width. This will cause a couple of callbacks and a complete
465 Matt: I am not entirely sure why we need to trigger callbacks because
466 tabs are only a graphical hint, not changing any text at all, but I leave
467 this in here for back compatibility.
469 void Fl_Text_Buffer::tab_distance(int tabDist
)
471 /* First call the pre-delete callbacks with the previous tab setting
473 call_predelete_callbacks(0, mLength
);
475 /* Change the tab setting */
478 /* Force any display routines to redisplay everything (unfortunately,
479 this means copying the whole buffer contents to provide "deletedText" */
480 const char *deletedText
= text();
481 call_modify_callbacks(0, mLength
, mLength
, 0, deletedText
);
482 free((void *) deletedText
);
487 Select a range of text.
488 Start and End must be at a character boundary.
490 void Fl_Text_Buffer::select(int start
, int end
)
492 IS_UTF8_ALIGNED2(this, (start
))
493 IS_UTF8_ALIGNED2(this, (end
))
495 Fl_Text_Selection oldSelection
= mPrimary
;
497 mPrimary
.set(start
, end
);
498 redisplay_selection(&oldSelection
, &mPrimary
);
503 Clear the primary selection.
505 void Fl_Text_Buffer::unselect()
507 Fl_Text_Selection oldSelection
= mPrimary
;
509 mPrimary
.mSelected
= 0;
510 redisplay_selection(&oldSelection
, &mPrimary
);
515 Return the primary selection range.
517 int Fl_Text_Buffer::selection_position(int *start
, int *end
)
519 return mPrimary
.position(start
, end
);
524 Return a copy of the selected text.
526 char *Fl_Text_Buffer::selection_text()
528 return selection_text_(&mPrimary
);
533 Remove the selected text.
535 void Fl_Text_Buffer::remove_selection()
537 remove_selection_(&mPrimary
);
542 Replace the selected text.
544 void Fl_Text_Buffer::replace_selection(const char *text
)
546 replace_selection_(&mPrimary
, text
);
552 Start and End must be at a character boundary.
554 void Fl_Text_Buffer::secondary_select(int start
, int end
)
556 Fl_Text_Selection oldSelection
= mSecondary
;
558 mSecondary
.set(start
, end
);
559 redisplay_selection(&oldSelection
, &mSecondary
);
566 void Fl_Text_Buffer::secondary_unselect()
568 Fl_Text_Selection oldSelection
= mSecondary
;
570 mSecondary
.mSelected
= 0;
571 redisplay_selection(&oldSelection
, &mSecondary
);
576 Return the selected range.
578 int Fl_Text_Buffer::secondary_selection_position(int *start
, int *end
)
580 return mSecondary
.position(start
, end
);
585 Return a copy of the text in this selection.
587 char *Fl_Text_Buffer::secondary_selection_text()
589 return selection_text_(&mSecondary
);
594 Remove the selected text.
596 void Fl_Text_Buffer::remove_secondary_selection()
598 remove_selection_(&mSecondary
);
603 Replace selected text.
605 void Fl_Text_Buffer::replace_secondary_selection(const char *text
)
607 replace_selection_(&mSecondary
, text
);
612 Highlight a range of text.
613 Start and End must be at a character boundary.
615 void Fl_Text_Buffer::highlight(int start
, int end
)
617 Fl_Text_Selection oldSelection
= mHighlight
;
619 mHighlight
.set(start
, end
);
620 redisplay_selection(&oldSelection
, &mHighlight
);
625 Remove text highlighting.
627 void Fl_Text_Buffer::unhighlight()
629 Fl_Text_Selection oldSelection
= mHighlight
;
631 mHighlight
.mSelected
= 0;
632 redisplay_selection(&oldSelection
, &mHighlight
);
637 Return position of highlight.
639 int Fl_Text_Buffer::highlight_position(int *start
, int *end
)
641 return mHighlight
.position(start
, end
);
646 Return a copy of highlighted text.
648 char *Fl_Text_Buffer::highlight_text()
650 return selection_text_(&mHighlight
);
655 Add a callback that is called whenever text is modified.
657 void Fl_Text_Buffer::add_modify_callback(Fl_Text_Modify_Cb bufModifiedCB
,
660 Fl_Text_Modify_Cb
*newModifyProcs
=
661 new Fl_Text_Modify_Cb
[mNModifyProcs
+ 1];
662 void **newCBArgs
= new void *[mNModifyProcs
+ 1];
663 for (int i
= 0; i
< mNModifyProcs
; i
++) {
664 newModifyProcs
[i
+ 1] = mModifyProcs
[i
];
665 newCBArgs
[i
+ 1] = mCbArgs
[i
];
667 if (mNModifyProcs
!= 0) {
668 delete[]mModifyProcs
;
671 newModifyProcs
[0] = bufModifiedCB
;
672 newCBArgs
[0] = cbArg
;
674 mModifyProcs
= newModifyProcs
;
682 void Fl_Text_Buffer::remove_modify_callback(Fl_Text_Modify_Cb bufModifiedCB
,
685 int i
, toRemove
= -1;
687 /* find the matching callback to remove */
688 for (i
= 0; i
< mNModifyProcs
; i
++) {
689 if (mModifyProcs
[i
] == bufModifiedCB
&& mCbArgs
[i
] == cbArg
) {
694 if (toRemove
== -1) {
696 ("Fl_Text_Buffer::remove_modify_callback(): Can't find modify CB to remove");
700 /* Allocate new lists for remaining callback procs and args (if
703 if (mNModifyProcs
== 0) {
705 delete[]mModifyProcs
;
711 Fl_Text_Modify_Cb
*newModifyProcs
= new Fl_Text_Modify_Cb
[mNModifyProcs
];
712 void **newCBArgs
= new void *[mNModifyProcs
];
714 /* copy out the remaining members and free the old lists */
715 for (i
= 0; i
< toRemove
; i
++) {
716 newModifyProcs
[i
] = mModifyProcs
[i
];
717 newCBArgs
[i
] = mCbArgs
[i
];
719 for (; i
< mNModifyProcs
; i
++) {
720 newModifyProcs
[i
] = mModifyProcs
[i
+ 1];
721 newCBArgs
[i
] = mCbArgs
[i
+ 1];
723 delete[]mModifyProcs
;
725 mModifyProcs
= newModifyProcs
;
731 Add a callback that is called before deleting text.
733 void Fl_Text_Buffer::add_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB
,
736 Fl_Text_Predelete_Cb
*newPreDeleteProcs
=
737 new Fl_Text_Predelete_Cb
[mNPredeleteProcs
+ 1];
738 void **newCBArgs
= new void *[mNPredeleteProcs
+ 1];
739 for (int i
= 0; i
< mNPredeleteProcs
; i
++) {
740 newPreDeleteProcs
[i
+ 1] = mPredeleteProcs
[i
];
741 newCBArgs
[i
+ 1] = mPredeleteCbArgs
[i
];
743 if (!mNPredeleteProcs
!= 0) {
744 delete[]mPredeleteProcs
;
745 delete[]mPredeleteCbArgs
;
747 newPreDeleteProcs
[0] = bufPreDeleteCB
;
748 newCBArgs
[0] = cbArg
;
750 mPredeleteProcs
= newPreDeleteProcs
;
751 mPredeleteCbArgs
= newCBArgs
;
758 void Fl_Text_Buffer::remove_predelete_callback(Fl_Text_Predelete_Cb bufPreDeleteCB
, void *cbArg
)
760 int i
, toRemove
= -1;
761 /* find the matching callback to remove */
762 for (i
= 0; i
< mNPredeleteProcs
; i
++) {
763 if (mPredeleteProcs
[i
] == bufPreDeleteCB
&&
764 mPredeleteCbArgs
[i
] == cbArg
) {
769 if (toRemove
== -1) {
771 ("Fl_Text_Buffer::remove_predelete_callback(): Can't find pre-delete CB to remove");
775 /* Allocate new lists for remaining callback procs and args (if
778 if (mNPredeleteProcs
== 0) {
779 mNPredeleteProcs
= 0;
780 delete[]mPredeleteProcs
;
781 mPredeleteProcs
= NULL
;
782 delete[]mPredeleteCbArgs
;
783 mPredeleteCbArgs
= NULL
;
786 Fl_Text_Predelete_Cb
*newPreDeleteProcs
=
787 new Fl_Text_Predelete_Cb
[mNPredeleteProcs
];
788 void **newCBArgs
= new void *[mNPredeleteProcs
];
790 /* copy out the remaining members and free the old lists */
791 for (i
= 0; i
< toRemove
; i
++) {
792 newPreDeleteProcs
[i
] = mPredeleteProcs
[i
];
793 newCBArgs
[i
] = mPredeleteCbArgs
[i
];
795 for (; i
< mNPredeleteProcs
; i
++) {
796 newPreDeleteProcs
[i
] = mPredeleteProcs
[i
+ 1];
797 newCBArgs
[i
] = mPredeleteCbArgs
[i
+ 1];
799 delete[]mPredeleteProcs
;
800 delete[]mPredeleteCbArgs
;
801 mPredeleteProcs
= newPreDeleteProcs
;
802 mPredeleteCbArgs
= newCBArgs
;
807 Return a copy of the line that contains a given index.
808 Pos must be at a character boundary.
810 char *Fl_Text_Buffer::line_text(int pos
) const {
811 return text_range(line_start(pos
), line_end(pos
));
816 Find the beginning of the line.
818 int Fl_Text_Buffer::line_start(int pos
) const
820 if (!findchar_backward(pos
, '\n', &pos
))
827 Find the end of the line.
829 int Fl_Text_Buffer::line_end(int pos
) const {
830 if (!findchar_forward(pos
, '\n', &pos
))
837 Find the beginning of a word.
840 int Fl_Text_Buffer::word_start(int pos
) const {
841 // FIXME: character is ucs-4
842 while (pos
>0 && (isalnum(char_at(pos
)) || char_at(pos
) == '_')) {
843 pos
= prev_char(pos
);
845 // FIXME: character is ucs-4
846 if (!(isalnum(char_at(pos
)) || char_at(pos
) == '_'))
847 pos
= next_char(pos
);
853 Find the end of a word.
856 int Fl_Text_Buffer::word_end(int pos
) const {
857 // FIXME: character is ucs-4
858 while (pos
< length() && (isalnum(char_at(pos
)) || char_at(pos
) == '_'))
860 pos
= next_char(pos
);
867 Count the number of characters between two positions.
869 int Fl_Text_Buffer::count_displayed_characters(int lineStartPos
,
872 IS_UTF8_ALIGNED2(this, (lineStartPos
))
873 IS_UTF8_ALIGNED2(this, (targetPos
))
877 int pos
= lineStartPos
;
878 while (pos
< targetPos
) {
879 pos
= next_char(pos
);
887 Skip ahead a number of characters from a given index.
888 This function breaks early if it encounters a newline character.
890 int Fl_Text_Buffer::skip_displayed_characters(int lineStartPos
, int nChars
)
892 IS_UTF8_ALIGNED2(this, (lineStartPos
))
894 int pos
= lineStartPos
;
896 for (int charCount
= 0; charCount
< nChars
&& pos
< mLength
; charCount
++) {
897 unsigned int c
= char_at(pos
);
900 pos
= next_char(pos
);
907 Count the number of newline characters between start and end.
908 startPos and endPos must be at a character boundary.
909 This function is optimized for speed by not using UTF-8 calls.
911 int Fl_Text_Buffer::count_lines(int startPos
, int endPos
) const {
912 IS_UTF8_ALIGNED2(this, (startPos
))
913 IS_UTF8_ALIGNED2(this, (endPos
))
915 int gapLen
= mGapEnd
- mGapStart
;
919 while (pos
< mGapStart
)
923 if (mBuf
[pos
++] == '\n')
926 while (pos
< mLength
) {
929 if (mBuf
[pos
++ + gapLen
] == '\n')
937 Skip to the first character, n lines ahead.
938 StartPos must be at a character boundary.
939 This function is optimized for speed by not using UTF-8 calls.
941 int Fl_Text_Buffer::skip_lines(int startPos
, int nLines
)
943 IS_UTF8_ALIGNED2(this, (startPos
))
948 int gapLen
= mGapEnd
- mGapStart
;
951 while (pos
< mGapStart
) {
952 if (mBuf
[pos
++] == '\n') {
954 if (lineCount
== nLines
) {
955 IS_UTF8_ALIGNED2(this, (pos
))
960 while (pos
< mLength
) {
961 if (mBuf
[pos
++ + gapLen
] == '\n') {
963 if (lineCount
>= nLines
) {
964 IS_UTF8_ALIGNED2(this, (pos
))
969 IS_UTF8_ALIGNED2(this, (pos
))
975 Skip to the first character, n lines back.
976 StartPos must be at a character boundary.
977 This function is optimized for speed by not using UTF-8 calls.
979 int Fl_Text_Buffer::rewind_lines(int startPos
, int nLines
)
981 IS_UTF8_ALIGNED2(this, (startPos
))
983 int pos
= startPos
- 1;
987 int gapLen
= mGapEnd
- mGapStart
;
989 while (pos
>= mGapStart
) {
990 if (mBuf
[pos
+ gapLen
] == '\n') {
991 if (++lineCount
>= nLines
) {
992 IS_UTF8_ALIGNED2(this, (pos
+1))
999 if (mBuf
[pos
] == '\n') {
1000 if (++lineCount
>= nLines
) {
1001 IS_UTF8_ALIGNED2(this, (pos
+1))
1012 Find a matching string in the buffer.
1014 int Fl_Text_Buffer::search_forward(int startPos
, const char *searchString
,
1015 int *foundPos
, int matchCase
) const
1017 IS_UTF8_ALIGNED2(this, (startPos
))
1018 IS_UTF8_ALIGNED(searchString
)
1025 while (startPos
< length()) {
1030 // we reached the end of the "needle", so we found the string!
1032 *foundPos
= startPos
;
1035 int l
= fl_utf8len1(c
);
1036 if (memcmp(sp
, address(bp
), l
))
1040 startPos
= next_char(startPos
);
1043 while (startPos
< length()) {
1047 // we reached the end of the "needle", so we found the string!
1049 *foundPos
= startPos
;
1053 unsigned int b
= char_at(bp
);
1054 unsigned int s
= fl_utf8decode(sp
, 0, &l
);
1055 if (fl_tolower(b
)!=fl_tolower(s
))
1060 startPos
= next_char(startPos
);
1066 int Fl_Text_Buffer::search_backward(int startPos
, const char *searchString
,
1067 int *foundPos
, int matchCase
) const
1069 IS_UTF8_ALIGNED2(this, (startPos
))
1070 IS_UTF8_ALIGNED(searchString
)
1077 while (startPos
>= 0) {
1082 // we reached the end of the "needle", so we found the string!
1084 *foundPos
= startPos
;
1087 int l
= fl_utf8len1(c
);
1088 if (memcmp(sp
, address(bp
), l
))
1092 startPos
= prev_char(startPos
);
1095 while (startPos
>= 0) {
1099 // we reached the end of the "needle", so we found the string!
1101 *foundPos
= startPos
;
1105 unsigned int b
= char_at(bp
);
1106 unsigned int s
= fl_utf8decode(sp
, 0, &l
);
1107 if (fl_tolower(b
)!=fl_tolower(s
))
1112 startPos
= prev_char(startPos
);
1121 Insert a string into the buffer.
1122 Pos must be at a character boundary. Text must be a correct UTF-8 string.
1124 int Fl_Text_Buffer::insert_(int pos
, const char *text
)
1126 if (!text
|| !*text
)
1129 int insertedLength
= strlen(text
);
1131 /* Prepare the buffer to receive the new text. If the new text fits in
1132 the current buffer, just move the gap (if necessary) to where
1133 the text should be inserted. If the new text is too large, reallocate
1134 the buffer with a gap large enough to accomodate the new text and a
1135 gap of mPreferredGapSize */
1136 if (insertedLength
> mGapEnd
- mGapStart
)
1137 reallocate_with_gap(pos
, insertedLength
+ mPreferredGapSize
);
1138 else if (pos
!= mGapStart
)
1141 /* Insert the new text (pos now corresponds to the start of the gap) */
1142 memcpy(&mBuf
[pos
], text
, insertedLength
);
1143 mGapStart
+= insertedLength
;
1144 mLength
+= insertedLength
;
1145 update_selections(pos
, 0, insertedLength
);
1148 if (undowidget
== this && undoat
== pos
&& undoinsert
) {
1149 undoinsert
+= insertedLength
;
1151 undoinsert
= insertedLength
;
1152 undoyankcut
= (undoat
== pos
) ? undocut
: 0;
1154 undoat
= pos
+ insertedLength
;
1159 return insertedLength
;
1164 Remove a string from the buffer.
1165 Unicode safe. Start and end must be at a character boundary.
1167 void Fl_Text_Buffer::remove_(int start
, int end
)
1169 /* if the gap is not contiguous to the area to remove, move it there */
1172 if (undowidget
== this && undoat
== end
&& undocut
) {
1173 undobuffersize(undocut
+ end
- start
+ 1);
1174 memmove(undobuffer
+ end
- start
, undobuffer
, undocut
);
1175 undocut
+= end
- start
;
1177 undocut
= end
- start
;
1178 undobuffersize(undocut
);
1186 if (start
> mGapStart
) {
1188 memcpy(undobuffer
, mBuf
+ (mGapEnd
- mGapStart
) + start
,
1191 } else if (end
< mGapStart
) {
1193 memcpy(undobuffer
, mBuf
+ start
, end
- start
);
1196 int prelen
= mGapStart
- start
;
1198 memcpy(undobuffer
, mBuf
+ start
, prelen
);
1199 memcpy(undobuffer
+ prelen
, mBuf
+ mGapEnd
, end
- start
- prelen
);
1203 /* expand the gap to encompass the deleted characters */
1204 mGapEnd
+= end
- mGapStart
;
1205 mGapStart
-= mGapStart
- start
;
1207 /* update the length */
1208 mLength
-= end
- start
;
1210 /* fix up any selections which might be affected by the change */
1211 update_selections(start
, end
- start
, 0);
1217 Unicode safe. Start and end must be at a character boundary.
1219 void Fl_Text_Selection::set(int startpos
, int endpos
)
1221 mSelected
= startpos
!= endpos
;
1222 mStart
= min(startpos
, endpos
);
1223 mEnd
= max(startpos
, endpos
);
1229 Unicode safe. Start and end will be at a character boundary.
1231 int Fl_Text_Selection::position(int *startpos
, int *endpos
) const {
1242 Return if a position is inside the selected area.
1243 Unicode safe. Pos must be at a character boundary.
1245 int Fl_Text_Selection::includes(int pos
) const {
1246 return (selected() && pos
>= start() && pos
< end() );
1251 Return a duplicate of the selected text, or an empty string.
1254 char *Fl_Text_Buffer::selection_text_(Fl_Text_Selection
* sel
) const {
1257 /* If there's no selection, return an allocated empty string */
1258 if (!sel
->position(&start
, &end
))
1260 char *s
= (char *) malloc(1);
1265 /* Return the selected range */
1266 return text_range(start
, end
);
1271 Remove the selected text.
1274 void Fl_Text_Buffer::remove_selection_(Fl_Text_Selection
* sel
)
1278 if (!sel
->position(&start
, &end
))
1281 //undoyankcut = undocut;
1286 Replace selection with text.
1289 void Fl_Text_Buffer::replace_selection_(Fl_Text_Selection
* sel
,
1292 Fl_Text_Selection oldSelection
= *sel
;
1294 /* If there's no selection, return */
1296 if (!sel
->position(&start
, &end
))
1299 /* Do the appropriate type of replace */
1300 replace(start
, end
, text
);
1302 /* Unselect (happens automatically in BufReplace, but BufReplaceRect
1303 can't detect when the contents of a selection goes away) */
1305 redisplay_selection(&oldSelection
, sel
);
1313 void Fl_Text_Buffer::call_modify_callbacks(int pos
, int nDeleted
,
1314 int nInserted
, int nRestyled
,
1315 const char *deletedText
) const {
1316 IS_UTF8_ALIGNED2(this, pos
)
1317 for (int i
= 0; i
< mNModifyProcs
; i
++)
1318 (*mModifyProcs
[i
]) (pos
, nInserted
, nDeleted
, nRestyled
,
1319 deletedText
, mCbArgs
[i
]);
1327 void Fl_Text_Buffer::call_predelete_callbacks(int pos
, int nDeleted
) const {
1328 for (int i
= 0; i
< mNPredeleteProcs
; i
++)
1329 (*mPredeleteProcs
[i
]) (pos
, nDeleted
, mPredeleteCbArgs
[i
]);
1334 Redisplay a new selected area.
1337 void Fl_Text_Buffer::redisplay_selection(Fl_Text_Selection
*
1342 int oldStart
, oldEnd
, newStart
, newEnd
, ch1Start
, ch1End
, ch2Start
,
1345 /* If either selection is rectangular, add an additional character to
1346 the end of the selection to request the redraw routines to wipe out
1347 the parts of the selection beyond the end of the line */
1348 oldStart
= oldSelection
->mStart
;
1349 newStart
= newSelection
->mStart
;
1350 oldEnd
= oldSelection
->mEnd
;
1351 newEnd
= newSelection
->mEnd
;
1353 /* If the old or new selection is unselected, just redisplay the
1354 single area that is (was) selected and return */
1355 if (!oldSelection
->mSelected
&& !newSelection
->mSelected
)
1357 if (!oldSelection
->mSelected
)
1359 call_modify_callbacks(newStart
, 0, 0, newEnd
- newStart
, NULL
);
1362 if (!newSelection
->mSelected
) {
1363 call_modify_callbacks(oldStart
, 0, 0, oldEnd
- oldStart
, NULL
);
1367 /* If the selections are non-contiguous, do two separate updates
1369 if (oldEnd
< newStart
|| newEnd
< oldStart
) {
1370 call_modify_callbacks(oldStart
, 0, 0, oldEnd
- oldStart
, NULL
);
1371 call_modify_callbacks(newStart
, 0, 0, newEnd
- newStart
, NULL
);
1375 /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
1376 changed areas), and the unchanged area of their intersection,
1377 and update only the changed area(s) */
1378 ch1Start
= min(oldStart
, newStart
);
1379 ch2End
= max(oldEnd
, newEnd
);
1380 ch1End
= max(oldStart
, newStart
);
1381 ch2Start
= min(oldEnd
, newEnd
);
1382 if (ch1Start
!= ch1End
)
1383 call_modify_callbacks(ch1Start
, 0, 0, ch1End
- ch1Start
, NULL
);
1384 if (ch2Start
!= ch2End
)
1385 call_modify_callbacks(ch2Start
, 0, 0, ch2End
- ch2Start
, NULL
);
1390 Move the gap around without changing buffer content.
1391 Unicode safe. Pos must be at a character boundary.
1393 void Fl_Text_Buffer::move_gap(int pos
)
1395 int gapLen
= mGapEnd
- mGapStart
;
1397 if (pos
> mGapStart
)
1398 memmove(&mBuf
[mGapStart
], &mBuf
[mGapEnd
], pos
- mGapStart
);
1400 memmove(&mBuf
[pos
+ gapLen
], &mBuf
[pos
], mGapStart
- pos
);
1401 mGapEnd
+= pos
- mGapStart
;
1402 mGapStart
+= pos
- mGapStart
;
1407 Create a larger gap.
1408 Unicode safe. Start must be at a character boundary.
1410 void Fl_Text_Buffer::reallocate_with_gap(int newGapStart
, int newGapLen
)
1412 char *newBuf
= (char *) malloc(mLength
+ newGapLen
);
1413 int newGapEnd
= newGapStart
+ newGapLen
;
1415 if (newGapStart
<= mGapStart
) {
1416 memcpy(newBuf
, mBuf
, newGapStart
);
1417 memcpy(&newBuf
[newGapEnd
], &mBuf
[newGapStart
],
1418 mGapStart
- newGapStart
);
1419 memcpy(&newBuf
[newGapEnd
+ mGapStart
- newGapStart
],
1420 &mBuf
[mGapEnd
], mLength
- mGapStart
);
1421 } else { /* newGapStart > mGapStart */
1422 memcpy(newBuf
, mBuf
, mGapStart
);
1423 memcpy(&newBuf
[mGapStart
], &mBuf
[mGapEnd
], newGapStart
- mGapStart
);
1424 memcpy(&newBuf
[newGapEnd
],
1425 &mBuf
[mGapEnd
+ newGapStart
- mGapStart
],
1426 mLength
- newGapStart
);
1428 free((void *) mBuf
);
1430 mGapStart
= newGapStart
;
1431 mGapEnd
= newGapEnd
;
1436 Update selection range if characters were inserted.
1437 Unicode safe. Pos must be at a character boundary.
1439 void Fl_Text_Buffer::update_selections(int pos
, int nDeleted
,
1442 mPrimary
.update(pos
, nDeleted
, nInserted
);
1443 mSecondary
.update(pos
, nDeleted
, nInserted
);
1444 mHighlight
.update(pos
, nDeleted
, nInserted
);
1448 // unicode safe, assuming the arguments are on character boundaries
1449 void Fl_Text_Selection::update(int pos
, int nDeleted
, int nInserted
)
1451 if (!mSelected
|| pos
> mEnd
)
1453 if (pos
+ nDeleted
<= mStart
) {
1454 mStart
+= nInserted
- nDeleted
;
1455 mEnd
+= nInserted
- nDeleted
;
1456 } else if (pos
<= mStart
&& pos
+ nDeleted
>= mEnd
) {
1460 } else if (pos
<= mStart
&& pos
+ nDeleted
< mEnd
) {
1462 mEnd
= nInserted
+ mEnd
- nDeleted
;
1463 } else if (pos
< mEnd
) {
1464 mEnd
+= nInserted
- nDeleted
;
1472 Find a UCS-4 character.
1473 StartPos must be at a character boundary, searchChar is UCS-4 encoded.
1475 int Fl_Text_Buffer::findchar_forward(int startPos
, unsigned searchChar
,
1476 int *foundPos
) const
1478 if (startPos
>= mLength
) {
1479 *foundPos
= mLength
;
1486 for ( ; startPos
<mLength
; startPos
= next_char(startPos
)) {
1487 if (searchChar
== char_at(startPos
)) {
1488 *foundPos
= startPos
;
1493 *foundPos
= mLength
;
1499 Find a UCS-4 character.
1500 StartPos must be at a character boundary, searchChar is UCS-4 encoded.
1502 int Fl_Text_Buffer::findchar_backward(int startPos
, unsigned int searchChar
,
1503 int *foundPos
) const {
1504 if (startPos
<= 0) {
1509 if (startPos
> mLength
)
1512 for (startPos
= prev_char(startPos
); startPos
>=0; startPos
= prev_char(startPos
)) {
1513 if (searchChar
== char_at(startPos
)) {
1514 *foundPos
= startPos
;
1523 //#define EXAMPLE_ENCODING // shows how to process any encoding for which a decoding function exists
1524 #ifdef EXAMPLE_ENCODING
1526 // returns the UCS equivalent of *p in CP1252 and advances p by 1
1527 unsigned cp1252toucs(char* &p
)
1529 // Codes 0x80..0x9f from the Microsoft CP1252 character set, translated
1531 static unsigned cp1252
[32] = {
1532 0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021,
1533 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
1534 0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014,
1535 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178
1537 unsigned char uc
= *(unsigned char*)p
;
1539 return (uc
< 0x80 || uc
>= 0xa0 ? uc
: cp1252
[uc
- 0x80]);
1542 // returns the UCS equivalent of *p in UTF-16 and advances p by 2 (or more for surrogates)
1543 unsigned utf16toucs(char* &p
)
1547 struct { unsigned char a
, b
;} chars
;
1549 struct { unsigned char b
, a
;} chars
;
1553 u
.chars
.a
= *(unsigned char*)p
++;
1554 u
.chars
.b
= *(unsigned char*)p
++;
1558 // filter that produces, from an input stream fed by reading from fp,
1559 // a UTF-8-encoded output stream written in buffer.
1560 // Input can be any (e.g., 8-bit, UTF-16) encoding.
1561 // Output is true UTF-8.
1562 // p_trf points to a function that transforms encoded byte(s) into one UCS
1563 // and that increases the pointer by the adequate quantity
1564 static int general_input_filter(char *buffer
, int buflen
,
1565 char *line
, int sline
, char* &endline
,
1566 unsigned (*p_trf
)(char* &),
1569 char *p
, *q
, multibyte
[5];
1573 while (q
< buffer
+ buflen
) {
1575 r
= fread(line
, 1, sline
, fp
);
1577 if (r
== 0) return q
- buffer
;
1580 if (q
+ 4 /*max width of utf-8 char*/ > buffer
+ buflen
) {
1581 memmove(line
, p
, endline
- p
);
1582 endline
-= (p
- line
);
1585 lq
= fl_utf8encode( p_trf(p
), multibyte
);
1586 memcpy(q
, multibyte
, lq
);
1589 memmove(line
, p
, endline
- p
);
1590 endline
-= (p
- line
);
1593 #endif // EXAMPLE_ENCODING
1596 filter that produces, from an input stream fed by reading from fp,
1597 a UTF-8-encoded output stream written in buffer.
1598 Input can be UTF-8. If it is not, it is decoded with CP1252.
1600 *input_was_changed is set to true if the input was not strict UTF-8 so output
1603 static int utf8_input_filter(char *buffer
, int buflen
, char *line
, int sline
, char* &endline
,
1604 FILE *fp
, int *input_was_changed
)
1606 char *p
, *q
, multibyte
[5];
1611 while (q
< buffer
+ buflen
) {
1613 r
= fread(line
, 1, sline
, fp
);
1615 if (r
== 0) return q
- buffer
;
1618 l
= fl_utf8len1(*p
);
1619 if (p
+ l
> endline
) {
1620 memmove(line
, p
, endline
- p
);
1621 endline
-= (p
- line
);
1622 r
= fread(endline
, 1, sline
- (endline
- line
), fp
);
1625 if (endline
- line
< l
) break;
1628 u
= fl_utf8decode(p
, p
+l
, &lp
);
1629 lq
= fl_utf8encode(u
, multibyte
);
1630 if (lp
!= l
|| lq
!= l
) *input_was_changed
= true;
1631 if (q
+ lq
> buffer
+ buflen
) {
1632 memmove(line
, p
, endline
- p
);
1633 endline
-= (p
- line
);
1636 memcpy(q
, multibyte
, lq
);
1642 memmove(line
, p
, endline
- p
);
1643 endline
-= (p
- line
);
1647 const char *Fl_Text_Buffer::file_encoding_warning_message
=
1648 "Displayed text contains the UTF-8 transcoding\n"
1649 "of the input file which was not UTF-8 encoded.\n"
1650 "Some changes may have occurred.";
1653 Insert text from a file.
1654 Input file can be of various encodings according to what input fiter is used.
1655 utf8_input_filter accepts UTF-8 or CP1252 as input encoding.
1656 Output is always UTF-8.
1658 int Fl_Text_Buffer::insertfile(const char *file
, int pos
, int buflen
)
1661 if (!(fp
= fl_fopen(file
, "r")))
1663 char *buffer
= new char[buflen
+ 1];
1664 char *endline
, line
[100];
1666 input_file_was_transcoded
= false;
1669 #ifdef EXAMPLE_ENCODING
1670 // example of 16-bit encoding: UTF-16
1671 l
= general_input_filter(buffer
, buflen
,
1672 line
, sizeof(line
), endline
,
1673 utf16toucs
, // use cp1252toucs to read CP1252-encoded files
1675 input_file_was_transcoded
= true;
1677 l
= utf8_input_filter(buffer
, buflen
, line
, sizeof(line
), endline
,
1678 fp
, &input_file_was_transcoded
);
1682 insert(pos
, buffer
);
1685 int e
= ferror(fp
) ? 2 : 0;
1688 if ( (!e
) && input_file_was_transcoded
&& transcoding_warning_action
) {
1689 transcoding_warning_action(this);
1699 int Fl_Text_Buffer::outputfile(const char *file
,
1703 if (!(fp
= fl_fopen(file
, "w")))
1705 for (int n
; (n
= min(end
- start
, buflen
)); start
+= n
) {
1706 const char *p
= text_range(start
, start
+ n
);
1707 int r
= fwrite(p
, 1, n
, fp
);
1713 int e
= ferror(fp
) ? 2 : 0;
1720 Return the previous character position.
1723 int Fl_Text_Buffer::prev_char_clipped(int pos
) const
1728 IS_UTF8_ALIGNED2(this, (pos
))
1736 } while ( (c
&0xc0) == 0x80);
1738 IS_UTF8_ALIGNED2(this, (pos
))
1744 Return the previous character position.
1745 Returns -1 if the beginning of the buffer is reached.
1747 int Fl_Text_Buffer::prev_char(int pos
) const
1749 if (pos
==0) return -1;
1750 return prev_char_clipped(pos
);
1755 Return the next character position.
1756 Returns length() if the end of the buffer is reached.
1758 int Fl_Text_Buffer::next_char(int pos
) const
1760 IS_UTF8_ALIGNED2(this, (pos
))
1761 int n
= fl_utf8len1(byte_at(pos
));
1765 IS_UTF8_ALIGNED2(this, (pos
))
1771 Return the next character position.
1772 If the end of the buffer is reached, it returns the current position.
1774 int Fl_Text_Buffer::next_char_clipped(int pos
) const
1776 return next_char(pos
);
1780 Align an index to the current UTF-8 boundary.
1782 int Fl_Text_Buffer::utf8_align(int pos
) const
1784 char c
= byte_at(pos
);
1785 while ( (c
&0xc0) == 0x80) {
1793 // End of "$Id: Fl_Text_Buffer.cxx 8040 2010-12-15 17:38:39Z manolo $".