summaryrefslogtreecommitdiff
path: root/src/Fl_Text_Buffer.cxx
diff options
context:
space:
mode:
authorNo Author <No Author>2001-08-01 21:24:49 +0000
committerNo Author <No Author>2001-08-01 21:24:49 +0000
commit3cb5ebe0e811f3db008085d985b7761725589a74 (patch)
tree0a7184a5f02fffe927af911758f3a9a4a2f4a37e /src/Fl_Text_Buffer.cxx
parent4477e166400f197bed50b09e01e695221cde96b6 (diff)
This commit was manufactured by cvs2svn to create branch 'branch-1.1'.
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.1@1513 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
Diffstat (limited to 'src/Fl_Text_Buffer.cxx')
-rw-r--r--src/Fl_Text_Buffer.cxx2287
1 files changed, 2287 insertions, 0 deletions
diff --git a/src/Fl_Text_Buffer.cxx b/src/Fl_Text_Buffer.cxx
new file mode 100644
index 000000000..70b2e7db0
--- /dev/null
+++ b/src/Fl_Text_Buffer.cxx
@@ -0,0 +1,2287 @@
+//
+// "$Id: Fl_Text_Buffer.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $"
+//
+// Copyright Mark Edel. Permission to distribute under the LGPL for
+// the FLTK library granted by Mark Edel.
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Library General Public
+// License as published by the Free Software Foundation; either
+// version 2 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Library General Public License for more details.
+//
+// You should have received a copy of the GNU Library General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+// USA.
+//
+// Please report all bugs and problems to "fltk-bugs@fltk.org".
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <fltk/Fl_Text_Buffer.h>
+
+
+#define PREFERRED_GAP_SIZE 80
+/* Initial size for the buffer gap (empty space
+in the buffer where text might be inserted
+if the user is typing sequential chars ) */
+
+static void histogramCharacters( const char *string, int length, char hist[ 256 ],
+ int init );
+static void subsChars( char *string, int length, char fromChar, char toChar );
+static char chooseNullSubsChar( char hist[ 256 ] );
+static void insertColInLine( const char *line, char *insLine, int column, int insWidth,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset );
+static void deleteRectFromLine( const char *line, int rectStart, int rectEnd,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset );
+static void overlayRectInLine( const char *line, char *insLine, int rectStart,
+ int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
+ int *outLen, int *endOffset );
+
+static void addPadding( char *string, int startIndent, int toIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *charsAdded );
+static char *copyLine( const char* text, int *lineLen );
+static int countLines( const char *string );
+static int textWidth( const char *text, int tabDist, char nullSubsChar );
+static char *realignTabs( const char *text, int origIndent, int newIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *newLength );
+static char *expandTabs( const char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen );
+static char *unexpandTabs( char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen );
+static int max( int i1, int i2 );
+static int min( int i1, int i2 );
+
+static const char *ControlCodeTable[ 32 ] = {
+ "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
+ "bs", "ht", "nl", "vt", "np", "cr", "so", "si",
+ "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
+ "can", "em", "sub", "esc", "fs", "gs", "rs", "us"};
+
+/*
+** Create an empty text buffer of a pre-determined size (use this to
+** avoid unnecessary re-allocation if you know exactly how much the buffer
+** will need to hold
+*/
+Fl_Text_Buffer::Fl_Text_Buffer( int requestedSize ) {
+ mLength = 0;
+ mBuf = (char *)malloc( requestedSize + PREFERRED_GAP_SIZE );
+ mGapStart = 0;
+ mGapEnd = PREFERRED_GAP_SIZE;
+ mTabDist = 8;
+ mUseTabs = 1;
+ mPrimary.mSelected = 0;
+ mPrimary.mRectangular = 0;
+ mPrimary.mStart = mPrimary.mEnd = 0;
+ mSecondary.mSelected = 0;
+ mSecondary.mStart = mSecondary.mEnd = 0;
+ mSecondary.mRectangular = 0;
+ mHighlight.mSelected = 0;
+ mHighlight.mStart = mHighlight.mEnd = 0;
+ mHighlight.mRectangular = 0;
+ mNodifyProcs = NULL;
+ mCbArgs = NULL;
+ mNModifyProcs = 0;
+ mNullSubsChar = '\0';
+#ifdef PURIFY
+{ int i; for (i = mGapStart; i < mGapEnd; i++) mBuf[ i ] = '.'; }
+#endif
+}
+
+/*
+** Free a text buffer
+*/
+Fl_Text_Buffer::~Fl_Text_Buffer() {
+ free( mBuf );
+ if ( mNModifyProcs != 0 ) {
+ free( ( void * ) mNodifyProcs );
+ free( ( void * ) mCbArgs );
+ }
+}
+
+/*
+** Get the entire contents of a text buffer. Memory is allocated to contain
+** the returned string, which the caller must free.
+*/
+const char * Fl_Text_Buffer::text() {
+ char *t;
+
+ t = (char *)malloc( mLength + 1 );
+ memcpy( t, mBuf, mGapStart );
+ memcpy( &t[ mGapStart ], &mBuf[ mGapEnd ],
+ mLength - mGapStart );
+ t[ mLength ] = '\0';
+ return t;
+}
+
+/*
+** Replace the entire contents of the text buffer
+*/
+void Fl_Text_Buffer::text( const char *t ) {
+ int length, deletedLength;
+ const char *deletedText;
+
+ /* Save information for redisplay, and get rid of the old buffer */
+ deletedText = text();
+ deletedLength = mLength;
+ free( (void *)mBuf );
+
+ /* Start a new buffer with a gap of PREFERRED_GAP_SIZE in the center */
+ length = strlen( t );
+ mBuf = (char *)malloc( length + PREFERRED_GAP_SIZE );
+ mLength = length;
+ mGapStart = length / 2;
+ mGapEnd = mGapStart + PREFERRED_GAP_SIZE;
+ memcpy( mBuf, t, mGapStart );
+ memcpy( &mBuf[ mGapEnd ], &t[ mGapStart ], length - mGapStart );
+#ifdef PURIFY
+{ int i; for ( i = mGapStart; i < mGapEnd; i++ ) mBuf[ i ] = '.'; }
+#endif
+
+ /* Zero all of the existing selections */
+ update_selections( 0, deletedLength, 0 );
+
+ /* Call the saved display routine(s) to update the screen */
+ call_modify_callbacks( 0, deletedLength, length, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+/*
+** Return a copy of the text between "start" and "end" character positions
+** from text buffer "buf". Positions start at 0, and the range does not
+** include the character pointed to by "end"
+*/
+const char * Fl_Text_Buffer::text_range( int start, int end ) {
+ char * text;
+ int length, part1Length;
+
+ /* Make sure start and end are ok, and allocate memory for returned string.
+ If start is bad, return "", if end is bad, adjust it. */
+ if ( start < 0 || start > mLength ) {
+ text = (char *)malloc( 1 );
+ text[ 0 ] = '\0';
+ return text;
+ }
+ if ( end < start ) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if ( end > mLength )
+ end = mLength;
+ length = end - start;
+ text = (char *)malloc( length + 1 );
+
+ /* Copy the text from the buffer to the returned string */
+ if ( end <= mGapStart ) {
+ memcpy( text, &mBuf[ start ], length );
+ } else if ( start >= mGapStart ) {
+ memcpy( text, &mBuf[ start + ( mGapEnd - mGapStart ) ], length );
+ } else {
+ part1Length = mGapStart - start;
+ memcpy( text, &mBuf[ start ], part1Length );
+ memcpy( &text[ part1Length ], &mBuf[ mGapEnd ], length - part1Length );
+ }
+ text[ length ] = '\0';
+ return text;
+}
+
+/*
+** Return the character at buffer position "pos". Positions start at 0.
+*/
+char Fl_Text_Buffer::character( int pos ) {
+ if ( pos < 0 || pos > mLength )
+ return '\0';
+ if ( pos < mGapStart )
+ return mBuf[ pos ];
+ else
+ return mBuf[ pos + mGapEnd - mGapStart ];
+}
+
+/*
+** Insert null-terminated string "text" at position "pos" in "buf"
+*/
+void Fl_Text_Buffer::insert( int pos, const char *text ) {
+ int nInserted;
+
+ /* if pos is not contiguous to existing text, make it */
+ if ( pos > mLength ) pos = mLength;
+ if ( pos < 0 ) pos = 0;
+
+ /* insert and redisplay */
+ nInserted = insert_( pos, text );
+ mCursorPosHint = pos + nInserted;
+ call_modify_callbacks( pos, 0, nInserted, 0, NULL );
+}
+
+/*
+** Delete the characters between "start" and "end", and insert the
+** null-terminated string "text" in their place in in "buf"
+*/
+void Fl_Text_Buffer::replace( int start, int end, const char *text ) {
+ const char * deletedText;
+ int nInserted;
+
+ deletedText = text_range( start, end );
+ remove_( start, end );
+ nInserted = insert_( start, text );
+ mCursorPosHint = start + nInserted;
+ call_modify_callbacks( start, end - start, nInserted, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+void Fl_Text_Buffer::remove( int start, int end ) {
+ const char * deletedText;
+
+ /* Make sure the arguments make sense */
+ if ( start > end ) {
+ int temp = start;
+ start = end;
+ end = temp;
+ }
+ if ( start > mLength ) start = mLength;
+ if ( start < 0 ) start = 0;
+ if ( end > mLength ) end = mLength;
+ if ( end < 0 ) end = 0;
+
+ /* Remove and redisplay */
+ deletedText = text_range( start, end );
+ remove_( start, end );
+ mCursorPosHint = start;
+ call_modify_callbacks( start, end - start, 0, 0, deletedText );
+ free( (void *)deletedText );
+}
+
+void Fl_Text_Buffer::copy( Fl_Text_Buffer *fromBuf, int fromStart,
+ int fromEnd, int toPos ) {
+ int length = fromEnd - fromStart;
+ int part1Length;
+
+ /* Prepare the buffer to receive the new text. If the new text fits in
+ the current buffer, just move the gap (if necessary) to where
+ the text should be inserted. If the new text is too large, reallocate
+ the buffer with a gap large enough to accomodate the new text and a
+ gap of PREFERRED_GAP_SIZE */
+ if ( length > mGapEnd - mGapStart )
+ reallocate_with_gap( toPos, length + PREFERRED_GAP_SIZE );
+ else if ( toPos != mGapStart )
+ move_gap( toPos );
+
+ /* Insert the new text (toPos now corresponds to the start of the gap) */
+ if ( fromEnd <= fromBuf->mGapStart ) {
+ memcpy( &mBuf[ toPos ], &fromBuf->mBuf[ fromStart ], length );
+ } else if ( fromStart >= fromBuf->mGapStart ) {
+ memcpy( &mBuf[ toPos ],
+ &fromBuf->mBuf[ fromStart + ( fromBuf->mGapEnd - fromBuf->mGapStart ) ],
+ length );
+ } else {
+ part1Length = fromBuf->mGapStart - fromStart;
+ memcpy( &mBuf[ toPos ], &fromBuf->mBuf[ fromStart ], part1Length );
+ memcpy( &mBuf[ toPos + part1Length ], &fromBuf->mBuf[ fromBuf->mGapEnd ],
+ length - part1Length );
+ }
+ mGapStart += length;
+ mLength += length;
+ update_selections( toPos, 0, length );
+}
+
+/*
+** Insert "text" columnwise into buffer starting at displayed character
+** position "column" on the line beginning at "startPos". Opens a rectangular
+** space the width and height of "text", by moving all text to the right of
+** "column" right. If charsInserted and charsDeleted are not NULL, the
+** number of characters inserted and deleted in the operation (beginning
+** at startPos) are returned in these arguments
+*/
+void Fl_Text_Buffer::insert_column( int column, int startPos, const char *text,
+ int *charsInserted, int *charsDeleted ) {
+ int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
+ const char *deletedText;
+
+ nLines = countLines( text );
+ lineStartPos = line_start( startPos );
+ nDeleted = line_end( skip_lines( startPos, nLines ) ) -
+ lineStartPos;
+ deletedText = text_range( lineStartPos, lineStartPos + nDeleted );
+ insert_column_( column, lineStartPos, text, &insertDeleted, &nInserted,
+ &mCursorPosHint );
+ if ( nDeleted != insertDeleted )
+ fprintf( stderr, "internal consistency check ins1 failed" );
+ call_modify_callbacks( lineStartPos, nDeleted, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( charsInserted != NULL )
+ * charsInserted = nInserted;
+ if ( charsDeleted != NULL )
+ * charsDeleted = nDeleted;
+}
+
+/*
+** Overlay "text" between displayed character positions "rectStart" and
+** "rectEnd" on the line beginning at "startPos". If charsInserted and
+** charsDeleted are not NULL, the number of characters inserted and deleted
+** in the operation (beginning at startPos) are returned in these arguments.
+*/
+void Fl_Text_Buffer::overlay_rectangular( int startPos, int rectStart,
+ int rectEnd, const char *text, int *charsInserted, int *charsDeleted ) {
+ int nLines, lineStartPos, nDeleted, insertDeleted, nInserted;
+ const char *deletedText;
+
+ nLines = countLines( text );
+ lineStartPos = line_start( startPos );
+ nDeleted = line_end( skip_lines( startPos, nLines ) ) -
+ lineStartPos;
+ deletedText = text_range( lineStartPos, lineStartPos + nDeleted );
+ overlay_rectangular_( lineStartPos, rectStart, rectEnd, text, &insertDeleted,
+ &nInserted, &mCursorPosHint );
+ if ( nDeleted != insertDeleted )
+ fprintf( stderr, "internal consistency check ovly1 failed" );
+ call_modify_callbacks( lineStartPos, nDeleted, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( charsInserted != NULL )
+ * charsInserted = nInserted;
+ if ( charsDeleted != NULL )
+ * charsDeleted = nDeleted;
+}
+
+/*
+** Replace a rectangular area in buf, given by "start", "end", "rectStart",
+** and "rectEnd", with "text". If "text" is vertically longer than the
+** rectangle, add extra lines to make room for it.
+*/
+void Fl_Text_Buffer::replace_rectangular( int start, int end, int rectStart,
+ int rectEnd, const char *text ) {
+ char *insPtr;
+ const char *deletedText;
+ char *insText = "";
+ int i, nInsertedLines, nDeletedLines, insLen, hint;
+ int insertDeleted, insertInserted, deleteInserted;
+ int linesPadded = 0;
+
+ /* Make sure start and end refer to complete lines, since the
+ columnar delete and insert operations will replace whole lines */
+ start = line_start( start );
+ end = line_end( end );
+
+ /* If more lines will be deleted than inserted, pad the inserted text
+ with newlines to make it as long as the number of deleted lines. This
+ will indent all of the text to the right of the rectangle to the same
+ column. If more lines will be inserted than deleted, insert extra
+ lines in the buffer at the end of the rectangle to make room for the
+ additional lines in "text" */
+ nInsertedLines = countLines( text );
+ nDeletedLines = count_lines( start, end );
+ if ( nInsertedLines < nDeletedLines ) {
+ insLen = strlen( text );
+ insText = (char *)malloc( insLen + nDeletedLines - nInsertedLines + 1 );
+ strcpy( insText, text );
+ insPtr = insText + insLen;
+ for ( i = 0; i < nDeletedLines - nInsertedLines; i++ )
+ *insPtr++ = '\n';
+ *insPtr = '\0';
+ } else if ( nDeletedLines < nInsertedLines ) {
+ linesPadded = nInsertedLines - nDeletedLines;
+ for ( i = 0; i < linesPadded; i++ )
+ insert_( end, "\n" );
+ } /* else nDeletedLines == nInsertedLines; */
+
+ /* Save a copy of the text which will be modified for the modify CBs */
+ deletedText = text_range( start, end );
+
+ /* Delete then insert */
+ remove_rectangular_( start, end, rectStart, rectEnd, &deleteInserted, &hint );
+ insert_column_( rectStart, start, insText, &insertDeleted, &insertInserted,
+ &mCursorPosHint );
+
+ /* Figure out how many chars were inserted and call modify callbacks */
+ if ( insertDeleted != deleteInserted + linesPadded )
+ fprintf( stderr, "NEdit: internal consistency check repl1 failed\n" );
+ call_modify_callbacks( start, end - start, insertInserted, 0, deletedText );
+ free( (void *) deletedText );
+ if ( nInsertedLines < nDeletedLines )
+ free( (void *) insText );
+}
+
+/*
+** Remove a rectangular swath of characters between character positions start
+** and end and horizontal displayed-character offsets rectStart and rectEnd.
+*/
+void Fl_Text_Buffer::remove_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ const char * deletedText;
+ int nInserted;
+
+ start = line_start( start );
+ end = line_end( end );
+ deletedText = text_range( start, end );
+ remove_rectangular_( start, end, rectStart, rectEnd, &nInserted,
+ &mCursorPosHint );
+ call_modify_callbacks( start, end - start, nInserted, 0, deletedText );
+ free( (void *) deletedText );
+}
+
+/*
+** Clear a rectangular "hole" out of the buffer between character positions
+** start and end and horizontal displayed-character offsets rectStart and
+** rectEnd.
+*/
+void Fl_Text_Buffer::clear_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ int i, nLines;
+ char *newlineString;
+
+ nLines = count_lines( start, end );
+ newlineString = (char *)malloc( nLines + 1 );
+ for ( i = 0; i < nLines; i++ )
+ newlineString[ i ] = '\n';
+ newlineString[ i ] = '\0';
+ overlay_rectangular( start, rectStart, rectEnd, newlineString,
+ NULL, NULL );
+ free( (void *) newlineString );
+}
+
+const char * Fl_Text_Buffer::text_in_rectangle( int start, int end,
+ int rectStart, int rectEnd ) {
+ int lineStart, selLeft, selRight, len;
+ char *textOut, *outPtr, *retabbedStr;
+ const char *textIn;
+
+ start = line_start( start );
+ end = line_end( end );
+ textOut = (char *)malloc( ( end - start ) + 1 );
+ lineStart = start;
+ outPtr = textOut;
+ while ( lineStart <= end ) {
+ rectangular_selection_boundaries( lineStart, rectStart, rectEnd,
+ &selLeft, &selRight );
+ textIn = text_range( selLeft, selRight );
+ len = selRight - selLeft;
+ memcpy( outPtr, textIn, len );
+ free( (void *) textIn );
+ outPtr += len;
+ lineStart = line_end( selRight ) + 1;
+ *outPtr++ = '\n';
+ }
+ if ( outPtr != textOut )
+ outPtr--; /* don't leave trailing newline */
+ *outPtr = '\0';
+
+ /* If necessary, realign the tabs in the selection as if the text were
+ positioned at the left margin */
+ retabbedStr = realignTabs( textOut, rectStart, 0, mTabDist,
+ mUseTabs, mNullSubsChar, &len );
+ free( (void *) textOut );
+ return retabbedStr;
+}
+
+/*
+** Set the hardware tab distance used by all displays for this buffer,
+** and used in computing offsets for rectangular selection operations.
+*/
+void Fl_Text_Buffer::tab_distance( int tabDist ) {
+ const char * deletedText;
+
+ /* Change the tab setting */
+ mTabDist = tabDist;
+
+ /* Force any display routines to redisplay everything (unfortunately,
+ this means copying the whole buffer contents to provide "deletedText" */
+ deletedText = text();
+ call_modify_callbacks( 0, mLength, mLength, 0, deletedText );
+ free( (void *) deletedText );
+}
+
+void Fl_Text_Buffer::select( int start, int end ) {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.set( start, end );
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+void Fl_Text_Buffer::unselect() {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.mSelected = 0;
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+void Fl_Text_Buffer::select_rectangular( int start, int end, int rectStart,
+ int rectEnd ) {
+ Fl_Text_Selection oldSelection = mPrimary;
+
+ mPrimary.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mPrimary );
+}
+
+int Fl_Text_Buffer::selection_position( int *start, int *end
+ ) {
+ return mPrimary.position( start, end );
+}
+
+int Fl_Text_Buffer::selection_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mPrimary.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::selection_text() {
+ return selection_text_( &mPrimary );
+}
+
+void Fl_Text_Buffer::remove_selection() {
+ remove_selection_( &mPrimary );
+}
+
+void Fl_Text_Buffer::replace_selection( const char *text ) {
+ replace_selection_( &mPrimary, text );
+}
+
+void Fl_Text_Buffer::secondary_select( int start, int end ) {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.set( start, end );
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+void Fl_Text_Buffer::secondary_unselect() {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.mSelected = 0;
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+void Fl_Text_Buffer::secondary_select_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ Fl_Text_Selection oldSelection = mSecondary;
+
+ mSecondary.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mSecondary );
+}
+
+int Fl_Text_Buffer::secondary_selection_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mSecondary.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::secondary_selection_text() {
+ return selection_text_( &mSecondary );
+}
+
+void Fl_Text_Buffer::remove_secondary_selection() {
+ remove_selection_( &mSecondary );
+}
+
+void Fl_Text_Buffer::replace_secondary_selection( const char *text ) {
+ replace_selection_( &mSecondary, text );
+}
+
+void Fl_Text_Buffer::highlight( int start, int end ) {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.set( start, end );
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+void Fl_Text_Buffer::unhighlight() {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.mSelected = 0;
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+void Fl_Text_Buffer::highlight_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ Fl_Text_Selection oldSelection = mHighlight;
+
+ mHighlight.set_rectangular( start, end, rectStart, rectEnd );
+ redisplay_selection( &oldSelection, &mHighlight );
+}
+
+int Fl_Text_Buffer::highlight_position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ return mHighlight.position( start, end, isRect, rectStart,
+ rectEnd );
+}
+
+const char * Fl_Text_Buffer::highlight_text() {
+ return selection_text_( &mHighlight );
+}
+
+/*
+** Add a callback routine to be called when the buffer is modified
+*/
+void Fl_Text_Buffer::add_modify_callback( Fl_Text_Modify_Cb bufModifiedCB,
+ void *cbArg ) {
+ Fl_Text_Modify_Cb * newModifyProcs;
+ void **newCBArgs;
+ int i;
+
+ newModifyProcs = new Fl_Text_Modify_Cb [ mNModifyProcs + 1 ];
+ newCBArgs = new void * [ mNModifyProcs + 1 ];
+ for ( i = 0; i < mNModifyProcs; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i ];
+ newCBArgs[ i ] = mCbArgs[ i ];
+ }
+ if ( mNModifyProcs != 0 ) {
+ delete [] mNodifyProcs;
+ delete [] mCbArgs;
+ }
+ newModifyProcs[ mNModifyProcs ] = bufModifiedCB;
+ newCBArgs[ mNModifyProcs ] = cbArg;
+ mNModifyProcs++;
+ mNodifyProcs = newModifyProcs;
+ mCbArgs = newCBArgs;
+}
+
+void Fl_Text_Buffer::remove_modify_callback( Fl_Text_Modify_Cb bufModifiedCB,
+ void *cbArg ) {
+ int i, toRemove = -1;
+ Fl_Text_Modify_Cb *newModifyProcs;
+ void **newCBArgs;
+
+ /* find the matching callback to remove */
+ for ( i = 0; i < mNModifyProcs; i++ ) {
+ if ( mNodifyProcs[ i ] == bufModifiedCB && mCbArgs[ i ] == cbArg ) {
+ toRemove = i;
+ break;
+ }
+ }
+ if ( toRemove == -1 ) {
+ fprintf( stderr, "Internal Error: Can't find modify CB to remove\n" );
+ return;
+ }
+
+ /* Allocate new lists for remaining callback procs and args (if
+ any are left) */
+ mNModifyProcs--;
+ if ( mNModifyProcs == 0 ) {
+ mNModifyProcs = 0;
+ delete[] mNodifyProcs;
+ mNodifyProcs = NULL;
+ delete[] mCbArgs;
+ mCbArgs = NULL;
+ return;
+ }
+ newModifyProcs = new Fl_Text_Modify_Cb [ mNModifyProcs ];
+ newCBArgs = new void * [ mNModifyProcs ];
+
+ /* copy out the remaining members and free the old lists */
+ for ( i = 0; i < toRemove; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i ];
+ newCBArgs[ i ] = mCbArgs[ i ];
+ }
+ for ( ; i < mNModifyProcs; i++ ) {
+ newModifyProcs[ i ] = mNodifyProcs[ i + 1 ];
+ newCBArgs[ i ] = mCbArgs[ i + 1 ];
+ }
+ delete[] mNodifyProcs;
+ delete[] mCbArgs;
+ mNodifyProcs = newModifyProcs;
+ mCbArgs = newCBArgs;
+}
+
+/*
+** Return the text from the entire line containing position "pos"
+*/
+const char * Fl_Text_Buffer::line_text( int pos ) {
+ return text_range( line_start( pos ), line_end( pos ) );
+}
+
+/*
+** Find the position of the start of the line containing position "pos"
+*/
+int Fl_Text_Buffer::line_start( int pos ) {
+ if ( !findchar_backward( pos, '\n', &pos ) )
+ return 0;
+ return pos + 1;
+}
+
+/*
+** Find the position of the end of the line containing position "pos"
+** (which is either a pointer to the newline character ending the line,
+** or a pointer to one character beyond the end of the buffer)
+*/
+int Fl_Text_Buffer::line_end( int pos ) {
+ if ( !findchar_forward( pos, '\n', &pos ) )
+ pos = mLength;
+ return pos;
+}
+
+int Fl_Text_Buffer::word_start( int pos ) {
+ while ( pos && ( isalnum( character( pos ) ) || character( pos ) == '_' ) ) {
+ pos--;
+ }
+ if ( !( isalnum( character( pos ) ) || character( pos ) == '_' ) ) pos++;
+ return pos;
+}
+
+int Fl_Text_Buffer::word_end( int pos ) {
+ while (pos < length() && (isalnum(character(pos)) || character(pos) == '_' )) {
+ pos++;
+ }
+ return pos;
+}
+
+/*
+** Get a character from the text buffer expanded into it's screen
+** representation (which may be several characters for a tab or a
+** control code). Returns the number of characters written to "outStr".
+** "indent" is the number of characters from the start of the line
+** for figuring tabs. Output string is guranteed to be shorter or
+** equal in length to FL_TEXT_MAX_EXP_CHAR_LEN
+*/
+int Fl_Text_Buffer::expand_character( int pos, int indent, char *outStr ) {
+ return expand_character( character( pos ), indent, outStr,
+ mTabDist, mNullSubsChar );
+}
+
+/*
+** Expand a single character from the text buffer into it's screen
+** representation (which may be several characters for a tab or a
+** control code). Returns the number of characters added to "outStr".
+** "indent" is the number of characters from the start of the line
+** for figuring tabs. Output string is guranteed to be shorter or
+** equal in length to FL_TEXT_MAX_EXP_CHAR_LEN
+*/
+int Fl_Text_Buffer::expand_character( char c, int indent, char *outStr, int tabDist,
+ char nullSubsChar ) {
+ int i, nSpaces;
+
+ /* Convert tabs to spaces */
+ if ( c == '\t' ) {
+ nSpaces = tabDist - ( indent % tabDist );
+ for ( i = 0; i < nSpaces; i++ )
+ outStr[ i ] = ' ';
+ return nSpaces;
+ }
+
+ /* Convert control codes to readable character sequences */
+ /*... is this safe with international character sets? */
+ if ( ( ( unsigned char ) c ) <= 31 ) {
+ sprintf( outStr, "<%s>", ControlCodeTable[ c ] );
+ return strlen( outStr );
+ } else if ( c == 127 ) {
+ sprintf( outStr, "<del>" );
+ return 5;
+ } else if ( c == nullSubsChar ) {
+ sprintf( outStr, "<nul>" );
+ return 5;
+ }
+
+ /* Otherwise, just return the character */
+ *outStr = c;
+ return 1;
+}
+
+/*
+** Return the length in displayed characters of character "c" expanded
+** for display (as discussed above in BufGetExpandedChar). If the
+** buffer for which the character width is being measured is doing null
+** substitution, nullSubsChar should be passed as that character (or nul
+** to ignore).
+*/
+int Fl_Text_Buffer::character_width( char c, int indent, int tabDist, char nullSubsChar ) {
+ /* Note, this code must parallel that in Fl_Text_Buffer::ExpandCharacter */
+ if ( c == '\t' )
+ return tabDist - ( indent % tabDist );
+ else if ( ( ( unsigned char ) c ) <= 31 )
+ return strlen( ControlCodeTable[ c ] ) + 2;
+ else if ( c == 127 )
+ return 5;
+ else if ( c == nullSubsChar )
+ return 5;
+ return 1;
+}
+
+/*
+** Count the number of displayed characters between buffer position
+** "lineStartPos" and "targetPos". (displayed characters are the characters
+** shown on the screen to represent characters in the buffer, where tabs and
+** control characters are expanded)
+*/
+int Fl_Text_Buffer::count_displayed_characters( int lineStartPos, int targetPos ) {
+ int pos, charCount = 0;
+ char expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+
+ pos = lineStartPos;
+ while ( pos < targetPos )
+ charCount += expand_character( pos++, charCount, expandedChar );
+ return charCount;
+}
+
+/*
+** Count forward from buffer position "startPos" in displayed characters
+** (displayed characters are the characters shown on the screen to represent
+** characters in the buffer, where tabs and control characters are expanded)
+*/
+int Fl_Text_Buffer::skip_displayed_characters( int lineStartPos, int nChars ) {
+ int pos, charCount = 0;
+ char c;
+
+ pos = lineStartPos;
+ while ( charCount < nChars && pos < mLength ) {
+ c = character( pos );
+ if ( c == '\n' )
+ return pos;
+ charCount += character_width( c, charCount, mTabDist, mNullSubsChar );
+ pos++;
+ }
+ return pos;
+}
+
+/*
+** Count the number of newlines between startPos and endPos in buffer "buf".
+** The character at position "endPos" is not counted.
+*/
+int Fl_Text_Buffer::count_lines( int startPos, int endPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = 0;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( pos == endPos )
+ return lineCount;
+ if ( mBuf[ pos++ ] == '\n' )
+ lineCount++;
+ }
+ while ( pos < mLength ) {
+ if ( pos == endPos )
+ return lineCount;
+ if ( mBuf[ pos++ + gapLen ] == '\n' )
+ lineCount++;
+ }
+ return lineCount;
+}
+
+/*
+** Find the first character of the line "nLines" forward from "startPos"
+** in "buf" and return its position
+*/
+int Fl_Text_Buffer::skip_lines( int startPos, int nLines ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = 0;
+
+ if ( nLines == 0 )
+ return startPos;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( mBuf[ pos++ ] == '\n' ) {
+ lineCount++;
+ if ( lineCount == nLines )
+ return pos;
+ }
+ }
+ while ( pos < mLength ) {
+ if ( mBuf[ pos++ + gapLen ] == '\n' ) {
+ lineCount++;
+ if ( lineCount >= nLines )
+ return pos;
+ }
+ }
+ return pos;
+}
+
+/*
+** Find the position of the first character of the line "nLines" backwards
+** from "startPos" (not counting the character pointed to by "startpos" if
+** that is a newline) in "buf". nLines == 0 means find the beginning of
+** the line
+*/
+int Fl_Text_Buffer::rewind_lines( int startPos, int nLines ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ int lineCount = -1;
+
+ pos = startPos - 1;
+ if ( pos <= 0 )
+ return 0;
+
+ while ( pos >= mGapStart ) {
+ if ( mBuf[ pos + gapLen ] == '\n' ) {
+ if ( ++lineCount >= nLines )
+ return pos + 1;
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ if ( mBuf[ pos ] == '\n' ) {
+ if ( ++lineCount >= nLines )
+ return pos + 1;
+ }
+ pos--;
+ }
+ return 0;
+}
+
+/*
+** Search forwards in buffer for string "searchString", starting with the
+** character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::search_forward( int startPos, const char *searchString,
+ int *foundPos, int matchCase )
+{
+ if (!searchString) return 0;
+ int bp;
+ const char* sp;
+ while (startPos < length()) {
+ bp = startPos;
+ sp = searchString;
+ do {
+ if (!*sp) { *foundPos = startPos; return 1; }
+ } while ((matchCase ? character(bp++) == *sp++ :
+ toupper(character(bp++)) == toupper(*sp++))
+ && bp < length());
+ startPos++;
+ }
+ return 0;
+}
+
+/*
+** Search backwards in buffer for string "searchString", starting with the
+** character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::search_backward( int startPos, const char *searchString,
+ int *foundPos, int matchCase )
+{
+ if (!searchString) return 0;
+ int bp;
+ const char* sp;
+ while (startPos > 0) {
+ bp = startPos-1;
+ sp = searchString+strlen(searchString)-1;
+ do {
+ if (sp < searchString) { *foundPos = bp+1; return 1; }
+ } while ((matchCase ? character(bp--) == *sp-- :
+ toupper(character(bp--)) == toupper(*sp--))
+ && bp >= 0);
+ startPos--;
+ }
+ return 0;
+}
+
+/*
+** Search forwards in buffer for characters in "searchChars", starting
+** with the character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::findchars_forward( int startPos, const char *searchChars,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ const char *c;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos++;
+ }
+ while ( pos < mLength ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos + gapLen ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos++;
+ }
+ *foundPos = mLength;
+ return 0;
+}
+
+/*
+** Search backwards in buffer for characters in "searchChars", starting
+** with the character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not.
+*/
+int Fl_Text_Buffer::findchars_backward( int startPos, const char *searchChars,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+ const char *c;
+
+ if ( startPos == 0 ) {
+ *foundPos = 0;
+ return 0;
+ }
+ pos = startPos == 0 ? 0 : startPos - 1;
+ while ( pos >= mGapStart ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos + gapLen ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ for ( c = searchChars; *c != '\0'; c++ ) {
+ if ( mBuf[ pos ] == *c ) {
+ *foundPos = pos;
+ return 1;
+ }
+ }
+ pos--;
+ }
+ *foundPos = 0;
+ return 0;
+}
+
+/*
+** A horrible design flaw in NEdit (from the very start, before we knew that
+** NEdit would become so popular), is that it uses C NULL terminated strings
+** to hold text. This means editing text containing NUL characters is not
+** possible without special consideration. Here is the special consideration.
+** The routines below maintain a special substitution-character which stands
+** in for a null, and translates strings an buffers back and forth from/to
+** the substituted form, figure out what to substitute, and figure out
+** when we're in over our heads and no translation is possible.
+*/
+
+/*
+** The primary routine for integrating new text into a text buffer with
+** substitution of another character for ascii nuls. This substitutes null
+** characters in the string in preparation for being copied or replaced
+** into the buffer, and if neccessary, adjusts the buffer as well, in the
+** event that the string contains the character it is currently using for
+** substitution. Returns 0, if substitution is no longer possible
+** because all non-printable characters are already in use.
+*/
+int Fl_Text_Buffer::substitute_null_characters( char *string, int length ) {
+ char histogram[ 256 ];
+
+ /* Find out what characters the string contains */
+ histogramCharacters( string, length, histogram, 1 );
+
+ /* Does the string contain the null-substitute character? If so, re-
+ histogram the buffer text to find a character which is ok in both the
+ string and the buffer, and change the buffer's null-substitution
+ character. If none can be found, give up and return 0 */
+ if ( histogram[ ( unsigned char ) mNullSubsChar ] != 0 ) {
+ char * bufString;
+ char newSubsChar;
+ bufString = (char*)text();
+ histogramCharacters( bufString, mLength, histogram, 0 );
+ newSubsChar = chooseNullSubsChar( histogram );
+ if ( newSubsChar == '\0' )
+ return 0;
+ subsChars( bufString, mLength, mNullSubsChar, newSubsChar );
+ remove_( 0, mLength );
+ insert_( 0, bufString );
+ free( (void *) bufString );
+ mNullSubsChar = newSubsChar;
+ }
+
+ /* If the string contains null characters, substitute them with the
+ buffer's null substitution character */
+ if ( histogram[ 0 ] != 0 )
+ subsChars( string, length, '\0', mNullSubsChar );
+ return 1;
+}
+
+/*
+** Convert strings obtained from buffers which contain null characters, which
+** have been substituted for by a special substitution character, back to
+** a null-containing string. There is no time penalty for calling this
+** routine if no substitution has been done.
+*/
+void Fl_Text_Buffer::unsubstitute_null_characters( char *string ) {
+ register char * c, subsChar = mNullSubsChar;
+
+ if ( subsChar == '\0' )
+ return;
+ for ( c = string; *c != '\0'; c++ )
+ if ( *c == subsChar )
+ * c = '\0';
+}
+
+/*
+** Create a pseudo-histogram of the characters in a string (don't actually
+** count, because we don't want overflow, just mark the character's presence
+** with a 1). If init is true, initialize the histogram before acumulating.
+** if not, add the new data to an existing histogram.
+*/
+static void histogramCharacters( const char *string, int length, char hist[ 256 ],
+ int init ) {
+ int i;
+ const char *c;
+
+ if ( init )
+ for ( i = 0; i < 256; i++ )
+ hist[ i ] = 0;
+ for ( c = string; c < &string[ length ]; c++ )
+ hist[ *( ( unsigned char * ) c ) ] |= 1;
+}
+
+/*
+** Substitute fromChar with toChar in string.
+*/
+static void subsChars( char *string, int length, char fromChar, char toChar ) {
+ char * c;
+
+ for ( c = string; c < &string[ length ]; c++ )
+ if ( *c == fromChar ) * c = toChar;
+}
+
+/*
+** Search through ascii control characters in histogram in order of least
+** likelihood of use, find an unused character to use as a stand-in for a
+** null. If the character set is full (no available characters outside of
+** the printable set, return the null character.
+*/
+static char chooseNullSubsChar( char hist[ 256 ] ) {
+#define N_REPLACEMENTS 25
+ static char replacements[ N_REPLACEMENTS ] = {1, 2, 3, 4, 5, 6, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 28, 29, 30, 31, 11, 7};
+ int i;
+ for ( i = 0; i < N_REPLACEMENTS; i++ )
+ if ( hist[ replacements[ i ] ] == 0 )
+ return replacements[ i ];
+ return '\0';
+}
+
+/*
+** Internal (non-redisplaying) version of BufInsert. Returns the length of
+** text inserted (this is just strlen(text), however this calculation can be
+** expensive and the length will be required by any caller who will continue
+** on to call redisplay). pos must be contiguous with the existing text in
+** the buffer (i.e. not past the end).
+*/
+int Fl_Text_Buffer::insert_( int pos, const char *text ) {
+ int length = strlen( text );
+
+ /* Prepare the buffer to receive the new text. If the new text fits in
+ the current buffer, just move the gap (if necessary) to where
+ the text should be inserted. If the new text is too large, reallocate
+ the buffer with a gap large enough to accomodate the new text and a
+ gap of PREFERRED_GAP_SIZE */
+ if ( length > mGapEnd - mGapStart )
+ reallocate_with_gap( pos, length + PREFERRED_GAP_SIZE );
+ else if ( pos != mGapStart )
+ move_gap( pos );
+
+ /* Insert the new text (pos now corresponds to the start of the gap) */
+ memcpy( &mBuf[ pos ], text, length );
+ mGapStart += length;
+ mLength += length;
+ update_selections( pos, 0, length );
+
+ return length;
+}
+
+/*
+** Internal (non-redisplaying) version of BufRemove. Removes the contents
+** of the buffer between start and end (and moves the gap to the site of
+** the delete).
+*/
+void Fl_Text_Buffer::remove_( int start, int end ) {
+ /* if the gap is not contiguous to the area to remove, move it there */
+ if ( start > mGapStart )
+ move_gap( start );
+ else if ( end < mGapStart )
+ move_gap( end );
+
+ /* expand the gap to encompass the deleted characters */
+ mGapEnd += end - mGapStart;
+ mGapStart -= mGapStart - start;
+
+ /* update the length */
+ mLength -= end - start;
+
+ /* fix up any selections which might be affected by the change */
+ update_selections( start, end - start, 0 );
+}
+
+/*
+** Insert a column of text without calling the modify callbacks. Note that
+** in some pathological cases, inserting can actually decrease the size of
+** the buffer because of spaces being coalesced into tabs. "nDeleted" and
+** "nInserted" return the number of characters deleted and inserted beginning
+** at the start of the line containing "startPos". "endPos" returns buffer
+** position of the lower left edge of the inserted column (as a hint for
+** routines which need to set a cursor position).
+*/
+void Fl_Text_Buffer::insert_column_( int column, int startPos, const char *insText,
+ int *nDeleted, int *nInserted, int *endPos ) {
+ int nLines, start, end, insWidth, lineStart, lineEnd;
+ int expReplLen, expInsLen, len, endOffset;
+ char *c, *outStr, *outPtr, *expText, *insLine;
+ const char *line;
+ const char *replText;
+ const char *insPtr;
+
+ if ( column < 0 )
+ column = 0;
+
+ /* Allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs in both the inserted text and the replaced
+ area, as well as per line: 1) an additional 2*FL_TEXT_MAX_EXP_CHAR_LEN
+ characters for padding where tabs and control characters cross the
+ column of the selection, 2) up to "column" additional spaces per
+ line for padding out to the position of "column", 3) padding up
+ to the width of the inserted text if that must be padded to align
+ the text beyond the inserted column. (Space for additional
+ newlines if the inserted text extends beyond the end of the buffer
+ is counted with the length of insText) */
+ start = line_start( startPos );
+ nLines = countLines( insText ) + 1;
+ insWidth = textWidth( insText, mTabDist, mNullSubsChar );
+ end = line_end( skip_lines( start, nLines - 1 ) );
+ replText = text_range( start, end );
+ expText = expandTabs( replText, 0, mTabDist, mNullSubsChar,
+ &expReplLen );
+ free( (void *) replText );
+ free( (void *) expText );
+ expText = expandTabs( insText, 0, mTabDist, mNullSubsChar,
+ &expInsLen );
+ free( (void *) expText );
+ outStr = (char *)malloc( expReplLen + expInsLen +
+ nLines * ( column + insWidth + FL_TEXT_MAX_EXP_CHAR_LEN ) + 1 );
+
+ /* Loop over all lines in the buffer between start and end removing the
+ text between rectStart and rectEnd and padding appropriately. Trim
+ trailing space from line (whitespace at the ends of lines otherwise
+ tends to multiply, since additional padding is added to maintain it */
+ outPtr = outStr;
+ lineStart = start;
+ insPtr = insText;
+ for (;;) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ insLine = copyLine( insPtr, &len );
+ insPtr += len;
+ insertColInLine( line, insLine, column, insWidth, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ free( (void *) insLine );
+ for ( c = outPtr + len - 1; c > outPtr && isspace( *c ); c-- )
+ len--;
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd < mLength ? lineEnd + 1 : mLength;
+ if ( *insPtr == '\0' )
+ break;
+ insPtr++;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the new stuff */
+ remove_( start, end );
+ insert_( start, outStr );
+ *nInserted = outPtr - outStr;
+ *nDeleted = end - start;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Delete a rectangle of text without calling the modify callbacks. Returns
+** the number of characters replacing those between start and end. Note that
+** in some pathological cases, deleting can actually increase the size of
+** the buffer because of tab expansions. "endPos" returns the buffer position
+** of the point in the last line where the text was removed (as a hint for
+** routines which need to position the cursor after a delete operation)
+*/
+void Fl_Text_Buffer::remove_rectangular_( int start, int end, int rectStart,
+ int rectEnd, int *replaceLen, int *endPos ) {
+ int nLines, lineStart, lineEnd, len, endOffset;
+ char *outStr, *outPtr, *expText;
+ const char *text, *line;
+
+ /* allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs as well as an additional FL_TEXT_MAX_EXP_CHAR_LEN * 2
+ characters per line for padding where tabs and control characters cross
+ the edges of the selection */
+ start = line_start( start );
+ end = line_end( end );
+ nLines = count_lines( start, end ) + 1;
+ text = text_range( start, end );
+ expText = expandTabs( text, 0, mTabDist, mNullSubsChar, &len );
+ free( (void *) text );
+ free( (void *) expText );
+ outStr = (char *)malloc( len + nLines * FL_TEXT_MAX_EXP_CHAR_LEN * 2 + 1 );
+
+ /* loop over all lines in the buffer between start and end removing
+ the text between rectStart and rectEnd and padding appropriately */
+ lineStart = start;
+ outPtr = outStr;
+ while ( lineStart <= mLength && lineStart <= end ) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ deleteRectFromLine( line, rectStart, rectEnd, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd + 1;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the newly created string */
+ remove_( start, end );
+ insert_( start, outStr );
+ *replaceLen = outPtr - outStr;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Overlay a rectangular area of text without calling the modify callbacks.
+** "nDeleted" and "nInserted" return the number of characters deleted and
+** inserted beginning at the start of the line containing "startPos".
+** "endPos" returns buffer position of the lower left edge of the inserted
+** column (as a hint for routines which need to set a cursor position).
+*/
+void Fl_Text_Buffer::overlay_rectangular_(int startPos, int rectStart,
+ int rectEnd, const char *insText,
+ int *nDeleted, int *nInserted,
+ int *endPos ) {
+ int nLines, start, end, lineStart, lineEnd;
+ int expInsLen, len, endOffset;
+ char *c, *outStr, *outPtr, *expText, *insLine;
+ const char *line;
+ const char *insPtr;
+
+ /* Allocate a buffer for the replacement string large enough to hold
+ possibly expanded tabs in the inserted text, as well as per line: 1)
+ an additional 2*FL_TEXT_MAX_EXP_CHAR_LEN characters for padding where tabs
+ and control characters cross the column of the selection, 2) up to
+ "column" additional spaces per line for padding out to the position
+ of "column", 3) padding up to the width of the inserted text if that
+ must be padded to align the text beyond the inserted column. (Space
+ for additional newlines if the inserted text extends beyond the end
+ of the buffer is counted with the length of insText) */
+ start = line_start( startPos );
+ nLines = countLines( insText ) + 1;
+ end = line_end( skip_lines( start, nLines - 1 ) );
+ expText = expandTabs( insText, 0, mTabDist, mNullSubsChar,
+ &expInsLen );
+ free( (void *) expText );
+ outStr = (char *)malloc( end - start + expInsLen +
+ nLines * ( rectEnd + FL_TEXT_MAX_EXP_CHAR_LEN ) + 1 );
+
+ /* Loop over all lines in the buffer between start and end overlaying the
+ text between rectStart and rectEnd and padding appropriately. Trim
+ trailing space from line (whitespace at the ends of lines otherwise
+ tends to multiply, since additional padding is added to maintain it */
+ outPtr = outStr;
+ lineStart = start;
+ insPtr = insText;
+ for (;;) {
+ lineEnd = line_end( lineStart );
+ line = text_range( lineStart, lineEnd );
+ insLine = copyLine( insPtr, &len );
+ insPtr += len;
+ overlayRectInLine( line, insLine, rectStart, rectEnd, mTabDist,
+ mUseTabs, mNullSubsChar, outPtr, &len, &endOffset );
+ free( (void *) line );
+ free( (void *) insLine );
+ for ( c = outPtr + len - 1; c > outPtr && isspace( *c ); c-- )
+ len--;
+ outPtr += len;
+ *outPtr++ = '\n';
+ lineStart = lineEnd < mLength ? lineEnd + 1 : mLength;
+ if ( *insPtr == '\0' )
+ break;
+ insPtr++;
+ }
+ if ( outPtr != outStr )
+ outPtr--; /* trim back off extra newline */
+ *outPtr = '\0';
+
+ /* replace the text between start and end with the new stuff */
+ remove_( start, end );
+ insert_( start, outStr );
+ *nInserted = outPtr - outStr;
+ *nDeleted = end - start;
+ *endPos = start + ( outPtr - outStr ) - len + endOffset;
+ free( (void *) outStr );
+}
+
+/*
+** Insert characters from single-line string "insLine" in single-line string
+** "line" at "column", leaving "insWidth" space before continuing line.
+** "outLen" returns the number of characters written to "outStr", "endOffset"
+** returns the number of characters from the beginning of the string to
+** the right edge of the inserted text (as a hint for routines which need
+** to position the cursor).
+*/
+static void insertColInLine( const char *line, char *insLine, int column, int insWidth,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset ) {
+ char * c, *outPtr, *retabbedStr;
+ const char *linePtr;
+ int indent, toIndent, len, postColIndent;
+
+ /* copy the line up to "column" */
+ outPtr = outStr;
+ indent = 0;
+ for ( linePtr = line; *linePtr != '\0'; linePtr++ ) {
+ len = Fl_Text_Buffer::character_width( *linePtr, indent, tabDist, nullSubsChar );
+ if ( indent + len > column )
+ break;
+ indent += len;
+ *outPtr++ = *linePtr;
+ }
+
+ /* If "column" falls in the middle of a character, and the character is a
+ tab, leave it off and leave the indent short and it will get padded
+ later. If it's a control character, insert it and adjust indent
+ accordingly. */
+ if ( indent < column && *linePtr != '\0' ) {
+ postColIndent = indent + len;
+ if ( *linePtr == '\t' )
+ linePtr++;
+ else {
+ *outPtr++ = *linePtr++;
+ indent += len;
+ }
+ } else
+ postColIndent = indent;
+
+ /* If there's no text after the column and no text to insert, that's all */
+ if ( *insLine == '\0' && *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* pad out to column if text is too short */
+ if ( indent < column ) {
+ addPadding( outPtr, indent, column, tabDist, useTabs, nullSubsChar, &len );
+ outPtr += len;
+ indent = column;
+ }
+
+ /* Copy the text from "insLine" (if any), recalculating the tabs as if
+ the inserted string began at column 0 to its new column destination */
+ if ( *insLine != '\0' ) {
+ retabbedStr = realignTabs( insLine, 0, indent, tabDist, useTabs,
+ nullSubsChar, &len );
+ for ( c = retabbedStr; *c != '\0'; c++ ) {
+ *outPtr++ = *c;
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ indent += len;
+ }
+ free( (void *) retabbedStr );
+ }
+
+ /* If the original line did not extend past "column", that's all */
+ if ( *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* Pad out to column + width of inserted text + (additional original
+ offset due to non-breaking character at column) */
+ toIndent = column + insWidth + postColIndent - column;
+ addPadding( outPtr, indent, toIndent, tabDist, useTabs, nullSubsChar, &len );
+ outPtr += len;
+ indent = toIndent;
+
+ /* realign tabs for text beyond "column" and write it out */
+ retabbedStr = realignTabs( linePtr, postColIndent, indent, tabDist,
+ useTabs, nullSubsChar, &len );
+ strcpy( outPtr, retabbedStr );
+ free( (void *) retabbedStr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + len;
+}
+
+/*
+** Remove characters in single-line string "line" between displayed positions
+** "rectStart" and "rectEnd", and write the result to "outStr", which is
+** assumed to be large enough to hold the returned string. Note that in
+** certain cases, it is possible for the string to get longer due to
+** expansion of tabs. "endOffset" returns the number of characters from
+** the beginning of the string to the point where the characters were
+** deleted (as a hint for routines which need to position the cursor).
+*/
+static void deleteRectFromLine( const char *line, int rectStart, int rectEnd,
+ int tabDist, int useTabs, char nullSubsChar, char *outStr, int *outLen,
+ int *endOffset ) {
+ int indent, preRectIndent, postRectIndent, len;
+ const char *c;
+ char *retabbedStr, *outPtr;
+
+ /* copy the line up to rectStart */
+ outPtr = outStr;
+ indent = 0;
+ for ( c = line; *c != '\0'; c++ ) {
+ if ( indent > rectStart )
+ break;
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ if ( indent + len > rectStart && ( indent == rectStart || *c == '\t' ) )
+ break;
+ indent += len;
+ *outPtr++ = *c;
+ }
+ preRectIndent = indent;
+
+ /* skip the characters between rectStart and rectEnd */
+ for ( ; *c != '\0' && indent < rectEnd; c++ )
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ postRectIndent = indent;
+
+ /* If the line ended before rectEnd, there's nothing more to do */
+ if ( *c == '\0' ) {
+ *outPtr = '\0';
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* fill in any space left by removed tabs or control characters
+ which straddled the boundaries */
+ indent = max( rectStart + postRectIndent - rectEnd, preRectIndent );
+ addPadding( outPtr, preRectIndent, indent, tabDist, useTabs, nullSubsChar,
+ &len );
+ outPtr += len;
+
+ /* Copy the rest of the line. If the indentation has changed, preserve
+ the position of non-whitespace characters by converting tabs to
+ spaces, then back to tabs with the correct offset */
+ retabbedStr = realignTabs( c, postRectIndent, indent, tabDist, useTabs,
+ nullSubsChar, &len );
+ strcpy( outPtr, retabbedStr );
+ free( (void *) retabbedStr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + len;
+}
+
+/*
+** Overlay characters from single-line string "insLine" on single-line string
+** "line" between displayed character offsets "rectStart" and "rectEnd".
+** "outLen" returns the number of characters written to "outStr", "endOffset"
+** returns the number of characters from the beginning of the string to
+** the right edge of the inserted text (as a hint for routines which need
+** to position the cursor).
+*/
+static void overlayRectInLine( const char *line, char *insLine, int rectStart,
+ int rectEnd, int tabDist, int useTabs, char nullSubsChar, char *outStr,
+ int *outLen, int *endOffset ) {
+ char * c, *outPtr, *retabbedStr;
+ int inIndent, outIndent, len, postRectIndent;
+ const char *linePtr;
+
+ /* copy the line up to "rectStart" */
+ outPtr = outStr;
+ inIndent = outIndent = 0;
+ for ( linePtr = line; *linePtr != '\0'; linePtr++ ) {
+ len = Fl_Text_Buffer::character_width( *linePtr, inIndent, tabDist, nullSubsChar );
+ if ( inIndent + len > rectStart )
+ break;
+ inIndent += len;
+ outIndent += len;
+ *outPtr++ = *linePtr;
+ }
+
+ /* If "rectStart" falls in the middle of a character, and the character
+ is a tab, leave it off and leave the outIndent short and it will get
+ padded later. If it's a control character, insert it and adjust
+ outIndent accordingly. */
+ if ( inIndent < rectStart && *linePtr != '\0' ) {
+ if ( *linePtr == '\t' ) {
+ linePtr++;
+ inIndent += len;
+ } else {
+ *outPtr++ = *linePtr++;
+ outIndent += len;
+ inIndent += len;
+ }
+ }
+
+ /* skip the characters between rectStart and rectEnd */
+ postRectIndent = rectEnd;
+ for ( ; *linePtr != '\0'; linePtr++ ) {
+ inIndent += Fl_Text_Buffer::character_width( *linePtr, inIndent, tabDist, nullSubsChar );
+ if ( inIndent >= rectEnd ) {
+ linePtr++;
+ postRectIndent = inIndent;
+ break;
+ }
+ }
+
+ /* If there's no text after rectStart and no text to insert, that's all */
+ if ( *insLine == '\0' && *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* pad out to rectStart if text is too short */
+ if ( outIndent < rectStart ) {
+ addPadding( outPtr, outIndent, rectStart, tabDist, useTabs, nullSubsChar,
+ &len );
+ outPtr += len;
+ }
+ outIndent = rectStart;
+
+ /* Copy the text from "insLine" (if any), recalculating the tabs as if
+ the inserted string began at column 0 to its new column destination */
+ if ( *insLine != '\0' ) {
+ retabbedStr = realignTabs( insLine, 0, rectStart, tabDist, useTabs,
+ nullSubsChar, &len );
+ for ( c = retabbedStr; *c != '\0'; c++ ) {
+ *outPtr++ = *c;
+ len = Fl_Text_Buffer::character_width( *c, outIndent, tabDist, nullSubsChar );
+ outIndent += len;
+ }
+ free( (void *) retabbedStr );
+ }
+
+ /* If the original line did not extend past "rectStart", that's all */
+ if ( *linePtr == '\0' ) {
+ *outLen = *endOffset = outPtr - outStr;
+ return;
+ }
+
+ /* Pad out to rectEnd + (additional original offset
+ due to non-breaking character at right boundary) */
+ addPadding( outPtr, outIndent, postRectIndent, tabDist, useTabs,
+ nullSubsChar, &len );
+ outPtr += len;
+ outIndent = postRectIndent;
+
+ /* copy the text beyond "rectEnd" */
+ strcpy( outPtr, linePtr );
+ *endOffset = outPtr - outStr;
+ *outLen = ( outPtr - outStr ) + strlen( linePtr );
+}
+
+void Fl_Text_Selection::set( int start, int end ) {
+ mSelected = start != end;
+ mRectangular = 0;
+ mStart = min( start, end );
+ mEnd = max( start, end );
+}
+
+void Fl_Text_Selection::set_rectangular( int start, int end,
+ int rectStart, int rectEnd ) {
+ mSelected = rectStart < rectEnd;
+ mRectangular = 1;
+ mStart = start;
+ mEnd = end;
+ mRectStart = rectStart;
+ mRectEnd = rectEnd;
+}
+
+int Fl_Text_Selection::position( int *start, int *end ) {
+ if ( !mSelected )
+ return 0;
+ *start = mStart;
+ *end = mEnd;
+
+ return 1;
+}
+
+int Fl_Text_Selection::position( int *start, int *end,
+ int *isRect, int *rectStart, int *rectEnd ) {
+ if ( !mSelected )
+ return 0;
+ *isRect = mRectangular;
+ *start = mStart;
+ *end = mEnd;
+ if ( mRectangular ) {
+ *rectStart = mRectStart;
+ *rectEnd = mRectEnd;
+ }
+ return 1;
+}
+
+/*
+** Return true if position "pos" with indentation "dispIndex" is in
+** the Fl_Text_Selection.
+*/
+int Fl_Text_Selection::includes(int pos, int lineStartPos, int dispIndex) {
+ return selected() &&
+ ( (!rectangular() && pos >= start() && pos < end()) ||
+ (rectangular() && pos >= start() && lineStartPos <= end() &&
+ dispIndex >= rect_start() && dispIndex < rect_end())
+ );
+}
+
+
+
+const char * Fl_Text_Buffer::selection_text_( Fl_Text_Selection *sel ) {
+ int start, end, isRect, rectStart, rectEnd;
+ char *text;
+
+ /* If there's no selection, return an allocated empty string */
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) ) {
+ text = (char *)malloc( 1 );
+ *text = '\0';
+ return text;
+ }
+
+ /* If the selection is not rectangular, return the selected range */
+ if ( isRect )
+ return text_in_rectangle( start, end, rectStart, rectEnd );
+ else
+ return text_range( start, end );
+}
+
+void Fl_Text_Buffer::remove_selection_( Fl_Text_Selection *sel ) {
+ int start, end;
+ int isRect, rectStart, rectEnd;
+
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) )
+ return;
+ if ( isRect )
+ remove_rectangular( start, end, rectStart, rectEnd );
+ else
+ remove( start, end );
+}
+
+void Fl_Text_Buffer::replace_selection_( Fl_Text_Selection *sel, const char *text ) {
+ int start, end, isRect, rectStart, rectEnd;
+ Fl_Text_Selection oldSelection = *sel;
+
+ /* If there's no selection, return */
+ if ( !sel->position( &start, &end, &isRect, &rectStart, &rectEnd ) )
+ return;
+
+ /* Do the appropriate type of replace */
+ if ( isRect )
+ replace_rectangular( start, end, rectStart, rectEnd, text );
+ else
+ replace( start, end, text );
+
+ /* Unselect (happens automatically in BufReplace, but BufReplaceRect
+ can't detect when the contents of a selection goes away) */
+ sel->mSelected = 0;
+ redisplay_selection( &oldSelection, sel );
+}
+
+static void addPadding( char *string, int startIndent, int toIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *charsAdded ) {
+ char * outPtr;
+ int len, indent;
+
+ indent = startIndent;
+ outPtr = string;
+ if ( useTabs ) {
+ while ( indent < toIndent ) {
+ len = Fl_Text_Buffer::character_width( '\t', indent, tabDist, nullSubsChar );
+ if ( len > 1 && indent + len <= toIndent ) {
+ *outPtr++ = '\t';
+ indent += len;
+ } else {
+ *outPtr++ = ' ';
+ indent++;
+ }
+ }
+ } else {
+ while ( indent < toIndent ) {
+ *outPtr++ = ' ';
+ indent++;
+ }
+ }
+ *charsAdded = outPtr - string;
+}
+
+/*
+** Call the stored modify callback procedure(s) for this buffer to update the
+** changed area(s) on the screen and any other listeners.
+*/
+void Fl_Text_Buffer::call_modify_callbacks( int pos, int nDeleted,
+ int nInserted, int nRestyled, const char *deletedText ) {
+ int i;
+
+ for ( i = 0; i < mNModifyProcs; i++ )
+ ( *mNodifyProcs[ i ] ) ( pos, nInserted, nDeleted, nRestyled,
+ deletedText, mCbArgs[ i ] );
+}
+
+/*
+** Call the stored redisplay procedure(s) for this buffer to update the
+** screen for a change in a selection.
+*/
+void Fl_Text_Buffer::redisplay_selection( Fl_Text_Selection *oldSelection,
+ Fl_Text_Selection *newSelection ) {
+ int oldStart, oldEnd, newStart, newEnd, ch1Start, ch1End, ch2Start, ch2End;
+
+ /* If either selection is rectangular, add an additional character to
+ the end of the selection to request the redraw routines to wipe out
+ the parts of the selection beyond the end of the line */
+ oldStart = oldSelection->mStart;
+ newStart = newSelection->mStart;
+ oldEnd = oldSelection->mEnd;
+ newEnd = newSelection->mEnd;
+ if ( oldSelection->mRectangular )
+ oldEnd++;
+ if ( newSelection->mRectangular )
+ newEnd++;
+
+ /* If the old or new selection is unselected, just redisplay the
+ single area that is (was) selected and return */
+ if ( !oldSelection->mSelected && !newSelection->mSelected )
+ return;
+ if ( !oldSelection->mSelected ) {
+ call_modify_callbacks( newStart, 0, 0, newEnd - newStart, NULL );
+ return;
+ }
+ if ( !newSelection->mSelected ) {
+ call_modify_callbacks( oldStart, 0, 0, oldEnd - oldStart, NULL );
+ return;
+ }
+
+ /* If the selection changed from normal to rectangular or visa versa, or
+ if a rectangular selection changed boundaries, redisplay everything */
+ if ( ( oldSelection->mRectangular && !newSelection->mRectangular ) ||
+ ( !oldSelection->mRectangular && newSelection->mRectangular ) ||
+ ( oldSelection->mRectangular && (
+ ( oldSelection->mRectStart != newSelection->mRectStart ) ||
+ ( oldSelection->mRectEnd != newSelection->mRectEnd ) ) ) ) {
+ call_modify_callbacks( min( oldStart, newStart ), 0, 0,
+ max( oldEnd, newEnd ) - min( oldStart, newStart ), NULL );
+ return;
+ }
+
+ /* If the selections are non-contiguous, do two separate updates
+ and return */
+ if ( oldEnd < newStart || newEnd < oldStart ) {
+ call_modify_callbacks( oldStart, 0, 0, oldEnd - oldStart, NULL );
+ call_modify_callbacks( newStart, 0, 0, newEnd - newStart, NULL );
+ return;
+ }
+
+ /* Otherwise, separate into 3 separate regions: ch1, and ch2 (the two
+ changed areas), and the unchanged area of their intersection,
+ and update only the changed area(s) */
+ ch1Start = min( oldStart, newStart );
+ ch2End = max( oldEnd, newEnd );
+ ch1End = max( oldStart, newStart );
+ ch2Start = min( oldEnd, newEnd );
+ if ( ch1Start != ch1End )
+ call_modify_callbacks( ch1Start, 0, 0, ch1End - ch1Start, NULL );
+ if ( ch2Start != ch2End )
+ call_modify_callbacks( ch2Start, 0, 0, ch2End - ch2Start, NULL );
+}
+
+void Fl_Text_Buffer::move_gap( int pos ) {
+ int gapLen = mGapEnd - mGapStart;
+
+ if ( pos > mGapStart )
+ memmove( &mBuf[ mGapStart ], &mBuf[ mGapEnd ],
+ pos - mGapStart );
+ else
+ memmove( &mBuf[ pos + gapLen ], &mBuf[ pos ], mGapStart - pos );
+ mGapEnd += pos - mGapStart;
+ mGapStart += pos - mGapStart;
+}
+
+/*
+** reallocate the text storage in "buf" to have a gap starting at "newGapStart"
+** and a gap size of "newGapLen", preserving the buffer's current contents.
+*/
+void Fl_Text_Buffer::reallocate_with_gap( int newGapStart, int newGapLen ) {
+ char * newBuf;
+ int newGapEnd;
+
+ newBuf = (char *)malloc( mLength + newGapLen );
+ newGapEnd = newGapStart + newGapLen;
+ if ( newGapStart <= mGapStart ) {
+ memcpy( newBuf, mBuf, newGapStart );
+ memcpy( &newBuf[ newGapEnd ], &mBuf[ newGapStart ],
+ mGapStart - newGapStart );
+ memcpy( &newBuf[ newGapEnd + mGapStart - newGapStart ],
+ &mBuf[ mGapEnd ], mLength - mGapStart );
+ } else { /* newGapStart > mGapStart */
+ memcpy( newBuf, mBuf, mGapStart );
+ memcpy( &newBuf[ mGapStart ], &mBuf[ mGapEnd ],
+ newGapStart - mGapStart );
+ memcpy( &newBuf[ newGapEnd ],
+ &mBuf[ mGapEnd + newGapStart - mGapStart ],
+ mLength - newGapStart );
+ }
+ free( (void *) mBuf );
+ mBuf = newBuf;
+ mGapStart = newGapStart;
+ mGapEnd = newGapEnd;
+#ifdef PURIFY
+{int i; for ( i = mGapStart; i < mGapEnd; i++ ) mBuf[ i ] = '.'; }
+#endif
+}
+
+/*
+** Update all of the selections in "buf" for changes in the buffer's text
+*/
+void Fl_Text_Buffer::update_selections( int pos, int nDeleted,
+ int nInserted ) {
+ mPrimary.update( pos, nDeleted, nInserted );
+ mSecondary.update( pos, nDeleted, nInserted );
+ mHighlight.update( pos, nDeleted, nInserted );
+}
+
+/*
+** Update an individual selection for changes in the corresponding text
+*/
+void Fl_Text_Selection::update( int pos, int nDeleted,
+ int nInserted ) {
+ if ( !mSelected || pos > mEnd )
+ return;
+ if ( pos + nDeleted <= mStart ) {
+ mStart += nInserted - nDeleted;
+ mEnd += nInserted - nDeleted;
+ } else if ( pos <= mStart && pos + nDeleted >= mEnd ) {
+ mStart = pos;
+ mEnd = pos;
+ mSelected = 0;
+ } else if ( pos <= mStart && pos + nDeleted < mEnd ) {
+ mStart = pos;
+ mEnd = nInserted + mEnd - nDeleted;
+ } else if ( pos < mEnd ) {
+ mEnd += nInserted - nDeleted;
+ if ( mEnd <= mStart )
+ mSelected = 0;
+ }
+}
+
+/*
+** Search forwards in buffer "buf" for character "searchChar", starting
+** with the character "startPos", and returning the result in "foundPos"
+** returns 1 if found, 0 if not. (The difference between this and
+** BufSearchForward is that it's optimized for single characters. The
+** overall performance of the text widget is dependent on its ability to
+** count lines quickly, hence searching for a single character: newline)
+*/
+int Fl_Text_Buffer::findchar_forward( int startPos, char searchChar,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+
+ pos = startPos;
+ while ( pos < mGapStart ) {
+ if ( mBuf[ pos ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos++;
+ }
+ while ( pos < mLength ) {
+ if ( mBuf[ pos + gapLen ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos++;
+ }
+ *foundPos = mLength;
+ return 0;
+}
+
+/*
+** Search backwards in buffer "buf" for character "searchChar", starting
+** with the character BEFORE "startPos", returning the result in "foundPos"
+** returns 1 if found, 0 if not. (The difference between this and
+** BufSearchBackward is that it's optimized for single characters. The
+** overall performance of the text widget is dependent on its ability to
+** count lines quickly, hence searching for a single character: newline)
+*/
+int Fl_Text_Buffer::findchar_backward( int startPos, char searchChar,
+ int *foundPos ) {
+ int pos, gapLen = mGapEnd - mGapStart;
+
+ if ( startPos == 0 ) {
+ *foundPos = 0;
+ return 0;
+ }
+ pos = startPos == 0 ? 0 : startPos - 1;
+ while ( pos >= mGapStart ) {
+ if ( mBuf[ pos + gapLen ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos--;
+ }
+ while ( pos >= 0 ) {
+ if ( mBuf[ pos ] == searchChar ) {
+ *foundPos = pos;
+ return 1;
+ }
+ pos--;
+ }
+ *foundPos = 0;
+ return 0;
+}
+
+/*
+** Copy from "text" to end up to but not including newline (or end of "text")
+** and return the copy as the function value, and the length of the line in
+** "lineLen"
+*/
+static char *copyLine( const char *text, int *lineLen ) {
+ int len = 0;
+ const char *c;
+ char *outStr;
+
+ for ( c = text; *c != '\0' && *c != '\n'; c++ )
+ len++;
+ outStr = (char *)malloc( len + 1 );
+ strncpy( outStr, text, len );
+ outStr[ len ] = '\0';
+ *lineLen = len;
+ return outStr;
+}
+
+/*
+** Count the number of newlines in a null-terminated text string;
+*/
+static int countLines( const char *string ) {
+ const char * c;
+ int lineCount = 0;
+
+ for ( c = string; *c != '\0'; c++ )
+ if ( *c == '\n' ) lineCount++;
+ return lineCount;
+}
+
+/*
+** Measure the width in displayed characters of string "text"
+*/
+static int textWidth( const char *text, int tabDist, char nullSubsChar ) {
+ int width = 0, maxWidth = 0;
+ const char *c;
+
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\n' ) {
+ if ( width > maxWidth )
+ maxWidth = width;
+ width = 0;
+ } else
+ width += Fl_Text_Buffer::character_width( *c, width, tabDist, nullSubsChar );
+ }
+ if ( width > maxWidth )
+ return width;
+ return maxWidth;
+}
+
+/*
+** Find the first and last character position in a line within a rectangular
+** selection (for copying). Includes tabs which cross rectStart, but not
+** control characters which do so. Leaves off tabs which cross rectEnd.
+**
+** Technically, the calling routine should convert tab characters which
+** cross the right boundary of the selection to spaces which line up with
+** the edge of the selection. Unfortunately, the additional memory
+** management required in the parent routine to allow for the changes
+** in string size is not worth all the extra work just for a couple of
+** shifted characters, so if a tab protrudes, just lop it off and hope
+** that there are other characters in the selection to establish the right
+** margin for subsequent columnar pastes of this data.
+*/
+void Fl_Text_Buffer::rectangular_selection_boundaries( int lineStartPos,
+ int rectStart, int rectEnd, int *selStart, int *selEnd ) {
+ int pos, width, indent = 0;
+ char c;
+
+ /* find the start of the selection */
+ for ( pos = lineStartPos; pos < mLength; pos++ ) {
+ c = character( pos );
+ if ( c == '\n' )
+ break;
+ width = Fl_Text_Buffer::character_width( c, indent, mTabDist, mNullSubsChar );
+ if ( indent + width > rectStart ) {
+ if ( indent != rectStart && c != '\t' ) {
+ pos++;
+ indent += width;
+ }
+ break;
+ }
+ indent += width;
+ }
+ *selStart = pos;
+
+ /* find the end */
+ for ( ; pos < mLength; pos++ ) {
+ c = character( pos );
+ if ( c == '\n' )
+ break;
+ width = Fl_Text_Buffer::character_width( c, indent, mTabDist, mNullSubsChar );
+ indent += width;
+ if ( indent > rectEnd ) {
+ if ( indent - width != rectEnd && c != '\t' )
+ pos++;
+ break;
+ }
+ }
+ *selEnd = pos;
+}
+
+/*
+** Adjust the space and tab characters from string "text" so that non-white
+** characters remain stationary when the text is shifted from starting at
+** "origIndent" to starting at "newIndent". Returns an allocated string
+** which must be freed by the caller with XtFree.
+*/
+static char *realignTabs( const char *text, int origIndent, int newIndent,
+ int tabDist, int useTabs, char nullSubsChar, int *newLength ) {
+ char * expStr, *outStr;
+ int len;
+
+ /* If the tabs settings are the same, retain original tabs */
+ if ( origIndent % tabDist == newIndent % tabDist ) {
+ len = strlen( text );
+ outStr = (char *)malloc( len + 1 );
+ strcpy( outStr, text );
+ *newLength = len;
+ return outStr;
+ }
+
+ /* If the tab settings are not the same, brutally convert tabs to
+ spaces, then back to tabs in the new position */
+ expStr = expandTabs( text, origIndent, tabDist, nullSubsChar, &len );
+ if ( !useTabs ) {
+ *newLength = len;
+ return expStr;
+ }
+ outStr = unexpandTabs( expStr, newIndent, tabDist, nullSubsChar, newLength );
+ free( (void *) expStr );
+ return outStr;
+}
+
+/*
+** Expand tabs to spaces for a block of text. The additional parameter
+** "startIndent" if nonzero, indicates that the text is a rectangular selection
+** beginning at column "startIndent"
+*/
+static char *expandTabs( const char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen ) {
+ char * outStr, *outPtr;
+ const char *c;
+ int indent, len, outLen = 0;
+
+ /* rehearse the expansion to figure out length for output string */
+ indent = startIndent;
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\t' ) {
+ len = Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ outLen += len;
+ indent += len;
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ outLen++;
+ } else {
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ outLen++;
+ }
+ }
+
+ /* do the expansion */
+ outStr = (char *)malloc( outLen + 1 );
+ outPtr = outStr;
+ indent = startIndent;
+ for ( c = text; *c != '\0'; c++ ) {
+ if ( *c == '\t' ) {
+ len = Fl_Text_Buffer::expand_character( *c, indent, outPtr, tabDist, nullSubsChar );
+ outPtr += len;
+ indent += len;
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ *outPtr++ = *c;
+ } else {
+ indent += Fl_Text_Buffer::character_width( *c, indent, tabDist, nullSubsChar );
+ *outPtr++ = *c;
+ }
+ }
+ outStr[ outLen ] = '\0';
+ *newLen = outLen;
+ return outStr;
+}
+
+/*
+** Convert sequences of spaces into tabs. The threshold for conversion is
+** when 3 or more spaces can be converted into a single tab, this avoids
+** converting double spaces after a period withing a block of text.
+*/
+static char *unexpandTabs( char *text, int startIndent, int tabDist,
+ char nullSubsChar, int *newLen ) {
+ char * outStr, *outPtr, *c, expandedChar[ FL_TEXT_MAX_EXP_CHAR_LEN ];
+ int indent, len;
+
+ outStr = (char *)malloc( strlen( text ) + 1 );
+ outPtr = outStr;
+ indent = startIndent;
+ for ( c = text; *c != '\0'; ) {
+ if ( *c == ' ' ) {
+ len = Fl_Text_Buffer::expand_character( '\t', indent, expandedChar, tabDist,
+ nullSubsChar );
+ if ( len >= 3 && !strncmp( c, expandedChar, len ) ) {
+ c += len;
+ *outPtr++ = '\t';
+ indent += len;
+ } else {
+ *outPtr++ = *c++;
+ indent++;
+ }
+ } else if ( *c == '\n' ) {
+ indent = startIndent;
+ *outPtr++ = *c++;
+ } else {
+ *outPtr++ = *c++;
+ indent++;
+ }
+ }
+ *outPtr = '\0';
+ *newLen = outPtr - outStr;
+ return outStr;
+}
+
+static int max( int i1, int i2 ) {
+ return i1 >= i2 ? i1 : i2;
+}
+
+static int min( int i1, int i2 ) {
+ return i1 <= i2 ? i1 : i2;
+}
+
+int
+Fl_Text_Buffer::insertfile(const char *file, int pos, int buflen) {
+ FILE *fp; int r;
+ if (!(fp = fopen(file, "r"))) return 1;
+ char *buffer = new char[buflen];
+ for (; (r = fread(buffer, 1, buflen - 1, fp)) > 0; pos += r) {
+ buffer[r] = (char)0;
+ insert(pos, buffer);
+ }
+
+ int e = ferror(fp) ? 2 : 0;
+ fclose(fp);
+ delete[] buffer;
+ return e;
+}
+
+int
+Fl_Text_Buffer::outputfile(const char *file, int start, int end, int buflen) {
+ FILE *fp;
+ if (!(fp = fopen(file, "w"))) return 1;
+ for (int n; (n = min(end - start, buflen)); start += n) {
+ const char *p = text_range(start, start + n);
+ int r = fwrite(p, 1, n, fp);
+ free((void *)p);
+ if (r != n) break;
+ }
+
+ int e = ferror(fp) ? 2 : 0;
+ fclose(fp);
+ return e;
+}
+
+
+//
+// End of "$Id: Fl_Text_Buffer.cxx,v 1.9 2001/07/23 09:50:05 spitzak Exp $".
+//