/* OpenDoors Online Software Programming Toolkit * (C) Copyright 1991 - 1999 by Brian Pirie. * * Oct-2001 door32.sys/socket modifications by Rob Swindell (www.synchro.net) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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 * * * File: ODEdit.c * * Description: Implementation of the OpenDoors multi-line editor, which * allows the user to edit strings which may span many lines. * Provides standard text editor features, such as word wrap. * * Revisions: Date Ver Who Change * --------------------------------------------------------------- * Dec 07, 1995 6.00 BP Created. * Dec 12, 1995 6.00 BP Added entry, exit and kernel macros. * Dec 30, 1995 6.00 BP Added ODCALL for calling convention. * Jan 04, 1996 6.00 BP Use od_get_input(). * Jan 12, 1996 6.00 BP Claim exclusive use of arrow keys. * Jan 31, 1996 6.00 BP Added timeout for od_get_input(). * Feb 08, 1996 6.00 BP Finished implementation details. * Feb 13, 1996 6.00 BP Added od_get_input() flags parameter. * Feb 16, 1996 6.00 BP New trans. size estimation heuristics. * Feb 19, 1996 6.00 BP Changed version number to 6.00. * Feb 24, 1996 6.00 BP Fixed garbage on [Enter] after w-wrap. * Mar 03, 1996 6.10 BP Begin version 6.10. * Mar 13, 1996 6.10 BP Restore cursor position after menu. * Mar 19, 1996 6.10 BP MSVC15 source-level compatibility. * Jun 08, 1996 6.10 BP Added cast in call to alloc function. * Oct 19, 2001 6.20 RS Eliminated MSVC 6.0 warning. * Aug 10, 2003 6.23 SH *nix support */ #define BUILDING_OPENDOORS #include #include #include #include "OpenDoor.h" #include "ODTypes.h" #include "ODGen.h" #include "ODCore.h" #include "ODKrnl.h" #include "ODStat.h" #include "ODCom.h" #include "ODScrn.h" /* ========================================================================= */ /* Misc. definitions. */ /* ========================================================================= */ /* Macros used by this module. */ #define IS_EOL_CHAR(ch) ((ch) == '\n' || (ch) == '\r' || (ch) == '\0') /* Configurable constants. */ #define LINE_ARRAY_GROW_SIZE 20 #define BUFFER_GROW_SIZE 4096 #define ANSI_SCROLL_DISTANCE 7 #define PRE_DRAIN_TIME 10000 #define MAX_TAB_STOP_SIZE 8 #define DEFAULT_TAB_STOP_SIZE 8 #define DEFAULT_LINE_BREAK "\n" /* Other manifest constants. */ #define REDRAW_NO_BOUNDARY 0xffff /* Default editor options. */ static tODEditOptions ODEditOptionsDefault = { 1, 1, 80, 23, FORMAT_PARAGRAPH_BREAKS, NULL, NULL, EFLAG_NORMAL, }; /* ========================================================================= */ /* Multiline editor instance structure. */ /* ========================================================================= */ typedef struct { char *pszEditBuffer; UINT unBufferSize; tODEditOptions *pUserOptions; UINT unCurrentLine; UINT unCurrentColumn; UINT unLineScrolledToTop; UINT unAreaWidth; UINT unAreaHeight; char **papchStartOfLine; UINT unLineArraySize; UINT unLinesInBuffer; BOOL bInsertMode; UINT unTabStopSize; UINT unScrollDistance; char *pszLineBreak; char *pszParagraphBreak; BOOL bWordWrapLongLines; void *pRememberBuffer; } tEditInstance; /* ========================================================================= */ /* Editor function prototypes. */ /* ========================================================================= */ /* High level implementation. */ static BOOL ODEditSetupInstance(tEditInstance *pEditInstance, char *pszBufferToEdit, UINT unBufferSize, tODEditOptions *pUserOptions); static void ODEditRedrawArea(tEditInstance *pEditInstance); static void ODEditDrawAreaLine(tEditInstance *pEditInstance, UINT unAreaLineToDraw); static INT ODEditMainLoop(tEditInstance *pEditInstance); static void ODEditGotoPreviousLine(tEditInstance *pEditInstance); static void ODEditGotoNextLine(tEditInstance *pEditInstance); static BOOL ODEditScrollArea(tEditInstance *pEditInstance, INT nDistance); static BOOL ODEditRecommendFullRedraw(tEditInstance *pEditInstance, UINT unEstPartialRedrawBytes, BOOL bDefault); static UINT ODEditEstDrawBytes(tEditInstance *pEditInstance, UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine, UINT unFinishRedrawColumn); static UINT ODEditGetCurrentLineInArea(tEditInstance *pEditInstance); static void ODEditUpdateCursorPos(tEditInstance *pEditInstance); static void ODEditUpdateCursorIfMoved(tEditInstance *pEditInstance); static tODResult ODEditEnterText(tEditInstance *pEditInstance, char *pszEntered, BOOL bInsertMode); static void ODEditSetBreakSequence(tEditInstance *pEditInstance, char chFirstEOLChar, char chSecondEOLChar); static BOOL ODEditCursorLeft(tEditInstance *pEditInstance); static void ODEditDeleteCurrentChar(tEditInstance *pEditInstance); static void ODEditDeleteCurrentLine(tEditInstance *pEditInstance); static BOOL ODEditPastEndOfCurLine(tEditInstance *pEditInstance); static size_t ODEditRememberBufferSize(tEditInstance *pEditInstance); static void ODEditRememberArea(tEditInstance *pEditInstance, void *pRememberedArea); static void ODEditRedrawChanged(tEditInstance *pEditInstance, void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary); static BOOL ODEditDetermineChanged(tEditInstance *pEditInstance, void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary, UINT *punStartRedrawLine, UINT *punStartRedrawColumn, UINT *punFinishRedrawLine, UINT *punFinishRedrawColumn); static void ODEditRedrawSubArea(tEditInstance *pEditInstance, UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine, UINT unFinishRedrawColumn); static void ODEditGetActualCurPos(tEditInstance *pEditInstance, UINT *punRow, UINT *punColumn); static BOOL ODEditIsEOLForMode(tEditInstance *pEditInstance, char chToTest); /* Low level buffer manipulation functions. */ static BOOL ODEditBufferFormatAndIndex(tEditInstance *pEditInstance); static UINT ODEditBufferGetLineLength(tEditInstance *pEditInstance, UINT unBufferLine); static UINT ODEditBufferGetTotalLines(tEditInstance *pEditInstance); static char *ODEditBufferGetCharacter(tEditInstance *pEditInstance, UINT unBufferLine, UINT unBufferColumn); static tODResult ODEditBufferMakeSpace(tEditInstance *pEditInstance, UINT unLine, UINT unColumn, UINT unNumChars); static tODResult ODEditTryToGrow(tEditInstance *pEditInstance, UINT unSizeNeeded); /* ========================================================================= */ /* High level editor implementation. */ /* ========================================================================= */ /* ---------------------------------------------------------------------------- * od_multiline_edit() * * Multiline editor function, allows the user to enter or change text that * spans multiple lines. * * Parameters: pszBufferToEdit - Pointer to '\0'-terminated buffer of text * to edit. * * unBufferSize - Size of the buffer, in characters. * * pEditOptions - Pointer to a tODEditOptions structure, or * NULL to use default settings. * * Return: An od_multiline_edit()-specific result code. */ ODAPIDEF INT ODCALL od_multiline_edit(char *pszBufferToEdit, UINT unBufferSize, tODEditOptions *pEditOptions) { tEditInstance EditInstance; INT nToReturn; /* Log function entry if running in trace mode */ TRACE(TRACE_API, "od_node_open()"); /* Initialize OpenDoors if not already done. */ if(!bODInitialized) od_init(); OD_API_ENTRY(); /* Validate parameters. */ if(pszBufferToEdit == NULL || unBufferSize == 0) { od_control.od_error = ERR_PARAMETER; OD_API_EXIT(); return(OD_MULTIEDIT_ERROR); } /* Check the user's terminal supports the required capabilities. */ if(!(od_control.user_ansi || od_control.user_avatar)) { od_control.od_error = ERR_NOGRAPHICS; OD_API_EXIT(); return(OD_MULTIEDIT_ERROR); } /* Initialize editor instance information structure. */ if(!ODEditSetupInstance(&EditInstance, pszBufferToEdit, unBufferSize, pEditOptions)) { OD_API_EXIT(); return(OD_MULTIEDIT_ERROR); } /* Attempt to build the buffer line index and ensure that the buffer */ /* conforms to the format specified by the client application. */ if(!ODEditBufferFormatAndIndex(&EditInstance)) { od_control.od_error = ERR_MEMORY; OD_API_EXIT(); return(OD_MULTIEDIT_ERROR); } /* Claim exclusive use of arrow keys. */ ODStatStartArrowUse(); /* Ensure that all information in the outbound communications buffer */ /* has been sent before starting. This way, we can safely purge the */ /* outbound buffer at any time without loosing anything that was sent */ /* before od_multiline_edit() was called. */ ODWaitDrain(PRE_DRAIN_TIME); /* Draw the initial edit area. */ ODEditRedrawArea(&EditInstance); /* Run the main editor loop. */ nToReturn = ODEditMainLoop(&EditInstance); /* Release exclusive use of arrow keys. */ ODStatEndArrowUse(); /* Set final information which will be available in the user options */ /* structure for the client application to access. */ EditInstance.pUserOptions->pszFinalBuffer = EditInstance.pszEditBuffer; EditInstance.pUserOptions->unFinalBufferSize = unBufferSize; OD_API_EXIT(); return(nToReturn); } /* ---------------------------------------------------------------------------- * ODEditSetupInstance() *** PRIVATE FUNCTION *** * * Initializes editor instance information structure. * * Parameters: pEditInstance - Editor instance information structure. * * pszBufferToEdit - Buffer pointer provided by client. * * unBufferSize - Initial buffer size, as specified by the * client. * * pUserOptions - Editor options specified by the client. * * Return: TRUE on success, FALSE on failure. In the case of failure, * od_control.od_error is set appropriately. */ static BOOL ODEditSetupInstance(tEditInstance *pEditInstance, char *pszBufferToEdit, UINT unBufferSize, tODEditOptions *pUserOptions) { ASSERT(pEditInstance != NULL); ASSERT(pszBufferToEdit != NULL); /* Setup editor instance structure. */ pEditInstance->pszEditBuffer = pszBufferToEdit; pEditInstance->unBufferSize = unBufferSize; if(pUserOptions == NULL) { /* Edit options is just the defaults. */ pEditInstance->pUserOptions = &ODEditOptionsDefault; } else { /* Edit options are supplied by the user. */ pEditInstance->pUserOptions = pUserOptions; /* Initialize any edit options that the user did not setup. */ /* Check that edit area has been initialized. */ if(pUserOptions->nAreaLeft == 0) { pUserOptions->nAreaLeft = ODEditOptionsDefault.nAreaLeft; } if(pUserOptions->nAreaRight == 0) { pUserOptions->nAreaRight = ODEditOptionsDefault.nAreaRight; } if(pUserOptions->nAreaTop == 0) { pUserOptions->nAreaTop = ODEditOptionsDefault.nAreaTop; } if(pUserOptions->nAreaBottom == 0) { pUserOptions->nAreaBottom = ODEditOptionsDefault.nAreaBottom; } } pEditInstance->unCurrentLine = 0; pEditInstance->unCurrentColumn = 0; pEditInstance->unLineScrolledToTop = 0; pEditInstance->papchStartOfLine = NULL; pEditInstance->unLineArraySize = 0; pEditInstance->unLinesInBuffer = 0; pEditInstance->unAreaWidth = (UINT)pEditInstance->pUserOptions-> nAreaRight - (UINT)pEditInstance->pUserOptions->nAreaLeft + 1; pEditInstance->unAreaHeight = (UINT)pEditInstance->pUserOptions-> nAreaBottom - (UINT)pEditInstance->pUserOptions->nAreaTop + 1; pEditInstance->bInsertMode = TRUE; pEditInstance->unTabStopSize = DEFAULT_TAB_STOP_SIZE; /* Setup line break and paragraph break sequences, if they can be */ /* determined at this point. If they can't be determined, set them */ /* to NULL. */ switch(pEditInstance->pUserOptions->TextFormat) { case FORMAT_FTSC_MESSAGE: /* FTSC compliant messages use \r as a paragraph break, and do */ /* not have any line break characters. */ pEditInstance->pszLineBreak = ""; pEditInstance->pszParagraphBreak = "\r"; break; case FORMAT_PARAGRAPH_BREAKS: /* Paragraph break mode only inserts CR/LF sequences at the end */ /* of a paragrah, and word-wraps the text that forms a paragrah. */ pEditInstance->pszLineBreak = ""; pEditInstance->pszParagraphBreak = NULL; break; case FORMAT_LINE_BREAKS: case FORMAT_NO_WORDWRAP: /* Line break mode and no word wrap mode both terminate every */ /* line of the file with a CR/LF sequence, and have no paragrah */ /* terminator. In line break mode, word wrap is enabled, whereas */ /* it is not in FORMAT_NO_WORDWRAP mode. */ pEditInstance->pszLineBreak = NULL; pEditInstance->pszParagraphBreak = ""; break; default: /* An invalid text format was specified. */ od_control.od_error = ERR_PARAMETER; return(FALSE); } /* Determine whether long lines sould be word wrapped or character */ /* wrapped. */ pEditInstance->bWordWrapLongLines = (pEditInstance->pUserOptions->TextFormat != FORMAT_NO_WORDWRAP); /* Attempt to allocate abuffer for remembered data. */ pEditInstance->pRememberBuffer = malloc(ODEditRememberBufferSize(pEditInstance)); if(pEditInstance->pRememberBuffer == NULL) { od_control.od_error = ERR_MEMORY; return(FALSE); } /* If AVATAR mode or local mode is active, then scroll up or down one */ /* line at a time. */ if(od_control.user_avatar || od_control.baud == 0) { pEditInstance->unScrollDistance = 1; } /* In ANSI mode with a remote connection, scroll multiple lines at a */ /* time. This is the minimum of the default scroll distance, and the */ /* current height of the edit area - 1. */ else { pEditInstance->unScrollDistance = MIN(ANSI_SCROLL_DISTANCE, pEditInstance->pUserOptions->nAreaBottom - pEditInstance->pUserOptions->nAreaTop); } /* Return with success. */ return(TRUE); } /* ---------------------------------------------------------------------------- * ODEditRedrawArea() *** PRIVATE FUNCTION *** * * Redraws the area of the screen used by the editor. * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditRedrawArea(tEditInstance *pEditInstance) { UINT unAreaLine; ASSERT(pEditInstance != NULL); ODScrnEnableCaret(FALSE); /* First, remove anything that is still in the outbound communications */ /* buffer, since whatever it was, it will no longer be visible after */ /* the screen redraw anyhow. */ if(od_control.baud != 0) ODComClearOutbound(hSerialPort); /* Loop, drawing every line in the edit area. */ for(unAreaLine = 0; unAreaLine < pEditInstance->unAreaHeight; ++unAreaLine) { ODEditDrawAreaLine(pEditInstance, unAreaLine); } ODScrnEnableCaret(TRUE); } /* ---------------------------------------------------------------------------- * ODEditDrawAreaLine() *** PRIVATE FUNCTION *** * * Redraws the specified line in the area of the screen used by the editor. * * Parameters: pEditInstance - Editor instance information structure. * * unAreaLineToDraw - 0-based line number in the edit area to be * redrawn. * * Return: void */ static void ODEditDrawAreaLine(tEditInstance *pEditInstance, UINT unAreaLineToDraw) { UINT unBufferLine; UINT unLineLength; ASSERT(pEditInstance != NULL); ASSERT(unAreaLineToDraw >= 0); ASSERT(unAreaLineToDraw < pEditInstance->unAreaHeight); /* Determine the buffer line that is displayed on this screen line. */ unBufferLine = unAreaLineToDraw + pEditInstance->unLineScrolledToTop; /* Position the cursor to the beginning of this line. */ od_set_cursor((UINT)pEditInstance->pUserOptions->nAreaTop + unAreaLineToDraw, (UINT)pEditInstance->pUserOptions->nAreaLeft); /* If this line is not beyond the end of the buffer. */ if(unBufferLine < pEditInstance->unLinesInBuffer) { /* Determine the length of this buffer line. */ unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine); od_disp(ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0), unLineLength, TRUE); } else { unLineLength = 0; } /* If right edge of edit area aligns with the right edge of the screen. */ if(pEditInstance->pUserOptions->nAreaRight == OD_SCREEN_WIDTH) { /* Clear the remainder of this line on the screen. */ od_clr_line(); } else { /* Place spaces after the end of the current line, up to right edge of */ /* the edit area. */ od_repeat(' ', (BYTE)(pEditInstance->unAreaWidth - unLineLength)); } } /* ---------------------------------------------------------------------------- * ODEditMainLoop() *** PRIVATE FUNCTION *** * * Implements the main editor loop, which repeatedly waits for input from the * user, until the user chooses to exit. * * Parameters: pEditInstance - Editor instance information structure. * * Return: Value to be returned by od_multiline_edit. */ static INT ODEditMainLoop(tEditInstance *pEditInstance) { tODInputEvent InputEvent; ASSERT(pEditInstance != NULL); /* Set initial cursor position. */ ODEditUpdateCursorPos(pEditInstance); /* Loop, obtaining keystrokes until the user chooses to exit. */ for(;;) { od_get_input(&InputEvent, OD_NO_TIMEOUT, GETIN_NORMAL); if(InputEvent.EventType == EVENT_EXTENDED_KEY) { switch(InputEvent.chKeyPress) { case OD_KEY_UP: /* If we aren't at the start of the file, then move to the */ /* previous line. */ if(pEditInstance->unCurrentLine > 0) { ODEditGotoPreviousLine(pEditInstance); ODEditUpdateCursorPos(pEditInstance); } break; case OD_KEY_DOWN: /* If we aren't at the end of the file, then move to the */ /* next line. */ if(pEditInstance->unCurrentLine < ODEditBufferGetTotalLines(pEditInstance) - 1) { ODEditGotoNextLine(pEditInstance); ODEditUpdateCursorPos(pEditInstance); } break; case OD_KEY_LEFT: /* Attempt to move the cursor left. */ if(ODEditCursorLeft(pEditInstance)) { /* If it was possible to move the cursor, then update the */ /* cursor position on the screen. */ ODEditUpdateCursorPos(pEditInstance); } break; case OD_KEY_RIGHT: /* In word wrap mode, we allow the cursor to move up to the */ /* end of this line, and then wrap around to the next line. */ if(pEditInstance->bWordWrapLongLines) { if(pEditInstance->unCurrentColumn < ODEditBufferGetLineLength (pEditInstance, pEditInstance->unCurrentLine)) { ++pEditInstance->unCurrentColumn; ODEditUpdateCursorPos(pEditInstance); } else if(pEditInstance->unCurrentLine < ODEditBufferGetTotalLines(pEditInstance) - 1) { ODEditGotoNextLine(pEditInstance); pEditInstance->unCurrentColumn = 0; ODEditUpdateCursorPos(pEditInstance); } } /* In character wrap mode, we allow the cursor to move up to */ /* the right edge of the edit area. */ else { if(pEditInstance->unCurrentColumn < pEditInstance->unAreaWidth - 1) { ++pEditInstance->unCurrentColumn; ODEditUpdateCursorPos(pEditInstance); } } break; case OD_KEY_HOME: pEditInstance->unCurrentColumn = 0; ODEditUpdateCursorPos(pEditInstance); break; case OD_KEY_END: pEditInstance->unCurrentColumn = ODEditBufferGetLineLength( pEditInstance, pEditInstance->unCurrentLine); ODEditUpdateCursorPos(pEditInstance); break; case OD_KEY_PGUP: if(pEditInstance->unLineScrolledToTop > 0) { UINT unDistance = MIN(pEditInstance->unAreaHeight - 1, pEditInstance->unLineScrolledToTop); ODEditScrollArea(pEditInstance, -((INT)unDistance)); pEditInstance->unCurrentLine -= unDistance; ODEditUpdateCursorPos(pEditInstance); } else if(pEditInstance->unCurrentLine != 0) { pEditInstance->unCurrentLine = 0; ODEditUpdateCursorPos(pEditInstance); } break; case OD_KEY_PGDN: if(pEditInstance->unLineScrolledToTop < pEditInstance->unLinesInBuffer - 1) { UINT unDistance = MIN(pEditInstance->unAreaHeight - 1, pEditInstance->unLinesInBuffer - pEditInstance->unLineScrolledToTop - 1); ODEditScrollArea(pEditInstance, (INT)unDistance); pEditInstance->unCurrentLine = MIN( pEditInstance->unCurrentLine + unDistance, pEditInstance->unLinesInBuffer - 1); ODEditUpdateCursorPos(pEditInstance); } break; case OD_KEY_INSERT: /* If the insert key is pressed, then toggle insert mode. */ pEditInstance->bInsertMode = !pEditInstance->bInsertMode; break; case OD_KEY_DELETE: /* Delete the character at the current position. */ /* If we are currently past the end of this line. */ if(ODEditPastEndOfCurLine(pEditInstance)) { /* Add spaces to this line to fill it up to the current */ /* cursor position. */ switch(ODEditBufferMakeSpace(pEditInstance, pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn, 0)) { case kODRCUnrecoverableFailure: /* If we encountered an unrecoverable failure, then */ /* exit from the editor with a memory allocation */ /* error. */ od_control.od_error = ERR_MEMORY; return(OD_MULTIEDIT_ERROR); case kODRCSuccess: /* On success, delete the current character. */ ODEditDeleteCurrentChar(pEditInstance); break; default: /* On any other failure, just beep and continue. */ od_putch('\a'); } } else { /* If we aren't pas the end of the line, then just do a */ /* simple delete character operation. */ ODEditDeleteCurrentChar(pEditInstance); } break; } } else if(InputEvent.EventType == EVENT_CHARACTER) { if(InputEvent.chKeyPress == 25) { /* Control-Y (delete line) has been pressed. */ ODEditDeleteCurrentLine(pEditInstance); } else if(InputEvent.chKeyPress == 26 || InputEvent.chKeyPress == 27) { /* Escape or control-Z has been pressed. */ /* If a menu callback function has been provided by */ /* the client, then call it. */ if(pEditInstance->pUserOptions->pfMenuCallback != NULL) { /* Call the menu callback function. */ switch((*pEditInstance->pUserOptions->pfMenuCallback)(NULL)) { case EDIT_MENU_EXIT_EDITOR: return(OD_MULTIEDIT_SUCCESS); case EDIT_MENU_DO_NOTHING: /* Continue in the editor without doing anything. */ break; default: ASSERT(FALSE); } /* If we are continuing, then restore initial cursor pos. */ ODEditUpdateCursorPos(pEditInstance); } else { /* If a menu key callback function has not been provided, */ /* then we exit the editor unconditionally. */ return(OD_MULTIEDIT_SUCCESS); } } else if(InputEvent.chKeyPress == '\b') { /* Backspace key has been pressed. */ /* If the cursor is past the end of the line, then we just move */ /* the cursor left, without deleting any characters. */ BOOL bDelete = !ODEditPastEndOfCurLine(pEditInstance); /* Backup the cursor one space. */ if(ODEditCursorLeft(pEditInstance)) { /* If there was space to move the cursor back to, then */ /* proceed and remove the character at the current position. */ if(bDelete) { ODEditDeleteCurrentChar(pEditInstance); } else { /* In this case, we must still show the new cursor */ /* position. */ ODEditUpdateCursorPos(pEditInstance); } } } else if(InputEvent.chKeyPress == '\t') { char szTextToAdd[MAX_TAB_STOP_SIZE + 1]; UINT unTargetColumn; UINT unTargetDistance; /* A tab key has been entered. */ /* Determine the column that this will move the cursor to. */ ASSERT(pEditInstance->unTabStopSize <= MAX_TAB_STOP_SIZE); unTargetColumn = ((pEditInstance->unCurrentColumn / pEditInstance-> unTabStopSize) + 1) * pEditInstance->unTabStopSize; /* In insert mode, then insert spaces into the buffer. */ if(pEditInstance->bInsertMode) { /* Determine the number of columns that we need to advance in */ /* order to reach this target column. */ unTargetDistance = unTargetColumn - pEditInstance->unCurrentColumn; ASSERT(unTargetDistance <= MAX_TAB_STOP_SIZE); /* Translate this to a string with the appropriate number of */ /* spaces. */ memset(szTextToAdd, ' ', unTargetDistance); szTextToAdd[unTargetDistance] = '\0'; /* Add this to the buffer. */ if(ODEditEnterText(pEditInstance, szTextToAdd, TRUE) == kODRCUnrecoverableFailure) { od_control.od_error = ERR_MEMORY; return(OD_MULTIEDIT_ERROR); } } /* In overwrite mode, then just advance the cursor position. */ else { /* Determine the column where the cursor should be wrapped. */ UINT unWrapColumn = pEditInstance->bWordWrapLongLines ? ODEditBufferGetLineLength(pEditInstance, pEditInstance->unCurrentLine) : pEditInstance->unAreaWidth; if(unTargetColumn < unWrapColumn) { pEditInstance->unCurrentColumn = unTargetColumn; ODEditUpdateCursorPos(pEditInstance); } else if(pEditInstance->unCurrentLine < ODEditBufferGetTotalLines(pEditInstance) - 1) { ODEditGotoNextLine(pEditInstance); pEditInstance->unCurrentColumn = 0; ODEditUpdateCursorPos(pEditInstance); } } } else if(InputEvent.chKeyPress == '\r') { char *pszTextToAdd; /* The enter key has been pressed. In insert mode, or at the end */ /* of the buffer in overwrite mode, this adds a new line. */ if(pEditInstance->bInsertMode || pEditInstance->unCurrentLine >= ODEditBufferGetTotalLines(pEditInstance) - 1) { if(!pEditInstance->bInsertMode) { /* If we are not in insert mode, begin by positioning the */ /* cursor at the end of this line. */ pEditInstance->unCurrentColumn = ODEditBufferGetLineLength( pEditInstance, pEditInstance->unCurrentLine); } /* Determine the line/paragraph break sequence to use. */ if(pEditInstance->pszLineBreak != NULL && strlen(pEditInstance->pszLineBreak) > 0) { pszTextToAdd = pEditInstance->pszLineBreak; } else if(pEditInstance->pszParagraphBreak != NULL && strlen(pEditInstance->pszParagraphBreak) > 0) { pszTextToAdd = pEditInstance->pszParagraphBreak; } else { pszTextToAdd = DEFAULT_LINE_BREAK; } /* Insert the sequence into the buffer. */ if(ODEditEnterText(pEditInstance, pszTextToAdd, TRUE) == kODRCUnrecoverableFailure) { od_control.od_error = ERR_MEMORY; return(OD_MULTIEDIT_ERROR); } } else { /* Pressing the enter key in overwrite mode just moves the */ /* cursor to the beginning of the next line. In other words, */ /* it is equivalent to pressing Down arrow followed by home. */ ODEditGotoNextLine(pEditInstance); pEditInstance->unCurrentColumn = 0; ODEditUpdateCursorPos(pEditInstance); } } else if(InputEvent.chKeyPress >= 32) { char szTextToAdd[2]; szTextToAdd[0] = InputEvent.chKeyPress; szTextToAdd[1] = '\0'; /* A valid buffer character has been entered. */ if(ODEditEnterText(pEditInstance, szTextToAdd, (BOOL)(pEditInstance->bInsertMode || ODEditPastEndOfCurLine(pEditInstance))) == kODRCUnrecoverableFailure) { od_control.od_error = ERR_MEMORY; return(OD_MULTIEDIT_ERROR); } } } } } /* ---------------------------------------------------------------------------- * ODEditGotoPreviousLine() *** PRIVATE FUNCTION *** * * Moves the current cursor position to the previous line, scrolling the screen * if necessary to keep the cursor visible. * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditGotoPreviousLine(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); /* If we are already at the first line, then return without doing */ /* anything. */ if(pEditInstance->unCurrentLine == 0) return; /* If cursor is at top of edit area, then scroll area */ /* first. */ if(ODEditGetCurrentLineInArea(pEditInstance) == 0) { ODEditScrollArea(pEditInstance, -(INT)(MIN(pEditInstance->unScrollDistance, pEditInstance->unCurrentLine))); } /* Move cursor to previous line. */ --pEditInstance->unCurrentLine; } /* ---------------------------------------------------------------------------- * ODEditGotoNextLine() *** PRIVATE FUNCTION *** * * Advances the current cursor position to the next line, scrolling the screen * if necessary to keep the cursor visible. * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditGotoNextLine(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); /* If we are already at the end of the file, then return without */ /* doing anything. */ if(pEditInstance->unCurrentLine >= ODEditBufferGetTotalLines(pEditInstance) - 1) { return; } /* If cursor is at the bottom of the edit area, then scroll area first. */ if(ODEditGetCurrentLineInArea(pEditInstance) == pEditInstance->unAreaHeight - 1) { ODEditScrollArea(pEditInstance, (INT)MIN(pEditInstance->unScrollDistance, ODEditBufferGetTotalLines(pEditInstance) - pEditInstance->unCurrentLine)); } /* Move cursor to next line. */ ++pEditInstance->unCurrentLine; } /* ---------------------------------------------------------------------------- * ODEditScrollArea() *** PRIVATE FUNCTION *** * * Scrolls the edit area up or down the specified distance, redrawing newly * "exposed" lines. * * Parameters: pEditInstance - Editor instance information structure. * * nDistance - Number of lines to scroll, where a positive * value moves the text upwards, and a negative * value moves the text down. * * Return: FALSE if a full redraw has been performed, TRUE if an efficient * scroll command has been used. */ static BOOL ODEditScrollArea(tEditInstance *pEditInstance, INT nDistance) { BOOL bUseScrollCommand = FALSE; UINT unAreaLine; UINT unBufferLine; UINT unFirstAreaLineToDraw; UINT unLastAreaLineToDraw; UINT unPositiveDistance; ASSERT(pEditInstance); /* If scroll distance is zero, then we don't need to do anything at all. */ if(nDistance == 0) { return(TRUE); } /* Otherwise, obtain the absolute value of the distance as an unsigned */ /* integer. */ else if(nDistance < 0) { unPositiveDistance = (UINT)-nDistance; } else { unPositiveDistance = (UINT)nDistance; } /* In AVATAR mode, if more than one of the currently visible lines will */ /* still be visible after scrolling, then we will consider using the */ /* scroll operation. */ if(od_control.user_avatar && ((INT)pEditInstance->unAreaHeight) - ((INT)unPositiveDistance) > 1) { /* Even under this situation, we only want to use the scroll operation */ /* if the amount of data still in the outbound buffer + our estimate */ /* of the amount of data that will be sent to perform the scroll */ /* operation is less than our estimate of the amount of data that */ /* would be sent by a complete screen redraw. */ UINT unEstimatedScrollData = ((pEditInstance->unAreaWidth + 4) * unPositiveDistance) + 7; if(!ODEditRecommendFullRedraw(pEditInstance, unEstimatedScrollData, TRUE)) { bUseScrollCommand = TRUE; } } /* In local mode, we can also use the scroll command for efficiency. */ if(od_control.baud == 0) { bUseScrollCommand = TRUE; } /* Area scroll is achieved by one of two means. We either use the scroll */ /* command, and then draw just the newly visible lines, or we redraw the */ /* entire edit area, after removing any data from the outbound */ /* communications buffer. */ if(bUseScrollCommand) { /* Use the od_scroll() function to scroll the screen contents. */ od_scroll(pEditInstance->pUserOptions->nAreaLeft, pEditInstance->pUserOptions->nAreaTop, pEditInstance->pUserOptions->nAreaRight, pEditInstance->pUserOptions->nAreaBottom, nDistance, SCROLL_NO_CLEAR); /* Fill the newly visible lines. First, the portion of the area that */ /* requires redrawing is determined, and then a loop redraws the lines */ /* that must be drawn. */ /* If we are moving text upwards, exposing new lines at the bottom of */ /* the area: */ if(nDistance > 0) { ASSERT(pEditInstance->unLineScrolledToTop + unPositiveDistance < pEditInstance->unLinesInBuffer); pEditInstance->unLineScrolledToTop += unPositiveDistance; unFirstAreaLineToDraw = pEditInstance->unAreaHeight - (UINT)unPositiveDistance; unLastAreaLineToDraw = pEditInstance->unAreaHeight - 1; } /* Otherwise, we have moved text downwards, exposing new lines at the */ /* top of the edit area. */ else { ASSERT(pEditInstance->unLineScrolledToTop >= unPositiveDistance); pEditInstance->unLineScrolledToTop -= unPositiveDistance; unFirstAreaLineToDraw = 0; unLastAreaLineToDraw = unPositiveDistance - 1; } ODScrnEnableCaret(FALSE); /* Now, redraw the new lines. */ unBufferLine = unFirstAreaLineToDraw + pEditInstance->unLineScrolledToTop; for(unAreaLine = unFirstAreaLineToDraw; unAreaLine <= unLastAreaLineToDraw; ++unAreaLine, ++unBufferLine) { /* Draw the entire line. */ ODEditDrawAreaLine(pEditInstance, unAreaLine); } ODScrnEnableCaret(TRUE); } /* Just redraw the entire edit area. */ else { /* Adjust the line number that is scrolled to the top of the screen. */ if(nDistance > 0) { pEditInstance->unLineScrolledToTop += unPositiveDistance; } else { pEditInstance->unLineScrolledToTop -= unPositiveDistance; } /* Perform redraw, first purging outbound buffer. */ ODEditRedrawArea(pEditInstance); } return(bUseScrollCommand); } /* ---------------------------------------------------------------------------- * ODEditRecommendFullRedraw() *** PRIVATE FUNCTION *** * * Determines whether it would be more efficient to add the specified number * of bytes to the outbound buffer as part of an incremental redraw, or if * it would be more efficient to just purge the outbound buffer and do a * complete redraw of the edit area. * * Parameters: pEditInstance - Editor instance information structure. * * unEstPartialRedrawBytes - Estimate of the number of bytes that * would be transmitted if an incremental * redraw is performed. * * bDefault - The default action (TRUE for full * redraw, FALSE for incremental) if the * number of bytes in the outbound buffer * cannot be determined. * * Return: TRUE if a full redraw is recommended, FALSE if an the * incremental redraw is recommended. */ static BOOL ODEditRecommendFullRedraw(tEditInstance *pEditInstance, UINT unEstPartialRedrawBytes, BOOL bDefault) { int nOutboundBufferBytes; UINT unEstFullRedrawBytes; /* In local mode, just return the default action. */ if(od_control.baud == 0) { return(bDefault); } /* Attempt to obtain the number of bytes in the communications outbound */ /* buffer. Unfortunately, this information may not be available. For */ /* example, FOSSIL drivers will only report whether or not there is */ /* still data in the outbound buffer, but not a count of the number of */ /* bytes in the buffer. Under such a situation, ODComOutbound() returns */ /* SIZE_NON_ZERO if there is data in the buffer, and 0 if there is no */ /* data in the buffer. This is not a problem under OpenDoor's internal */ /* serial I/O code, nor is it a problem under Win32's communications */ /* facilities. */ ODComOutbound(hSerialPort, &nOutboundBufferBytes); if(nOutboundBufferBytes == SIZE_NON_ZERO) { /* We know that there is data in the outbound buffer, but we don't */ /* know how much, and so we cannot make a recommendation. Instead, */ /* the default course of action will be taken. */ return(bDefault); } /* Estimate the # of bytes required for a full redraw of the edit area. */ unEstFullRedrawBytes = ODEditEstDrawBytes(pEditInstance, 0, 0, pEditInstance->unAreaHeight - 1, pEditInstance->unAreaWidth); /* Recommend a full redraw if the number of bytes for an incremental */ /* redraw plus the number of bytes already in the outbound buffer */ /* exceed the number of bytes required for a full redraw. */ if(unEstPartialRedrawBytes + (UINT)nOutboundBufferBytes > unEstFullRedrawBytes) { return(TRUE); } else { return(FALSE); } } /* ---------------------------------------------------------------------------- * ODEditEstDrawBytes() *** PRIVATE FUNCTION *** * * Estimates the number of bytes which will be transmitted in order to redraw * the specified portion of the edit area. * * Parameters: pEditInstance - Editor instance information structure. * * unStartRedrawLine - Line of first character to draw. * * unStartRedrawColumn - Column of first character to draw. * * unFinishRedrawLine - Line of last character to draw. * * unFinishRedrawColumn - Column after last character to draw. * * Return: A rough estimate of the number of bytes required for the redraw. */ static UINT ODEditEstDrawBytes(tEditInstance *pEditInstance, UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine, UINT unFinishRedrawColumn) { UINT unAreaLine; UINT unBufferLine; UINT unLineLength; UINT unByteTally = 0; /* If we are only drawing text on a single line, then estimate is just */ /* the distance between the start and finish redraw column. This number */ /* is precise only if the cursor is already at the location where */ /* output is to begin, and the final cursor position is the location */ /* where output finishes. This is in fact the situation when the user */ /* is entering new text in the middle of the line - the most common */ /* situation that will be encountered. */ if(unStartRedrawLine == unFinishRedrawLine) { return(unFinishRedrawColumn - unStartRedrawColumn); } /* If we are drawing text on multiple lines, then inspect the contents */ /* of those lines to estimate the number of bytes to be transmitted. */ for(unAreaLine = unStartRedrawLine, unBufferLine = pEditInstance->unLineScrolledToTop + unStartRedrawLine; unAreaLine <= unFinishRedrawLine; ++unAreaLine, ++unBufferLine) { /* Determine the length of this line. */ if(unBufferLine < pEditInstance->unLinesInBuffer) { unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine); if(unAreaLine == unStartRedrawLine) { unLineLength -= unStartRedrawColumn; } } else { unLineLength = 0; } /* Add the number of characters on this line, along with the number of */ /* bytes required to reposition the cursor and to clear the unused */ /* portion of this line to the tally. This assumes that the edit area */ /* spans the entire screen. */ unByteTally += unLineLength + 7; } return(unByteTally); } /* ---------------------------------------------------------------------------- * ODEditGetCurrentLineInArea() *** PRIVATE FUNCTION *** * * Determines which line of the edit area the cursor is currently located in. * * Parameters: pEditInstance - Editor instance information structure. * * Return: 0-based index of the distance from the top of the edit area * (as specified in the user options structure) where the * cursor is currently located. */ static UINT ODEditGetCurrentLineInArea(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); return(pEditInstance->unCurrentLine - pEditInstance->unLineScrolledToTop); } /* ---------------------------------------------------------------------------- * ODEditUpdateCursorPos() *** PRIVATE FUNCTION *** * * Unconditionally updates the position of the cursor on the screen to * reflect its actual position. Compare with ODEditUpdateCursorIfMoved(). * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditUpdateCursorPos(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); /* Reposition the cursor on the screen. */ od_set_cursor(ODEditGetCurrentLineInArea(pEditInstance) + pEditInstance->pUserOptions->nAreaTop, pEditInstance->unCurrentColumn + pEditInstance->pUserOptions->nAreaLeft); } /* ---------------------------------------------------------------------------- * ODEditUpdateCursorIfMoved() *** PRIVATE FUNCTION *** * * Updates the position of the cursor on the screen to reflec its actual * position only if we belive it isn't already there. Compare with * ODEditUpdateCursorPos(). * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditUpdateCursorIfMoved(tEditInstance *pEditInstance) { UINT unActualRow; UINT unActualColumn; ODEditGetActualCurPos(pEditInstance, &unActualRow, &unActualColumn); if(!(unActualRow == ODEditGetCurrentLineInArea(pEditInstance) + pEditInstance->pUserOptions->nAreaTop && unActualColumn == pEditInstance->unCurrentColumn + pEditInstance->pUserOptions->nAreaLeft)) { ODEditUpdateCursorPos(pEditInstance); } } /* ---------------------------------------------------------------------------- * ODEditEnterText() *** PRIVATE FUNCTION *** * * Inserts new text at the current cursor position, updating the cursor * position accordingly. * * Parameters: pEditInstance - Editor instance information structure. * * pszEntered - The character(s) to be inserted. * * Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to * obtain enough buffer space before making any changes, or * kODRCUnrecoverableFailure if the buffer has been changed, but * there was insufficient memory to re-index the buffer. */ static tODResult ODEditEnterText(tEditInstance *pEditInstance, char *pszEntered, BOOL bInsertMode) { UINT unNumCharsToAdd; char *pch; tODResult Result; ASSERT(pEditInstance != NULL); ASSERT(pszEntered != NULL); /* Remember initial edit area contents, to permit selective redraw. */ ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer); /* Determine the number of characters that are to be added to the buffer. */ unNumCharsToAdd = strlen(pszEntered); /* Make room in the buffer for the new characters, if needed. */ if(bInsertMode) { Result = ODEditBufferMakeSpace(pEditInstance, pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn, unNumCharsToAdd); if(Result != kODRCSuccess) { /* Beep on failure. */ od_putch('\a'); return(Result); } } /* Copy the new characters to the buffer. */ pch = ODEditBufferGetCharacter(pEditInstance, pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn); memcpy(pch, pszEntered, unNumCharsToAdd); /* Move the cursor position to the end of the newly added text. The */ /* cursor position may be temporarily assigned to a position that is */ /* past the end of the edit area or past the end of the buffer. */ for(pch = pszEntered; *pch != '\0'; ++pch) { if(IS_EOL_CHAR(*pch)) { /* A carriage return character advances the cursor to the */ /* leftmost column on the next line. */ pEditInstance->unCurrentColumn = 0; pEditInstance->unCurrentLine++; /* If the next character is a different EOL character, and is */ /* not the end of the string, then skip that character. */ if(IS_EOL_CHAR(pch[1]) && pch[1] != '\0' && pch[1] != *pch) { ++pch; } } else { /* All other characters move the cursor ahead one column. */ pEditInstance->unCurrentColumn++; } } /* Reindex and reformat the buffer based on its new contents. */ if(!ODEditBufferFormatAndIndex(pEditInstance)) { return(kODRCUnrecoverableFailure); } /* Check whether the cursor is now positioned past the end of the edit */ /* area, requiring the edit area to be scrolled up. */ if(ODEditGetCurrentLineInArea(pEditInstance) >= pEditInstance->unAreaHeight) { /* We need to scroll the area accordingly. */ /* Distance to scroll is maximum of the current single-step scroll */ /* distance, and the distance that the cursor is positioned below */ /* the bottom of the current edit area. */ UINT unScrollDistance = MAX(pEditInstance->unScrollDistance, ODEditGetCurrentLineInArea(pEditInstance) - pEditInstance->unAreaHeight + 1); /* Perform actual scroll operation. */ if(ODEditScrollArea(pEditInstance, (INT)unScrollDistance)) { /* Entire area wasn't redrawn by operation, so some redrawing */ /* may still be required, within the area of text that was */ /* visible before the scroll operation. */ ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer, 0, pEditInstance->unAreaHeight - unScrollDistance); } } /* Perform necessary redrawing and reposition the cursor if needed. */ ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer, REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY); /* Return with success. */ return(kODRCSuccess); } /* ---------------------------------------------------------------------------- * ODEditSetBreakSequence() *** PRIVATE FUNCTION *** * * If the default line or paragraph break sequence has not yet been set, then * this function sets it based on the break sequence that was encountered in * the buffer supplied by the client application. * * Parameters: pEditInstance - Editor instance information structure. * * chFirstEOLChar - First character in the encountered sequence. * * chSecondEOLChar - Second character in the encountered sequence. * * Return: void */ static void ODEditSetBreakSequence(tEditInstance *pEditInstance, char chFirstEOLChar, char chSecondEOLChar) { char *pszSequence; ASSERT(pEditInstance != NULL); if(pEditInstance->pszParagraphBreak != NULL && pEditInstance->pszLineBreak != NULL) { /* In this situation, both the default line break sequence and default */ /* paragraph break sequence have been set, so there is nothing for us */ /* to do. */ return; } /* Obtain a pointer to the encountered sequence. We want to use a static */ /* string constant for this, so that the string will continue to exist, */ /* in unchanged form. */ if(chFirstEOLChar == '\r' && chSecondEOLChar == '\0') { pszSequence = "\r"; } else if(chFirstEOLChar == '\n' && chSecondEOLChar == '\0') { pszSequence = "\n"; } else if(chFirstEOLChar == '\n' && chSecondEOLChar == '\r') { pszSequence = "\n\r"; } else if(chFirstEOLChar == '\r' && chSecondEOLChar == '\n') { pszSequence = "\r\n"; } else { /* This should never happen: an invalid end of line sequence was */ /* passed in. */ pszSequence = NULL; ASSERT(FALSE); } /* Set the as yet undetermined line/paragraph terminators. */ if(pEditInstance->pszParagraphBreak == NULL) { pEditInstance->pszParagraphBreak = pszSequence; } if(pEditInstance->pszLineBreak == NULL) { pEditInstance->pszLineBreak = pszSequence; } } /* ---------------------------------------------------------------------------- * ODEditCursorLeft() *** PRIVATE FUNCTION *** * * Attempts to move the cursor left. Called when the user presses the left * arrow key, and is also called as part of the process of handling the * backspace key. * * Parameters: pEditInstance - Editor instance information structure. * * Return: TRUE on success, or FALSE if the cursor cannot be moved left any * further. */ static BOOL ODEditCursorLeft(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); /* In word wrap mode, pressing the left key when the cursor */ /* is past the end of the line jumps the cursor to the end */ /* of the line. */ if(pEditInstance->bWordWrapLongLines && pEditInstance->unCurrentColumn > ODEditBufferGetLineLength (pEditInstance, pEditInstance->unCurrentLine)) { pEditInstance->unCurrentColumn = ODEditBufferGetLineLength (pEditInstance, pEditInstance->unCurrentLine); return(TRUE); } /* If we are not already at the leftmost column. */ else if(pEditInstance->unCurrentColumn > 0) { /* Move left one column. */ --pEditInstance->unCurrentColumn; return(TRUE); } else if(pEditInstance->bWordWrapLongLines) { /* In word wrap mode, this will move us up to the end of */ /* the previous line. */ if(pEditInstance->unCurrentLine > 0) { ODEditGotoPreviousLine(pEditInstance); pEditInstance->unCurrentColumn = ODEditBufferGetLineLength( pEditInstance, pEditInstance->unCurrentLine); return(TRUE); } } /* It wasn't possible to move the cursor. */ return(FALSE); } /* ---------------------------------------------------------------------------- * ODEditDeleteCurrentChar() *** PRIVATE FUNCTION *** * * Deletes the character at the current cursor position, performing necessary * redraw. Pressing the delete key causes just this function to be called. * Pressing the backspace key causes ODEditCursorLeft() to be called to first * move the cursor left before calling ODEditDeleteCurrentChar(). * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditDeleteCurrentChar(tEditInstance *pEditInstance) { char *pch; ASSERT(pEditInstance != NULL); /* Remember initial edit area contents, to permit selective redraw. */ ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer); /* Backup the entire buffer contents by one character. */ pch = ODEditBufferGetCharacter(pEditInstance, pEditInstance->unCurrentLine, pEditInstance->unCurrentColumn); memmove(pch, pch + 1, strlen(pch + 1) + 1); /* Reindex and reformat the buffer based on its new contents. */ ODEditBufferFormatAndIndex(pEditInstance); /* Perform necessary redrawing and reposition the cursor if needed. */ ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer, REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY); } /* ---------------------------------------------------------------------------- * ODEditDeleteCurrentLine() *** PRIVATE FUNCTION *** * * Removes the entire current line from the buffer. * * Parameters: pEditInstance - Editor instance information structure. * * Return: void */ static void ODEditDeleteCurrentLine(tEditInstance *pEditInstance) { char *pszStartOfThisLine; char *pszStartOfNextLine; ASSERT(pEditInstance != NULL); /* Remember initial edit area contents, to permit selective redraw. */ ODEditRememberArea(pEditInstance, pEditInstance->pRememberBuffer); /* Determine start of this line. */ pszStartOfThisLine = ODEditBufferGetCharacter(pEditInstance, pEditInstance->unCurrentLine, 0); if(pEditInstance->unLinesInBuffer == pEditInstance->unCurrentLine + 1) { /* If this is the last line of the buffer, then we just remove */ /* everything from this line, without actually removing the line. */ *pszStartOfThisLine = '\0'; } else { /* If this is not the last line of the buffer, then remove this */ /* entire line, so that the next line will become the current */ /* line. */ pszStartOfNextLine = ODEditBufferGetCharacter(pEditInstance, pEditInstance->unCurrentLine + 1, 0); memmove(pszStartOfThisLine, pszStartOfNextLine, strlen(pszStartOfNextLine) + 1); } /* Reset the cursor position to the beginning of the current line. */ pEditInstance->unCurrentColumn = 0; /* Reindex and reformat the buffer based on its new contents. */ ODEditBufferFormatAndIndex(pEditInstance); /* Perform necessary redrawing and reposition the cursor if needed. */ ODEditRedrawChanged(pEditInstance, pEditInstance->pRememberBuffer, REDRAW_NO_BOUNDARY, REDRAW_NO_BOUNDARY); } /* ---------------------------------------------------------------------------- * ODEditPastEndOfCurLine() *** PRIVATE FUNCTION *** * * Determines whether the cursor is currently past the end of the current line. * The end of the line is considered to be the first column after the last * character on the line. So, on a blank line, a cursor is considered to be * past the end if it is in or past the second column (column 0). * * Parameters: pEditInstance - Editor instance information structure. * * Return: TRUE if the cursor is past the end of the current line, * FALSE if it is not. */ static BOOL ODEditPastEndOfCurLine(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); return(pEditInstance->unCurrentColumn > ODEditBufferGetLineLength(pEditInstance, pEditInstance->unCurrentLine)); } /* ---------------------------------------------------------------------------- * ODEditRememberBufferSize() *** PRIVATE FUNCTION *** * * Determines the buffer size required by ODEditRememberArea(). * * Parameters: pEditInstance - Editor instance information structure. * * Return: the required buffer size, in size_t units (bytes). */ static size_t ODEditRememberBufferSize(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); return((pEditInstance->unAreaWidth + 1) * pEditInstance->unAreaHeight); } /* ---------------------------------------------------------------------------- * ODEditRememberArea() *** PRIVATE FUNCTION *** * * Stores a copy of the text currently displayed in the edit area in the * provided buffer, so that it can later be reused to redraw only the portion * of the edit area which has been changed by an operation. * * Parameters: pEditInstance - Editor instance information structure. * * pRememberedArea - Pointer to a buffer, which is at least * the number of bytes specified by * ODEditRememberBufferSize() in size. * * Return: void */ static void ODEditRememberArea(tEditInstance *pEditInstance, void *pRememberedArea) { UINT unDataLineOffset = 0; UINT unDataLineSize; UINT unAreaLine; UINT unBufferLine; UINT unLineLength; char *pchStartOfLine; char *pchDataLocation; ASSERT(pEditInstance != NULL); ASSERT(pRememberedArea != NULL); /* Determine the length of a single line in the remember buffer. */ unDataLineSize = pEditInstance->unAreaWidth + 1; pchDataLocation = (char *)pRememberedArea + unDataLineOffset; for(unBufferLine = pEditInstance->unLineScrolledToTop, unAreaLine = 0; unAreaLine < pEditInstance->unAreaHeight; ++unAreaLine, ++unBufferLine) { /* If this line is not beyond the end of the buffer. */ if(unBufferLine < pEditInstance->unLinesInBuffer) { /* Determine the length of this buffer line. */ unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine); /* Determine the start location of this buffer line. */ pchStartOfLine = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0); } else { /* If this line is beyond the end of the buffer, then it is empty. */ unLineLength = 0; pchStartOfLine = ""; } /* Copy the contents of this line to the data buffer. */ memcpy(pchDataLocation, pchStartOfLine, unLineLength); pchDataLocation[unLineLength] = '\0'; /* Update the location where data is being stored in the buffer. */ pchDataLocation += unDataLineSize; } } /* ---------------------------------------------------------------------------- * ODEditRedrawChanged() *** PRIVATE FUNCTION *** * * Redraws the portion of the edit area which has been changed by an operation, * based on the original edit area contents as stored in a buffer by the * ODEditRememberArea() function. * * Parameters: pEditInstance - Editor instance information structure. * * pRememberedArea - Pointer to a buffer that was filled by a * previous call to ODEditRememberArea(). * * unUpperBoundary - The first line in the edit area to consider * redrawing, or REDRAW_NO_BOUNDARY to specify * the top of the edit area. * * unLowerBoundary - The last line in the edit area to consider * redrawing, or REDRAW_NO_BOUNDARY to specify * the bottom of the edit area. * * Return: void */ static void ODEditRedrawChanged(tEditInstance *pEditInstance, void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary) { UINT unStartRedrawLine; UINT unStartRedrawColumn; UINT unFinishRedrawLine; UINT unFinishRedrawColumn; UINT unEstPartialRedrawBytes; ASSERT(pEditInstance != NULL); ASSERT(pRememberedArea != NULL); /* Determine what portion of the edit area, within the specified upper */ /* and lower boundaries, has been changed. */ if(!ODEditDetermineChanged(pEditInstance, pRememberedArea, unUpperBoundary, unLowerBoundary, &unStartRedrawLine, &unStartRedrawColumn, &unFinishRedrawLine, &unFinishRedrawColumn)) { /* Nothing has changed in the edit area. */ ODEditUpdateCursorIfMoved(pEditInstance); return; } /* Now that we have determined the portion of the edit area that would */ /* be redraw by a partial (incremental) redraw, we compare the amount */ /* of data involved + the amount of data still in the outbound buffer */ /* with the amount of data involved in a full redraw. If the amount of */ /* data in the outbound buffer cannot be determined, we default to */ /* using an incremental redraw. */ unEstPartialRedrawBytes = ODEditEstDrawBytes(pEditInstance, unStartRedrawLine, unStartRedrawColumn, unFinishRedrawLine, unFinishRedrawColumn); if(ODEditRecommendFullRedraw(pEditInstance, unEstPartialRedrawBytes, FALSE)) { /* Purge the outbound buffer and do a full redraw. */ ODEditRedrawArea(pEditInstance); /* Move the cursor back to its appropriate position. */ ODEditUpdateCursorPos(pEditInstance); } else { /* Perform a partial (incremental) redraw. */ /* Now, redraw the portion of the edit area that has been determined to */ /* require redrawing. */ ODEditRedrawSubArea(pEditInstance, unStartRedrawLine, unStartRedrawColumn, unFinishRedrawLine, unFinishRedrawColumn); /* Now, move the cursor back to its appropriate position, if it isn't */ /* already there. */ ODEditUpdateCursorIfMoved(pEditInstance); } } /* ---------------------------------------------------------------------------- * ODEditDetermineChanged() *** PRIVATE FUNCTION *** * * Determines what portion of the edit area, within the specifiede upper and * lower boundary, has been changed. Area is specified as row and column * position of the first changed characer between boundaries, and the row * and column position past the last changed character between the boundaries. * * Parameters: pEditInstance - Editor instance information structure. * * pRememberedArea - Pointer to a buffer that was filled by a * previous call to ODEditRememberArea(). * * unUpperBoundary - The first line in the edit area to * consider redrawing, or * REDRAW_NO_BOUNDARY to specify the top of * the edit area. * * unLowerBoundary - The last line in the edit area to * consider redrawing, or * REDRAW_NO_BOUNDARY to specify the bottom * of the edit area. * * punStartRedrawLine - Output: Line of first changed character. * * punStartRedrawColumn - Output: Column of first changed char. * * punFinishRedrawLine - Output: Line of last changed character. * * punFinishRedrawColumn - Output: Column after last changed char. * * Return: TRUE if some text in the edit area has been changed, FALSE * if there is no change. */ static BOOL ODEditDetermineChanged(tEditInstance *pEditInstance, void *pRememberedArea, UINT unUpperBoundary, UINT unLowerBoundary, UINT *punStartRedrawLine, UINT *punStartRedrawColumn, UINT *punFinishRedrawLine, UINT *punFinishRedrawColumn) { BOOL bFoundStart = FALSE; BOOL bFoundFinish = FALSE; UINT unDataLineOffset = 0; UINT unDataLineSize; UINT unAreaLine; UINT unLineLength; UINT unColumn; UINT unBufferLine; char *pchCurrent; char *pchRemembered; /* Determine the length of a single line in the remember buffer. */ unDataLineSize = pEditInstance->unAreaWidth + 1; /* If caller specified no upper boundary, then reset upper boundary */ /* to 0. */ if(unUpperBoundary == REDRAW_NO_BOUNDARY) unUpperBoundary = 0; /* Likewise, iff caller specified no lower boundary, then reset the */ /* lower boundary to the bottom of the edit area. */ if(unLowerBoundary == REDRAW_NO_BOUNDARY) { unLowerBoundary = pEditInstance->unAreaHeight; } /* Loop through the area within boundaries, determining which */ /* portion of the edit area has changed. */ for(unBufferLine = pEditInstance->unLineScrolledToTop + unUpperBoundary, unAreaLine = unUpperBoundary; unAreaLine < unLowerBoundary; ++unAreaLine, ++unBufferLine) { /* Determine location of corresponding line in remembered data. */ pchRemembered = (char *)pRememberedArea + unDataLineOffset + unDataLineSize * unAreaLine; /* If this line is not beyond the end of the buffer. */ if(unBufferLine < pEditInstance->unLinesInBuffer) { /* Determine the start location of this buffer line. */ pchCurrent = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0); /* Determine the length of this buffer line. */ unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine); } else { pchCurrent = ""; unLineLength = 0; } /* Start at the first column on this line. */ unColumn = 0; /* Look for any characters that differ. */ for(;; ++unColumn, ++pchCurrent, ++pchRemembered) { /* Determine if we are at the end of the remembered line. */ BOOL bEndOfRemembered = (*pchRemembered == '\0'); /* Determine if we are at the end of the current buffer line. */ BOOL bEndOfCurrent = (unColumn == unLineLength); /* If we are at the end of either of the buffers (but not both), */ /* or if these two characters differ, then we have found the */ /* start of the area that must be redrawn. */ if(!(bEndOfRemembered && bEndOfCurrent)) { if(bEndOfRemembered || bEndOfCurrent || *pchCurrent != *pchRemembered) { if(bFoundStart) { bFoundFinish = FALSE; } else { /* We have found a character that differs. */ bFoundStart = TRUE; *punStartRedrawLine = unAreaLine; *punStartRedrawColumn = unColumn; } } } /* If we have found the first changed text in the buffer, then we */ /* are now looking for the last changed text in the buffer. */ if(bFoundStart && !bFoundFinish) { if(*pchCurrent == *pchRemembered) { bFoundFinish = TRUE; *punFinishRedrawLine = unAreaLine; *punFinishRedrawColumn = unColumn; } else if(bEndOfRemembered) { bFoundFinish = TRUE; *punFinishRedrawLine = unAreaLine; *punFinishRedrawColumn = unLineLength; } else if(bEndOfCurrent) { bFoundFinish = TRUE; *punFinishRedrawLine = unAreaLine; *punFinishRedrawColumn = unColumn + strlen(pchRemembered); } } /* If we are at the end of either buffers. */ if(bEndOfRemembered || bEndOfCurrent) { /* Now, proceed to processing the next line in the edit area. */ break; } } } /* If we haven't found any text in the edit area that has changed. */ if(!bFoundStart) { /* Then return indicating no change. */ return(FALSE); } /* If we haven't found an end to the portion of the area that has */ /* changed, then we must redraw up to the end of the edit area. */ if(!bFoundFinish) { *punFinishRedrawLine = unLowerBoundary; *punFinishRedrawColumn = unColumn; } /* Return indicating that ther has been some change. */ return(TRUE); } /* ---------------------------------------------------------------------------- * ODEditRedrawSubArea() *** PRIVATE FUNCTION *** * * Redraws the portion of the edit area within the specified range. Redrawing * is performed from the location of the start redraw row and column, up to * but not includin gthe finish redraw row and column. * * Parameters: pEditInstance - Editor instance information structure. * * unStartRedrawLine - Line of first character to draw. * * unStartRedrawColumn - Column of first character to draw. * * unFinishRedrawLine - Line of last character to draw. * * unFinishRedrawColumn - Column after last character to draw. * * Return: void */ static void ODEditRedrawSubArea(tEditInstance *pEditInstance, UINT unStartRedrawLine, UINT unStartRedrawColumn, UINT unFinishRedrawLine, UINT unFinishRedrawColumn) { UINT unAreaLine; UINT unLineLength; UINT unBufferLine; char *pchCurrent; UINT unStartColumn; UINT unFinishColumn; UINT unScrnStartColumn; UINT unTextLength; /* Now, perform actual redraw in area that requires redrawing. */ for(unBufferLine = pEditInstance->unLineScrolledToTop + unStartRedrawLine, unAreaLine = unStartRedrawLine; unAreaLine <= unFinishRedrawLine; ++unBufferLine, ++unAreaLine) { BOOL bFirstLine = (unAreaLine == unStartRedrawLine); BOOL bLastLine = (unAreaLine == unFinishRedrawLine); UINT unScrnRow = (UINT)pEditInstance->pUserOptions->nAreaTop + unAreaLine; /* If this line is not beyond the end of the buffer. */ if(unBufferLine < pEditInstance->unLinesInBuffer) { pchCurrent = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0); unTextLength = unLineLength = ODEditBufferGetLineLength(pEditInstance, unBufferLine); } else { pchCurrent = ""; unTextLength = unLineLength = 0; } /* Move to the position on the first line to begin redraw. */ if(bFirstLine) { UINT unActualRow; UINT unActualColumn; ODEditGetActualCurPos(pEditInstance, &unActualRow, &unActualColumn); unStartColumn = unStartRedrawColumn; unScrnStartColumn = (UINT)pEditInstance->pUserOptions->nAreaLeft + unStartColumn; if(unScrnRow != unActualRow || unScrnStartColumn != unActualColumn) { od_set_cursor(unScrnRow, unScrnStartColumn); } pchCurrent += unStartRedrawColumn; unTextLength -= unStartRedrawColumn; } else { unStartColumn = 0; unScrnStartColumn = (UINT)pEditInstance->pUserOptions->nAreaLeft; od_set_cursor(unScrnRow, unScrnStartColumn); } /* If this is the last line to redraw, then adjust accordingly. */ if(bLastLine) { if(unFinishRedrawColumn < unLineLength) { unTextLength -= unLineLength - unFinishRedrawColumn; } unFinishColumn = unFinishRedrawColumn; } else { unFinishColumn = pEditInstance->unAreaWidth; } /* Output changed text. */ if(unStartColumn < unLineLength) { od_disp(pchCurrent, unTextLength, TRUE); unStartColumn += unTextLength; } /* If we need to clear the rest of the line. */ if(unFinishColumn == pEditInstance->unAreaWidth) { /* If right edge of edit area aligns with the right edge of the */ /* screen. */ if(pEditInstance->pUserOptions->nAreaRight == OD_SCREEN_WIDTH) { /* Clear the remainder of this line on the screen. */ od_clr_line(); } else { /* Place spaces after the end of the current line, up to right */ /* edge of the edit area. */ od_repeat(' ', (BYTE)(pEditInstance->unAreaWidth - unLineLength)); } } else if(unStartColumn < unFinishColumn) { od_repeat(' ', (BYTE)(unFinishColumn - unStartColumn)); } } } /* ---------------------------------------------------------------------------- * ODEditGetActualCurPos() *** PRIVATE FUNCTION *** * * Estimates the actual position of the cursor on the screen. * * Parameters: pEditInstance - Editor instance information structure. * * punRow - Pointer to location where cursor row number * should be stored. * * punColumn - Pointer to location where cursor column number * should be stored. * * Return: void */ static void ODEditGetActualCurPos(tEditInstance *pEditInstance, UINT *punRow, UINT *punColumn) { tODScrnTextInfo TextInfo; ASSERT(pEditInstance != NULL); ASSERT(punRow != NULL); ASSERT(punColumn != NULL); UNUSED(pEditInstance); /* Obtain current cursor position information from the ODScrn module. */ ODScrnGetTextInfo(&TextInfo); *punRow = (UINT)TextInfo.cury; *punColumn = (UINT)TextInfo.curx; } /* ---------------------------------------------------------------------------- * ODEditIsEOLForMode() *** PRIVATE FUNCTION *** * * Determines whether the specified character should be treated as an EOL * character for the current mode. * * Parameters: pEditInstance - Editor instance information structure. * * Return: TRUE if this is an EOL character, FALSE otherwise. */ static BOOL ODEditIsEOLForMode(tEditInstance *pEditInstance, char chToTest) { switch(pEditInstance->pUserOptions->TextFormat) { case FORMAT_FTSC_MESSAGE: return(chToTest == '\r' || chToTest == '\0'); default: return(IS_EOL_CHAR(chToTest)); } } /* ========================================================================= */ /* Low level buffer manipulation functions. */ /* ========================================================================= */ /* ---------------------------------------------------------------------------- * ODEditBufferFormatAndIndex() *** PRIVATE FUNCTION *** * * Regenerates the count of lines in the buffer, and the array of pointers to * the start of each line. * * Parameters: pEditInstance - Editor instance information structure. * * Return: TRUE on success, or FALSE if there is not enough memory * available to complete this operation. */ static BOOL ODEditBufferFormatAndIndex(tEditInstance *pEditInstance) { char *pch; char *pchLastSpace; UINT unProcessingColumn; UINT unProcessingLine; BOOL bAtEndOfBuffer = FALSE; BOOL bLineEndedByBreak; BOOL bFTSCMode = (pEditInstance->pUserOptions->TextFormat == FORMAT_FTSC_MESSAGE); ASSERT(pEditInstance != NULL); /* Reset current line count. */ unProcessingLine = 0; /* Begin at the beginning of the buffer to edit. */ pch = pEditInstance->pszEditBuffer; ASSERT(pch != NULL); /* Loop for each line in the buffer. */ while(!bAtEndOfBuffer) { /* In FTSC mode, skip a line if it begins with a ^A ("kludge lines"). */ if(bFTSCMode) { /* Loop while the current line begins with a ^A. */ while(*pch == 0x01) { /* Loop until the end of the line is found. */ while(!ODEditIsEOLForMode(pEditInstance, *pch)) ++pch; if(*pch == '\0') { /* If the line was ended by a null character, then note that */ /* the end of the buffer has been reached. */ bAtEndOfBuffer = TRUE; } else { /* If the line was not ended by a null character, then skip */ /* the end of line character. */ ++pch; } } continue; } /* Add the address of the start of this line to the line array. */ /* If the line array is full, then attempt to grow it. */ ASSERT(unProcessingLine <= pEditInstance->unLineArraySize); if(unProcessingLine == pEditInstance->unLineArraySize) { /* Determine the size to grow the array to. */ UINT unNewArraySize = pEditInstance->unLineArraySize + LINE_ARRAY_GROW_SIZE; /* Attempt to reallocate this memory block. */ char **papchNewLineArray = (char **)realloc( pEditInstance->papchStartOfLine, unNewArraySize * sizeof(char *)); /* If reallocation failed, then return with failure. */ if(papchNewLineArray == NULL) { return(FALSE); } /* Otherwise, update the editor instance information with the new */ /* array address and array size information. */ pEditInstance->papchStartOfLine = papchNewLineArray; pEditInstance->unLineArraySize = unNewArraySize; } /* Add the address of the start of this line to the array. */ pEditInstance->papchStartOfLine[unProcessingLine] = pch; /* Reset word wrap information. */ pchLastSpace = NULL; /* Now, find the end of this line. */ bLineEndedByBreak = TRUE; unProcessingColumn = 0; while(!ODEditIsEOLForMode(pEditInstance, *pch)) { /* If this character is a space, then record the location of the */ /* last space characters. */ if(*pch == ' ') pchLastSpace = pch; /* Check for characters which must be filtered from the buffer */ /* in FTSC message mode. */ if(bFTSCMode) { if(*pch == 0x0a || ((unsigned char)*pch) == 0x8d) { /* If this character must be removed, then move rest of */ /* buffer up by one character, and proceed to next loop */ /* iteration. */ memmove(pch, pch + 1, strlen(pch + 1) + 1); continue; } } /* Increment count of characters on this line. */ ++unProcessingColumn; /* Check whether we have reached the maximum number of characters */ /* that will fit on this line. */ if(unProcessingColumn >= pEditInstance->unAreaWidth - 1) { if(pEditInstance->bWordWrapLongLines) { /* If we are to word wrap long lines, then back up to the */ /* beginning of the last word, if we have encountered any */ /* space characters. */ if(pchLastSpace != NULL && pchLastSpace < pch) { /* Update current column number accordingly. */ unProcessingColumn -= (UINT)(pch - pchLastSpace); /* Back up to position to perform word wrap at. */ pch = pchLastSpace; } } /* If we are wrapping text where the cursor is located, then we */ /* will have to reposition the cursor accordingly. */ if(unProcessingLine == pEditInstance->unCurrentLine && unProcessingColumn < pEditInstance->unCurrentColumn) { /* Move the cursor to the next line. */ pEditInstance->unCurrentLine++; /* Adjust the cursor column number to the position where the */ /* corresponding wrapped text will appear. */ pEditInstance->unCurrentColumn -= unProcessingColumn; } /* Note that line was not ended by en explicit line break. */ bLineEndedByBreak = FALSE; break; } /* Move to the next character in the buffer. */ ++pch; } /* If we the line was terminated by a '\0', then note that the end of */ /* the buffer has been reached. */ if(*pch == '\0') { bAtEndOfBuffer = TRUE; } /* If the line was not terminated by a '\0', then find the first */ /* character of the next line. */ else { char chFirstEOLChar = *pch; char chSecondEOLChar = '\0'; /* Move to the next character in the buffer. */ ++pch; /* If this character is a different EOL sequence character from the */ /* already encountered EOL character, then skip past it too. */ if(ODEditIsEOLForMode(pEditInstance, chFirstEOLChar) && *pch != '\0' && ODEditIsEOLForMode(pEditInstance, *pch) && *pch != chFirstEOLChar) { chSecondEOLChar = *pch; ++pch; } /* If we don't already know what form of line/paragraph break to */ /* use (just a CR, just a LF, a CR/LF sequence or a LF/CR */ /* sequence), then use this line termination as an example of */ /* what should be used. */ if(bLineEndedByBreak) { ODEditSetBreakSequence(pEditInstance, chFirstEOLChar, chSecondEOLChar); } } /* Increment the count of the current line number. */ unProcessingLine++; } /* Update count of number of lines in the buffer, based on the number */ /* that we have found. */ pEditInstance->unLinesInBuffer = unProcessingLine; /* Return with success. */ return(TRUE); } /* ---------------------------------------------------------------------------- * ODEditBufferGetLineLength() *** PRIVATE FUNCTION *** * * Determines the length of the specified line in the buffer, not including * any line terminator characters. * * Parameters: pEditInstance - Editor instance information structure. * * unBufferLine - 0-based index of the line in question. * * Return: The number of characters on this line. */ static UINT ODEditBufferGetLineLength(tEditInstance *pEditInstance, UINT unBufferLine) { char *pch; char *pchStartOfLine; UINT unCharsBeforeEOL; ASSERT(pEditInstance != NULL); ASSERT(unBufferLine < pEditInstance->unLinesInBuffer); ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize); /* Get the address of the start of this line in the buffer. */ pchStartOfLine = ODEditBufferGetCharacter(pEditInstance, unBufferLine, 0); /* Count the number of characters before the next end of line character. */ for(pch = pchStartOfLine, unCharsBeforeEOL = 0; !ODEditIsEOLForMode(pEditInstance, *pch); ++unCharsBeforeEOL, ++pch); /* If this is the last line in the buffer, then the number of characers */ /* before the next end of line character is the length of this line. */ if(unBufferLine >= pEditInstance->unLinesInBuffer - 1) { return(unCharsBeforeEOL); } /* If this is not the last line in the buffer, then the length of this */ /* line is the minimum of the number of characters before the next end */ /* of line character and the number of characters before the next line, */ /* according to the line index information. This is because all lines */ /* do not necessarily end with an end-of-line character. */ else { return(MIN(unCharsBeforeEOL, (UINT)(ODEditBufferGetCharacter( pEditInstance, unBufferLine + 1, 0) - pchStartOfLine))); } } /* ---------------------------------------------------------------------------- * ODEditBufferGetTotalLines() *** PRIVATE FUNCTION *** * * Determines the number of lines in the current edit buffer. * * Parameters: pEditInstance - Editor instance information structure. * * Return: The total number of lines in the buffer. */ static UINT ODEditBufferGetTotalLines(tEditInstance *pEditInstance) { ASSERT(pEditInstance != NULL); ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize); /* Return the total number of lines in the buffer. */ return(pEditInstance->unLinesInBuffer); } /* ---------------------------------------------------------------------------- * ODEditBufferGetCharacter() *** PRIVATE FUNCTION *** * * Obtains the character at the specified position in the buffer. * * Parameters: pEditInstance - Editor instance information structure. * * unBufferLine - 0-based index of the line in question. * * unBufferColumn - The position in the line of the required * character. * * Return: A pointer to the character at the specified position in the * specified line. The caller can assume that any remaining * character(s) in the line follow this character, but should * not assume that the pointer can be used to access following * lines in the buffer. */ static char *ODEditBufferGetCharacter(tEditInstance *pEditInstance, UINT unBufferLine, UINT unBufferColumn) { ASSERT(pEditInstance != NULL); ASSERT(unBufferLine < pEditInstance->unLinesInBuffer); ASSERT(pEditInstance->unLinesInBuffer <= pEditInstance->unLineArraySize); ASSERT(unBufferColumn <= ODEditBufferGetLineLength(pEditInstance, unBufferLine)); /* The position of this character is the position of this line, plus */ /* the number of characters into the line. */ return(pEditInstance->papchStartOfLine[unBufferLine] + unBufferColumn); } /* ---------------------------------------------------------------------------- * ODEditBufferMakeSpace() *** PRIVATE FUNCTION *** * * Moves the remaining buffer contents by the specified distance to make room * for new text. The new space is filled by space (' ') characters. Does not * necessarily reindex the buffer before returning, and so this should be done * by the caller after the new buffer space has been filled. * * Parameters: pEditInstance - Editor instance information structure. * * unLine - Line number to make more room on. * * unColumn - Column number to insert the space. * * unNumChars - Number of characters to make room for. * * Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to * obtain enough buffer space before making any changes, or * kODRCUnrecoverableFailure if the buffer has been changed, but * there was insufficient memory to re-index the buffer. */ static tODResult ODEditBufferMakeSpace(tEditInstance *pEditInstance, UINT unLine, UINT unColumn, UINT unNumChars) { UINT unLineLength; UINT unBufferUsed; UINT unBufferUnused; UINT unRemainingBufferBytes; UINT unCount; char *pchBufferPos; tODResult Result; ASSERT(pEditInstance != NULL); ASSERT(unLine < pEditInstance->unLinesInBuffer); /* Determine the current length of the specified line. */ unLineLength = ODEditBufferGetLineLength(pEditInstance, unLine); /* If a column past the current end of this line was specified, then */ /* adjust column and number of characters in order to extend the line */ /* up to the specified column as well as adding space beginning at that */ /* column. */ if(unColumn > unLineLength) { UINT unExtendLineBy = unColumn - unLineLength; unColumn -= unExtendLineBy; unNumChars += unExtendLineBy; } /* Now, determine whether the buffer is large enough for the additional */ /* space that will be added. */ unBufferUsed = strlen(pEditInstance->pszEditBuffer) + 1; unBufferUnused = pEditInstance->unBufferSize - unBufferUsed; if(unBufferUnused < unNumChars) { /* There is not currently sufficient room in the buffer for the new */ /* characters, then attempt to grow the buffer to make more room. */ Result = ODEditTryToGrow(pEditInstance, unBufferUsed + unNumChars); if(Result != kODRCSuccess) { /* On failure, return the result code that indicates the nature */ /* of the failure. */ return(Result); } } /* Now, shift the buffer contents from this location forward by */ /* unNumChars characters. */ pchBufferPos = ODEditBufferGetCharacter(pEditInstance, unLine, unColumn); unRemainingBufferBytes = strlen(pchBufferPos) + 1; memmove(pchBufferPos + unNumChars, pchBufferPos, unRemainingBufferBytes); /* Next, we fill the new buffer area with space characters. */ for(unCount = 0; unCount < unNumChars; ++unCount) { *pchBufferPos++ = ' '; } /* Return with success. */ return(kODRCSuccess); } /* ---------------------------------------------------------------------------- * ODEditTryToGrow() *** PRIVATE FUNCTION *** * * Attempts to reallocate the buffer to a larger size. This function is called * if it is found that the current buffer isn't large enough to complete some * operation. If the client application has setup the editor to permit buffer * reallocation, then this function will call the realloc callback function * supplied by the client application. If buffer growing is not possible, then * this function automatically fails. * * Parameters: pEditInstance - Editor instance information structure. * * unSizeNeeded - The minimum buffer size that is needed. * * Return: kODRCSuccess on success, kODRCSafeFailure if we were unable to * obtain enough buffer space before making any changes, or * kODRCUnrecoverableFailure if the buffer has been changed, but * there was insufficient memory to re-index the buffer. */ static tODResult ODEditTryToGrow(tEditInstance *pEditInstance, UINT unSizeNeeded) { BOOL bFullReIndexRequired = FALSE; ASSERT(pEditInstance != NULL); ASSERT(unSizeNeeded > pEditInstance->unBufferSize); if(pEditInstance->pUserOptions->pfBufferRealloc != NULL) { /* If the buffer is growable, then attempt to grow it using the */ /* realloc function provided by the client application. */ UINT unNewBufferSize = MAX(pEditInstance->unBufferSize + BUFFER_GROW_SIZE, unSizeNeeded); char *pszNewBuffer = (char *)((*pEditInstance->pUserOptions-> pfBufferRealloc)(pEditInstance->pszEditBuffer, unNewBufferSize)); /* If we were unable to grow the buffer, then fail now. At this */ /* point, nothing has changed, and so the buffer information */ /* is still intact and valid. */ if(pszNewBuffer == NULL) { return(kODRCSafeFailure); } /* Otherwise, determine whether the entire buffer will now have to */ /* be reindexed. This is necessary if the reallocated buffer is at */ /* a new location than the original was. */ if(pszNewBuffer != pEditInstance->pszEditBuffer) { bFullReIndexRequired = TRUE; } /* Now, store the new buffer pointer and buffer size information. */ pEditInstance->pszEditBuffer = pszNewBuffer; pEditInstance->unBufferSize = unNewBufferSize; } else { /* If the buffer is not growable, then fail right away. */ return(kODRCSafeFailure); } /* If a full reindex is required due to buffer reallocation, then do so. */ if(bFullReIndexRequired) { if(!ODEditBufferFormatAndIndex(pEditInstance)) { /* If this fails, then return with failure. */ return(kODRCUnrecoverableFailure); } bFullReIndexRequired = FALSE; } /* If we get to this point, we suceeded in growing the buffer to the */ /* required size, so return with success. */ return(kODRCSuccess); }