This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
magicka/deps/odoors/ODEdit.c

2651 lines
98 KiB
C
Raw Normal View History

2016-12-03 05:08:50 +00:00
/* 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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#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);
}