diff options
| author | No Author <No Author> | 2001-08-01 21:24:49 +0000 |
|---|---|---|
| committer | No Author <No Author> | 2001-08-01 21:24:49 +0000 |
| commit | 3cb5ebe0e811f3db008085d985b7761725589a74 (patch) | |
| tree | 0a7184a5f02fffe927af911758f3a9a4a2f4a37e /src/Fl_Text_Buffer.cxx | |
| parent | 4477e166400f197bed50b09e01e695221cde96b6 (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.cxx | 2287 |
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 $". +// |
