From cc8beb85788c296dcc84ab30ca1b2f3e0f43234e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ken=20Pettit=20=E2=80=8E?= Date: Wed, 16 Jan 2019 19:21:34 -0600 Subject: [PATCH] Bug fixes and changes to existing functionality =============================================== 1. Deletion of last line in file using 'dd' caused infinite loop searching for 'nextline'. Fixed. 2. Insertion of line above 1st line using 'O' caused line to be inserted on 2nd line. Fixed. 3. Paste buffer from 'dd' was being free'd after the 'p'aste operation, preventing multiple paste opportunity. Fixed. 4. The cursor was not being bound to the line end and was allowed to 'hover' over the '\n' EOL character. This caused wierd (relative to standard vi) insertion locations and cursor movement with 'a'ppend and 'i'nsert. Fixed. 5. The 'vi_shrinkpos' position didn't take the end of file pointer into account when calculating 'curpos', 'prevpos' variables causing wierdness when deleting things near the end of the file. Fixed. 6. The 'yy'ank command was improperly deleting the text from the document instead of simply yanking to the paste buffer. Fixed. 7. The 'dd'elete line funciton was not copying the deleted line to the paste buffer as part of the delete operation. Fixed. 8. The bottom line of the screen was sometimes being used for document text and other times for command / find entry. Fixed by reserving the bottom the bottom line for status / command only. 9. When scrolling up / down through a file, the cursor position was not preserved and moved further and further to the left column based on the line lengths of the lines visited. Fixed. 10. The display was being COMPLETELY redrawn with each keystroke, causing the responsiveness to be imbearable. Fixed. Added logic to dynamicall indicated which portions of the screen need to be updated (full display, current line to end of screen, current line only, none). 11. The individual display line updates were being performed a character at a time by calling the vi_putch() function, causing slow performance, especially in telnet or USBCDC sessions where there is a lot of overhead per packet. Fixed by calling vi_write() instead with a block of characters with breaks as needed for TAB exansion. 12. The forward delete 'x' operation allowed deletion through '\n' characters causing lines to be joined. This is different from standard vi where 'x' at the end of a line performs a delete backward operation. Fixed. 13. When in 'ex' or find sub-modes (':' or '/' bottom line modes), hitting backspace with empty text did not return the operation to command mode like it does in standard vi. Fixed. 14. Performing a 'find' opertation would only search from the current cursor position to the end of the file and did not wrap like in standard vi. Fixed. 15. Loading a file that was larger than the initial text allocation would force a call to 'vi_extendtext', marking the file as modified when in fact it hadn't even been loaded yet. Fixed. 16. Combined vi_insert_mode and vi_replace_mode into a single function to save code space since they are nearly identical routines. 17. In command mode, backspace was deleting characters which is different from standard vi. Backspace in command mode normally simply moves the cursor left / to the previous line. Fixed. 18. Added code to handle boundry conditions when the file is new and commands are applied to an empty file. 19. Fixed vi_shrinktext so it doesn't allocate a zero-length buffer when the last character in the file is deleted. 20. The 'x' command was not copying to the paste buffer. Fixed. 21. Fixed parsecolon routine to properly deal with ":wq" command sequence. New feature additions ===================== 1. Vi startup feature to prepend the current working directory to the supplied filename if it does not start with '/' absolute path specifier. This allows editing files in the current directory without needing to fully qualify the filename. 2. Standard '~' empty screen line characters for proper identification of empty '\n' only lines at the end of the file. 3. Moving into insert or append mode now prints '--INSERT--' in the status line and clears it upon exit. 4. Integration with termcurses to detect special keystrokes and to handle terminal type differences. Haven't yet converted all vt100 print codes to termcurses equivalents. 5. Support for up, down, left, right arrows, page-up, page-down keys in command mode. 6. Added 'b' command to move cursor 'b'ack to previous word. Honors the command repeat value for multiple word moves. 7. Added 'w' command to move cursor to next 'w'ord. Honors the command repeat value for multiple word moves. 8. Added 'f' and 't' commands to find characters on the current line. Honors the command repeat value. 9. Added the find 'n'ext command to repeat the previous find operation. 10. Added the 'H', 'M', and 'L' commands to move the cursor to top, middle and bottom of the display. 11. Extended yank/paste to manage a paste buffer with character mode in addition to line mode. Character mode allows cut / paste of individual characters or groups of characters vs. full lines. 12. Added support in the 'd'elete and 'y'ank commands for character mode deletions such as 'dw', 'dfa', '2yw', etc. 13. Added support in paste routine for multiple paste using command value argument. 14. Added support for the 'D' and 'C' delete and change to end of line commands. 15. Added support for 's'ubstitute key ('x' followed by insert mode). 16. Modified yank / paste allocation scheme to avoid repeated alloc and free of paste buffer. Uses a minimum allocation size scheme and only does free / realloc when paste does not fit. If the paste is smaller than the current allocation and the current allocation is larger than the threshold, then the buffer is freed and a smaller buffer allocated. Otherwise the existing buffer is used. 17. Support for 'P'aste before current cursor position. 18. Support for '?' reverse search mode. 19. Support for 'J'oin next line with current line command. 20. Printing of current row,col in status line of display. 21. Command repeat '.' support for commands that modifiy text. 22. Support in replace / insert mode for arrow keys, PGUP / PGDN, HOME, and END. Using these will cause the command repeat buffer to reset such that only the last text addition after a cursor movement is saved. 23. Added 'X' delete previous command. 24. Added "gg" go to top of document command sequence. 25. Added "ZZ" save and quit command (equivalent to :wq). 26. Implemented '^' goto first non-whitespace on current line, along with '+' and '-' goto first non-whitespace on next / previous line. 27. Added CR / LF goto first non-whitespace on next line in command mode. --- system/vi/Kconfig | 19 + system/vi/vi.c | 2865 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 2495 insertions(+), 389 deletions(-) diff --git a/system/vi/Kconfig b/system/vi/Kconfig index e940188b3..8b27ccf8f 100644 --- a/system/vi/Kconfig +++ b/system/vi/Kconfig @@ -5,6 +5,7 @@ menuconfig SYSTEM_VI tristate "VI Work-Alike Text Editor" + select SYSTEM_TERMCURSES default n ---help--- Enable support for NuttX tiny VI work-alike editor. @@ -84,5 +85,23 @@ config SYSTEM_VI_PRIORITY ---help--- Priority of the task to configure when started VI from NSH +config SYSTEM_VI_INCLUDE_COMMAND_REPEAT + bool "Include support for '.' command repeat key" + default y + +# These are placeholders and will be implemented soon +#config SYSTEM_VI_INCLUDE_UNDO +# bool "Include undo/redo support" +# default y +# ---help--- +# Adds multi-level undo / redo support. +# +#config SYSTEM_VI_UNDO_LEVELS +# int "Undo / Redo stack size" +# depends on SYSTEM_VI_INCLUDE_UNDO +# default 10 +# ---help--- +# Number of undo / redo operations saved. + endif # NSH_BUILTIN_APPS endif # SYSTEM_VI diff --git a/system/vi/vi.c b/system/vi/vi.c index 0661987d9..ccfe90067 100644 --- a/system/vi/vi.c +++ b/system/vi/vi.c @@ -3,6 +3,7 @@ * * Copyright (C) 2014, 2018 Gregory Nutt. All rights reserved. * Author: Gregory Nutt + * Major Edits 2019, Ken Pettit * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -54,6 +55,9 @@ #include #include +#include +#include + #include #include @@ -97,6 +101,10 @@ # define CONFIG_EOL_IS_EITHER_CRLF 1 #endif +#ifndef CONFIG_SYSTEM_VI_YANK_THRESHOLD +#define CONFIG_SYSTEM_VI_YANK_THRESHOLD 128 +#endif + /* Control characters */ #undef CTRL @@ -107,15 +115,17 @@ /* Sizes of things */ #define MAX_STRING 64 /* The maximum size of a filename or search string */ +#define MAX_FILENAME 128 /* The maximum size of a filename or search string */ #define SCRATCH_BUFSIZE 128 /* The maximum size of the scratch buffer */ +#define CMD_BUFSIZE 128 /* The maximum size of the scratch buffer */ #define TEXT_GULP_SIZE 512 /* Text buffer allocations are managed with this unit */ #define TEXT_GULP_MASK 511 /* Mask for aligning buffer allocation sizes */ #define ALIGN_GULP(x) (((x) + TEXT_GULP_MASK) & ~TEXT_GULP_MASK) -#define TABSIZE 8 /* A TAB is eight characters */ +#define VI_TABSIZE 8 /* A TAB is eight characters */ #define TABMASK 7 /* Mask for TAB alignment */ -#define NEXT_TAB(p) (((p) + TABSIZE) & ~TABMASK) +#define NEXT_TAB(p) (((p) + VI_TABSIZE) & ~TABMASK) /* Parsed command action bits */ @@ -123,7 +133,7 @@ #define CMD_WRITE_MASK (3 << 1) /* Bits 1-2: x1=Write operation */ # define CMD_WRITE (1 << 1) /* 01=Write (without overwriting) */ # define CMD_OWRITE (3 << 1) /* 11=Overwrite */ -#define CMD_QUIT_MASK (3 << 3) /* Bits 3-4: x1=Quite operation */ +#define CMD_QUIT_MASK (3 << 3) /* Bits 3-4: x1=Quit operation */ # define CMD_QUIT (1 << 3) /* 01=Quit if saved */ # define CMD_DISCARD (3 << 3) /* 11=Quit without saving */ @@ -137,11 +147,18 @@ #define IS_NOWRITE(a) (((uint8_t)(a) & CMD_WRITE_MASK) == CMD_WRITE) #define IS_QUIT(a) (((uint8_t)(a) & CMD_QUIT) != 0) #define IS_DISCARD(a) (((uint8_t)(a) & CMD_QUIT_MASK) == CMD_DISCARD) -#define IS_NDISCARD(a) (((uint8_t)(a) & CMD_QUIT_MASK) == CMD_QUIT) +#define IS_NDISCARD(a) (((uint8_t)(a) & CMD_QUIT_MASK) == CMD_QUIT && !IS_WRITE(a)) #define CMD_FILE_MASK (CMD_READ | CMD_WRITE) #define USES_FILE(a) (((uint8_t)(a) & CMD_FILE_MASK) != 0) +/* Search control */ + +#define VI_CHAR_SPACE 0 +#define VI_CHAR_ALPHA 1 +#define VI_CHAR_PUNCT 2 +#define VI_CHAR_CRLF 3 + /* Output */ #define vi_error(vi, fmt, ...) vi_printf(vi, "ERROR: ", fmt, ##__VA_ARGS__) @@ -186,37 +203,69 @@ # endif #endif +/* Uncomment to enable bottom line debug printing. Useful during yank / + * paste debugging, etc. + */ + +/* #define ENABLE_BOTTOM_LINE_DEBUG */ + /**************************************************************************** * Private Types ****************************************************************************/ + /* VI Key Bindings */ enum vi_cmdmode_key_e { KEY_CMDMODE_BEGINLINE = '0', /* Move cursor to start of current line */ KEY_CMDMODE_APPEND = 'a', /* Enter insertion mode after current character */ + KEY_CMDMODE_WORDBACK = 'b', /* Scan to previous word */ KEY_CMDMODE_DEL_LINE = 'd', /* "dd" deletes a lines */ + KEY_CMDMODE_FINDINLINE = 'f', /* Find within current line */ + KEY_CMDMODE_GOTOTOP = 'g', /* Two of these sends cursor to the top */ KEY_CMDMODE_LEFT = 'h', /* Move left one character */ KEY_CMDMODE_INSERT = 'i', /* Enter insertion mode before current character */ KEY_CMDMODE_DOWN = 'j', /* Move down one line */ KEY_CMDMODE_UP = 'k', /* Move up one line */ KEY_CMDMODE_RIGHT = 'l', /* Move right one character */ KEY_CMDMODE_MARK = 'm', /* Place a mark beginning at the current cursor position */ + KEY_CMDMODE_FINDNEXT = 'n', /* Find next */ KEY_CMDMODE_OPENBELOW = 'o', /* Enter insertion mode in new line below current */ KEY_CMDMODE_PASTE = 'p', /* Paste line(s) from into text after current line */ KEY_CMDMODE_REPLACECH = 'r', /* Replace character(s) under cursor */ - KEY_CMDMODE_YANK = 'y', /* "yy" yanks the current line(s) into the buffer */ + KEY_CMDMODE_SUBSTITUTE = 's', /* Substitute character with new text */ + KEY_CMDMODE_TFINDINLINE = 't', /* Find within current line, cursor at previous char */ + KEY_CMDMODE_WORDFWD = 'w', /* Scan to next word */ KEY_CMDMODE_DEL = 'x', /* Delete a single character */ + KEY_CMDMODE_YANK = 'y', /* "yy" yanks the current line(s) into the buffer */ + + KEY_CMDMODE_CR = '\r', /* CR moves to first non-space on next line */ + KEY_CMDMODE_NL = '\n', /* NL moves to first non-space on next line */ KEY_CMDMODE_APPENDEND = 'A', /* Enter insertion mode at the end of the current line */ + KEY_CMDMODE_CHANGETOEOL = 'C', /* Change (del to EOL and go to insert mode */ + KEY_CMDMODE_DELTOEOL = 'D', /* Delete to End Of Line */ KEY_CMDMODE_GOTO = 'G', /* Got to line */ + KEY_CMDMODE_TOP = 'H', /* Got to top of screen */ + KEY_CMDMODE_JOIN = 'J', /* Join line below with current line */ + KEY_CMDMODE_BOTTOM = 'L', /* Got to bottom of screen */ KEY_CMDMODE_INSBEGIN = 'I', /* Enter insertion mode at the beginning of the current */ + KEY_CMDMODE_MIDDLE = 'M', /* Got to middle of screen */ + KEY_CMDMODE_FINDPREV = 'N', /* Find previous */ KEY_CMDMODE_OPENABOVE = 'O', /* Enter insertion mode in new line above current line */ + KEY_CMDMODE_PASTEBEFORE = 'P', /* Paste text before cursor location */ KEY_CMDMODE_REPLACE = 'R', /* Replace character(s) under cursor until ESC */ + KEY_CMDMODE_DELBACKWARD = 'X', /* Replace character(s) under cursor until ESC */ + KEY_CMDMODE_SAVEQUIT = 'Z', /* Another one is the same as "wq" */ KEY_CMDMODE_COLONMODE = ':', /* The next character command prefaced with a colon */ KEY_CMDMODE_FINDMODE = '/', /* Enter forward search string */ KEY_CMDMODE_ENDLINE = '$', /* Move cursor to end of current line */ + KEY_CMDMODE_REVFINDMODE = '?', /* Enter forward search string */ + KEY_CMDMODE_REPEAT = '.', /* Repeat last command */ + KEY_CMDMODE_FIRSTCHAR = '^', /* Find first non-whitespace character on line */ + KEY_CMDMODE_NEXTLINE = '+', /* Find first non-whitespace character on line */ + KEY_CMDMODE_PREVLINE = '-', /* Find first non-whitespace character on line */ KEY_CMDMODE_PAGEUP = CTRL('b'), /* Move backward one screen */ KEY_CMDMODE_HALFDOWN = CTRL('d'), /* Move down (forward) one half screen */ @@ -252,9 +301,11 @@ enum vi_mode_s MODE_COMMAND = 0, /* ESC Command mode */ SUBMODE_COLON, /* : Command sub-mode */ SUBMODE_FIND, /* / Search sub-mode */ + SUBMODE_REVFIND, /* ? Search sub-mode */ SUBMODE_REPLACECH, /* r Replace sub-mode 1 */ MODE_INSERT, /* i,I,a,A,o,O Insert mode */ - MODE_REPLACE /* R Replace sub-mode 2 */ + MODE_REPLACE, /* R Replace sub-mode 2 */ + MODE_FINDINLINE /* f Find in line sub-mode */ }; /* This structure represents a cursor position */ @@ -265,6 +316,21 @@ struct vi_pos_s uint16_t column; }; +#ifdef CONFIG_SYSTEM_VI_INCLUDE_UNDO +/* The undo structure. Will be implemented soon. */ + +struct vi_undo_s +{ + off_t curpos; /* Cursor position before operation */ + off_t ybytes; /* Bytes yanked */ + off_t ibytes; /* Bytes inserted */ + FAR char *yank; /* The yanked bytes */ + FAR char *insert; /* The inserted bytes */ + uint8_t type; /* Type of operation */ + uint8_t complete; /* Indicates if this operation complete */ +}; +#endif + /* This structure describes the overall state of the editor */ struct vi_s @@ -272,18 +338,30 @@ struct vi_s struct vi_pos_s cursor; /* Current cursor position */ struct vi_pos_s cursave; /* Saved cursor position */ struct vi_pos_s display; /* Display size */ + FAR struct termcurses_s * tcurs; off_t curpos; /* The current cursor offset into the text buffer */ off_t textsize; /* The size of the text buffer */ off_t winpos; /* Offset corresponding to the start of the display */ off_t prevpos; /* Previous display position */ + off_t vscroll; /* Vertical dislay offset in rows */ uint16_t hscroll; /* Horizontal display offset */ uint16_t value; /* Numeric value entered prior to a command */ + uint16_t reqcolumn; /* Requested column when moving up/down */ uint8_t mode; /* See enum vi_mode_s */ uint8_t cmdlen; /* Length of the command in the scratch[] buffer */ bool modified; /* True: The file has modified */ bool error; /* True: There is an error message on the last line */ bool delarm; /* One more 'd' and the line(s) will be deleted */ bool yankarm; /* One more 'y' and the line(s) will be yanked */ + bool toparm; /* One more 'g' and the cursor moves to the top */ + bool wqarm; /* One more 'Z' is the same as :wq */ + bool fullredraw; /* True to draw all lines on screen */ + bool drawtoeos; /* True to draw all lines to end of screen */ + bool redrawline; /* True to draw current line */ + bool updatereqcol; /* True to update the requested column */ + bool tfind; /* Find in line 't' mode */ + bool revfind; /* In ? reverse find mode */ + bool yankcharmode; /* Indicates yank buffer is char vs. line mode */ /* Buffers */ @@ -291,10 +369,25 @@ struct vi_s size_t txtalloc; /* Current allocated size of the text buffer */ FAR char *yank; /* Dynamically allocated yank buffer */ size_t yankalloc; /* Current allocated size of the yank buffer */ + size_t yanksize; /* Current size of the text in the yank buffer */ - char filename[MAX_STRING]; /* Holds the currently selected filename */ - char findstr[MAX_STRING]; /* Holds the current search string */ - char scratch[SCRATCH_BUFSIZE]; /* For general, scratch usage */ + char filename[MAX_FILENAME]; /* Holds the currently selected filename */ + char findstr[MAX_STRING]; /* Holds the current search string */ + char scratch[SCRATCH_BUFSIZE]; /* For general, scratch usage */ + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_UNDO + struct vi_undo_s undo[CONFIG_SYSTEM_VI_UNDO_LEVELS]; + uint16_t undocount; /* Number of valid undo entries */ + uint16_t undoindex; /* Current index in undo/redo stack */ +#endif + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + char cmdbuf[CMD_BUFSIZE]; /* Last command buffer */ + uint16_t cmdindex; /* Current index within cmdbuf */ + uint16_t cmdcount; /* Count of entries in cmdbuf */ + uint16_t repeatvalue; /* The command repeat value */ + bool cmdrepeat; /* Command repeat is active */ +#endif }; /**************************************************************************** @@ -306,7 +399,7 @@ struct vi_s static void vi_write(FAR struct vi_s *vi, FAR const char *buffer, size_t buflen); static void vi_putch(FAR struct vi_s *vi, char ch); -static char vi_getch(FAR struct vi_s *vi); +static int vi_getch(FAR struct vi_s *vi); #if 0 /* Not used */ static void vi_blinkon(FAR struct vi_s *vi); #endif @@ -341,7 +434,8 @@ static off_t vi_nextline(FAR struct vi_s *vi, off_t pos); static bool vi_extendtext(FAR struct vi_s *vi, off_t pos, size_t increment); -static void vi_shrinkpos(off_t delpos, size_t delsize, FAR off_t *pos); +static void vi_shrinkpos(FAR struct vi_s *vi, off_t delpos, + size_t delsize, FAR off_t *pos); static void vi_shrinktext(FAR struct vi_s *vi, off_t pos, size_t size); /* File access */ @@ -365,6 +459,7 @@ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, uint16_t *pcolumn, off_t *ppos); static void vi_scrollcheck(FAR struct vi_s *vi); static void vi_showtext(FAR struct vi_s *vi); +static void vi_showlinecol(FAR struct vi_s *vi); /* Command mode */ @@ -378,10 +473,16 @@ static void vi_delforward(FAR struct vi_s *vi); static void vi_delbackward(FAR struct vi_s *vi); static void vi_linerange(FAR struct vi_s *vi, off_t *start, off_t *end); static void vi_delline(FAR struct vi_s *vi); -static void vi_yank(FAR struct vi_s *vi); -static void vi_paste(FAR struct vi_s *vi); +static void vi_deltoeol(FAR struct vi_s *vi); +static void vi_yanktext(FAR struct vi_s *vi, off_t start, off_t end, + bool yankcharmode, bool del_after_yank); +static void vi_yank(FAR struct vi_s *vi, bool del_after_yank); +static void vi_paste(FAR struct vi_s *vi, bool paste_before); static void vi_gotoline(FAR struct vi_s *vi); +static void vi_join(FAR struct vi_s *vi); static void vi_cmd_mode(FAR struct vi_s *vi); +static int vi_gotoscreenbottom(FAR struct vi_s *vi, int rows); +static void vi_gotofirstnonwhite(FAR struct vi_s *vi); /* Command sub-modes */ @@ -392,25 +493,41 @@ static void vi_parsecolon(FAR struct vi_s *vi); static void vi_cmd_submode(FAR struct vi_s *vi); static bool vi_findstring(FAR struct vi_s *vi); -static void vi_parsefind(FAR struct vi_s *vi); -static void vi_find_submode(FAR struct vi_s *vi); +static bool vi_revfindstring(FAR struct vi_s *vi); +static void vi_parsefind(FAR struct vi_s *vi, bool revfind); +static void vi_find_submode(FAR struct vi_s *vi, bool revfind); static void vi_replacech(FAR struct vi_s *vi, char ch); static void vi_replacech_submode(FAR struct vi_s *vi); +static void vi_findinline_mode(FAR struct vi_s *vi); + /* Insert and replace modes */ static void vi_insertch(FAR struct vi_s *vi, char ch); static void vi_insert_mode(FAR struct vi_s *vi); -static void vi_replace_mode(FAR struct vi_s *vi); - /* Command line processing */ static void vi_release(FAR struct vi_s *vi); static void vi_showusage(FAR struct vi_s *vi, FAR const char *progname, int exitcode); +/* Find next / prev word processing */ + +static int vi_chartype(char ch); +static off_t vi_findnextword(FAR struct vi_s *vi); +static void vi_gotonextword(FAR struct vi_s *vi); +static off_t vi_findprevword(FAR struct vi_s *vi); +static void vi_gotoprevword(FAR struct vi_s *vi); + +/* Command repeat processing */ + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT +static void vi_saverepeat(FAR struct vi_s *vi, uint16_t ch); +static void vi_appendrepeat(FAR struct vi_s *vi, uint16_t ch); +#endif + /**************************************************************************** * Private Data ****************************************************************************/ @@ -449,6 +566,9 @@ static const char g_fmtfileexists[] = "File exists (add ! to override)"; static const char g_fmtmodified[] = "No write since last change (add ! to override)"; static const char g_fmtnotvalid[] = "Command not valid"; static const char g_fmtnotcmd[] = "Not an editor command: %s"; +static const char g_fmtsrcbot[] = "search hit BOTTOM, continuing at TOP"; +static const char g_fmtsrctop[] = "search hit TOP, continuing at BOTTOM"; +static const char g_fmtinsert[] = "--INSERT--"; /**************************************************************************** * Private Functions @@ -500,8 +620,6 @@ static void vi_write(FAR struct vi_s *vi, FAR const char *buffer, ssize_t nwritten; size_t nremaining = buflen; - //viinfo("buffer=%p buflen=%d\n", buffer, (int)buflen); - /* Loop until all bytes have been successfully written (or until a * unrecoverable error is encountered) */ @@ -562,7 +680,7 @@ static void vi_putch(FAR struct vi_s *vi, char ch) * ****************************************************************************/ -static char vi_getch(FAR struct vi_s *vi) +static int vi_getch(FAR struct vi_s *vi) { char buffer; ssize_t nread; @@ -571,30 +689,42 @@ static char vi_getch(FAR struct vi_s *vi) * error occurs). */ - do + if (vi->tcurs != NULL) { - /* Read one character from the incoming stream */ + int specialkey; + int modifiers; - nread = read(0, &buffer, 1); + /* Get key from termcurses */ - /* Check for error or end-of-file. */ - - if (nread <= 0) + return termcurses_getkeycode(vi->tcurs, &specialkey, &modifiers); + } + else + { + do { - /* EINTR is not really an error; it simply means that a signal we - * received while waiting for input. - */ + /* Read one character from the incoming stream */ - int errcode = errno; - if (nread == 0 || errcode != EINTR) + nread = read(0, &buffer, 1); + + /* Check for error or end-of-file. */ + + if (nread <= 0) { - fprintf(stderr, "ERROR: read from stdin failed: %d\n", - errcode); - exit(EXIT_FAILURE); + /* EINTR is not really an error; it simply means that a signal + * we received while waiting for input. + */ + + int errcode = errno; + if (nread == 0 || errcode != EINTR) + { + fprintf(stderr, "ERROR: read from stdin failed: %d\n", + errcode); + exit(EXIT_FAILURE); + } } } + while (nread < 1); } - while (nread < 1); /* On success, return the character that was read */ @@ -603,21 +733,18 @@ static char vi_getch(FAR struct vi_s *vi) } /**************************************************************************** - * Name: vi_blinkon + * Name: vi_clearbottomline * * Description: - * Enable the blinking attribute at the current cursor location + * Clear the bottom statusline. * ****************************************************************************/ -#if 0 /* Not used */ -static void vi_blinkon(FAR struct vi_s *vi) +static void vi_clearbottomline(FAR struct vi_s *vi) { - /* Send the VT100 BLINKON command */ - - vi_write(vi, g_blinkon, sizeof(g_blinkon)); + vi_setcursor(vi, vi->display.row-1, 0); + vi_clrtoeol(vi); } -#endif /**************************************************************************** * Name: vi_boldon @@ -694,23 +821,6 @@ static void vi_cursoroff(FAR struct vi_s *vi) vi_write(vi, g_cursoroff, sizeof(g_cursoroff)); } -/**************************************************************************** - * Name: vi_cursorhome - * - * Description: - * Move the current cursor to the upper left hand corner of the display - * - ****************************************************************************/ - -#if 0 /* Not used */ -static void vi_cursorhome(FAR struct vi_s *vi) -{ - /* Send the VT100 CURSORHOME command */ - - vi_write(vi, g_cursorhome, sizeof(g_cursorhome)); -} -#endif - /**************************************************************************** * Name: vi_setcursor * @@ -751,23 +861,6 @@ static void vi_clrtoeol(FAR struct vi_s *vi) vi_write(vi, g_erasetoeol, sizeof(g_erasetoeol)); } -/**************************************************************************** - * Name: vi_clrscreen - * - * Description: - * Clear the entire display - * - ****************************************************************************/ - -#if 0 /* Not used */ -static void vi_clrscreen(FAR struct vi_s *vi) -{ - /* Send the VT100 CLRSCREEN command */ - - vi_write(vi, g_clrscreen, sizeof(g_clrscreen)); -} -#endif - /**************************************************************************** * Name: vi_scrollup * @@ -788,6 +881,11 @@ static void vi_scrollup(FAR struct vi_s *vi, uint16_t nlines) vi_write(vi, g_index, sizeof(g_index)); } + + /* Ensure bottom line is clared */ + + vi_setcursor(vi, vi->display.row-1, 0); + vi_clrtoeol(vi); } /**************************************************************************** @@ -802,6 +900,11 @@ static void vi_scrolldown(FAR struct vi_s *vi, uint16_t nlines) { viinfo("nlines=%d\n", nlines); + /* Ensure the bottom line is cleared after the scroll */ + + vi_setcursor(vi, vi->display.row-2, 0); + vi_clrtoeol(vi); + /* Scroll for the specified number of lines */ for (; nlines; nlines--) @@ -940,6 +1043,11 @@ static off_t vi_lineend(FAR struct vi_s *vi, off_t pos) pos++; } + if (vi->text[pos] == '\n') + { + pos--; + } + viinfo("Return pos=%ld\n", (long)pos); return pos; } @@ -956,7 +1064,7 @@ static off_t vi_nextline(FAR struct vi_s *vi, off_t pos) { /* Position at the end of the current line */ - pos = vi_lineend(vi, pos); + pos = vi_lineend(vi, pos) + 1; /* If this is not the last byte in the buffer, then increment by one * for position of the first byte of the next line. @@ -1001,7 +1109,7 @@ static bool vi_extendtext(FAR struct vi_s *vi, off_t pos, size_t increment) size_t allocsize = ALIGN_GULP(vi->textsize + increment); alloc = realloc(vi->text, allocsize); - if (!alloc) + if (alloc == NULL) { /* Reallocation failed */ @@ -1046,7 +1154,8 @@ static bool vi_extendtext(FAR struct vi_s *vi, off_t pos, size_t increment) * ****************************************************************************/ -static void vi_shrinkpos(off_t delpos, size_t delsize, FAR off_t *pos) +static void vi_shrinkpos(FAR struct vi_s *vi, off_t delpos, size_t delsize, + FAR off_t *pos) { viinfo("delpos=%ld delsize=%ld pos=%ld\n", (long)delpos, (long)delsize, (long)*pos); @@ -1068,6 +1177,23 @@ static void vi_shrinkpos(off_t delpos, size_t delsize, FAR off_t *pos) { *pos = delpos; } + + /* Ensure the position is within the text bounds in case the + * text at the end of the buffer is being deleted + */ + + if (*pos >= vi->textsize && vi->mode != MODE_INSERT && + vi->mode != MODE_REPLACE) + { + *pos = vi->textsize - 1; + } + + /* Check pos for negative bounds */ + + if (*pos < 0) + { + *pos = 0; + } } /**************************************************************************** @@ -1095,17 +1221,29 @@ static void vi_shrinktext(FAR struct vi_s *vi, off_t pos, size_t size) vi->text[i - size] = vi->text[i]; } + /* Ensure we are not shrinking more than we have */ + + if (size > vi->textsize) + { + size = vi->textsize; + } + /* Adjust sizes and positions */ vi->textsize -= size; vi->modified = true; - vi_shrinkpos(pos, size, &vi->curpos); - vi_shrinkpos(pos, size, &vi->winpos); - vi_shrinkpos(pos, size, &vi->prevpos); + vi_shrinkpos(vi, pos, size, &vi->curpos); + vi_shrinkpos(vi, pos, size, &vi->winpos); + vi_shrinkpos(vi, pos, size, &vi->prevpos); /* Reallocate the buffer to free up memory no longer in use */ allocsize = ALIGN_GULP(vi->textsize); + if (allocsize == 0) + { + allocsize = TEXT_GULP_SIZE; + } + if (allocsize < vi->txtalloc) { alloc = realloc(vi->text, allocsize); @@ -1151,7 +1289,7 @@ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, result = stat(filename, &buf); if (result < 0) { - vi_message(vi, "\"%s\" [New File]\n", filename); + vi_message(vi, "\"%s\" [New File]", filename); return false; } @@ -1163,7 +1301,7 @@ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, return false; } - /* Open the file for reading*/ + /* Open the file for reading */ stream = fopen(filename, "r"); if (!stream) @@ -1197,6 +1335,7 @@ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, } } + vi->fullredraw = true; (void)fclose(stream); return ret; } @@ -1212,8 +1351,9 @@ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, static bool vi_savetext(FAR struct vi_s *vi, FAR const char *filename, off_t pos, size_t size) { - FILE *stream; + FAR FILE *stream; size_t nwritten; + int len; viinfo("filename=\"%s\" pos=%ld size=%ld\n", filename, (long)pos, (long)size); @@ -1242,6 +1382,9 @@ static bool vi_savetext(FAR struct vi_s *vi, FAR const char *filename, } (void)fclose(stream); + + len = sprintf(vi->scratch, "%dC written", nwritten); + vi_write(vi, vi->scratch, len); return true; } @@ -1309,7 +1452,8 @@ static void vi_setmode(FAR struct vi_s *vi, uint8_t mode, long value) vi->mode = mode; vi->delarm = false; vi->yankarm = false; -//vi->error = false; need to preserve until vi_showtext is called + vi->toparm = false; + vi->wqarm = false; vi->value = value; vi->cmdlen = 0; } @@ -1416,7 +1560,8 @@ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, * is within range. */ - for (pos = start, column = 0; pos < end; pos++) + for (pos = start, column = 0; pos < end && (column < vi->reqcolumn || + vi->updatereqcol); pos++) { /* Is there a newline terminator at this position? */ @@ -1424,6 +1569,13 @@ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, { /* Yes... break out of the loop return the cursor column */ + if (vi->mode != MODE_INSERT && vi->mode != MODE_REPLACE && + pos != start) + { + pos--; + column--; + } + break; } @@ -1444,6 +1596,16 @@ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, } } + /* Keep cursor in bounds of text (i.e. not at the '\n') */ + + if (((pos == vi->textsize && column != 0) || + (vi->text[pos] == '\n' && pos != start)) && + vi->mode != MODE_INSERT && vi->mode != MODE_REPLACE) + { + pos--; + column--; + } + /* Now return the requested values */ if (ppos) @@ -1455,6 +1617,12 @@ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, { *pcolumn = column; } + + if (vi->updatereqcol) + { + vi->reqcolumn = column; + vi->updatereqcol = false; + } } /**************************************************************************** @@ -1485,6 +1653,8 @@ static void vi_scrollcheck(FAR struct vi_s *vi) * line line and check again */ vi->winpos = vi_prevline(vi, vi->winpos); + vi->vscroll--; + vi->fullredraw = true; } /* Reset the cursor row position so that it is relative to the @@ -1499,11 +1669,13 @@ static void vi_scrollcheck(FAR struct vi_s *vi) /* Check if the cursor row position is below the bottom of the display */ - for (; vi->cursor.row >= vi->display.row; vi->cursor.row--) + for (; vi->cursor.row >= vi->display.row-1; vi->cursor.row--) { /* Yes.. move the window position down by one line and check again */ vi->winpos = vi_nextline(vi, vi->winpos); + vi->vscroll++; + vi->fullredraw = true; } /* Check if the cursor column is on the display. vi_windowpos returns the @@ -1522,8 +1694,9 @@ static void vi_scrollcheck(FAR struct vi_s *vi) while (column < 0) { - column += TABSIZE; - vi->hscroll -= TABSIZE; + column += VI_TABSIZE; + vi->hscroll -= VI_TABSIZE; + vi->fullredraw = true; } /* If the cursor column lies to the right of the display, then adjust @@ -1533,8 +1706,9 @@ static void vi_scrollcheck(FAR struct vi_s *vi) while (column >= vi->display.column) { - column -= TABSIZE; - vi->hscroll += TABSIZE; + column -= VI_TABSIZE; + vi->hscroll += VI_TABSIZE; + vi->fullredraw = true; } /* That final adjusted position is the display cursor column */ @@ -1553,7 +1727,7 @@ static void vi_scrollcheck(FAR struct vi_s *vi) */ for (nlines = 0, pos = vi->prevpos; - pos != vi->winpos && nlines < vi->display.row; + pos != vi->winpos && nlines < vi->display.row-1; nlines++) { pos = vi_nextline(vi, pos); @@ -1561,9 +1735,10 @@ static void vi_scrollcheck(FAR struct vi_s *vi) /* Then scroll up that number of lines */ - if (nlines < vi->display.row) + if (nlines < vi->display.row-1) { vi_scrollup(vi, nlines); + vi->fullredraw = true; } } @@ -1574,9 +1749,8 @@ static void vi_scrollcheck(FAR struct vi_s *vi) else if (vi->winpos < vi->prevpos) { - for (nlines = 0, pos = vi->prevpos; - pos != vi->winpos && nlines < vi->display.row; + pos != vi->winpos && nlines < vi->display.row - 1; nlines++) { pos = vi_prevline(vi, pos); @@ -1584,9 +1758,10 @@ static void vi_scrollcheck(FAR struct vi_s *vi) /* Then scroll down that number of lines */ - if (nlines < vi->display.row) + if (nlines < vi->display.row-1) { vi_scrolldown(vi, nlines); + vi->fullredraw = true; } } @@ -1612,11 +1787,13 @@ static void vi_scrollcheck(FAR struct vi_s *vi) static void vi_showtext(FAR struct vi_s *vi) { off_t pos; + off_t writefrom; uint16_t row; uint16_t endrow; uint16_t column; uint16_t endcol; uint16_t tabcol; + bool redraw_line; /* Check if any of the preceding operations will cause the display to * scroll. @@ -1624,15 +1801,18 @@ static void vi_showtext(FAR struct vi_s *vi) vi_scrollcheck(vi); + /* If no display updates needed after scrollcheck, just return */ + + if (!vi->fullredraw && !vi->drawtoeos && !vi->redrawline) + { + return; + } + /* If there is an error message at the bottom of the display, then * do not update the last line. */ - endrow = vi->display.row; - if (vi->error) - { - endrow--; - } + endrow = vi->display.row-1; /* Make sure that all character attributes are disabled; Turn off the * cursor during the update. @@ -1641,14 +1821,63 @@ static void vi_showtext(FAR struct vi_s *vi) vi_attriboff(vi); vi_cursoroff(vi); + /* Set loop control variables based on draw mode */ + + if (vi->fullredraw) + { + /* Start from beginning of display */ + + pos = vi->winpos; + row = 0; + + /* Ensure drawtoeos and redraw line are also set */ + + vi->drawtoeos = true; + vi->redrawline = true; + } + else + { + /* Start drawing from current row */ + + pos = vi_linebegin(vi, vi->curpos); + row = vi->cursor.row; + + if (vi->drawtoeos) + { + vi->redrawline = true; + } + else + { + endrow = row + 1; + } + } + /* Write each line to the display, handling horizontal scrolling and * tab expansion. */ - for (pos = vi->winpos, row = 0; - pos < vi->textsize && row < endrow; - row++) + for (; pos < vi->textsize && row < endrow; row++) { + /* Test if this line needs to be redrawn */ + + redraw_line = true; + if (!vi->redrawline) + { + redraw_line = false; + } + else if (row+1 < vi->cursor.row && !vi->fullredraw) + { + redraw_line = false; + } + else if (row > vi->cursor.row && !vi->drawtoeos) + { + redraw_line = false; + } + else if (row == vi->cursor.row && !vi->redrawline) + { + redraw_line = false; + } + /* Get the last column on this row. Avoid writing into the last byte * on the screen which may trigger a scroll. */ @@ -1671,51 +1900,72 @@ static void vi_showtext(FAR struct vi_s *vi) * the end of the line. */ - vi_setcursor(vi, row, 0); - vi_clrtoeol(vi); - - /* Loop for each column */ - - for (column = 0; pos < vi->textsize && column < endcol; pos++) + if (redraw_line) { - /* Break out of the loop if we encounter the newline before the - * last column is encountered. - */ + vi_setcursor(vi, row, 0); - if (vi->text[pos] == '\n') + /* Loop for each column */ + + writefrom = pos; + for (column = 0; pos < vi->textsize && column < endcol; pos++) { - break; - } + /* Break out of the loop if we encounter the newline before the + * last column is encountered. + */ - /* Perform TAB expansion */ - - else if (vi->text[pos] == '\t') - { - tabcol = NEXT_TAB(column); - if (tabcol < endcol) + if (vi->text[pos] == '\n') { - for (; column < tabcol; column++) + break; + } + + /* Perform TAB expansion */ + + else if (vi->text[pos] == '\t') + { + /* Write collected characters */ + + if (writefrom != pos) { - vi_putch(vi, ' '); + vi_write(vi, &vi->text[writefrom], pos-writefrom); + } + + tabcol = NEXT_TAB(column); + if (tabcol < endcol) + { + for (; column < tabcol; column++) + { + vi_putch(vi, ' '); + } + + writefrom = pos + 1; + } + else + { + /* Break out of the loop... there is nothing left on the + * line but whitespace. + */ + + writefrom = pos; + break; } } + + /* Add the normal character to the display */ + else - { - /* Break out of the loop... there is nothing left on the - * line but whitespace. - */ - - break; - } + { + column++; + } } - /* Add the normal character to the display */ + /* Write collected characters */ - else + if (writefrom != pos) { - vi_putch(vi, vi->text[pos]); - column++; + vi_write(vi, &vi->text[writefrom], pos-writefrom); } + + vi_clrtoeol(vi); } /* Skip to the beginning of the next line */ @@ -1723,24 +1973,69 @@ static void vi_showtext(FAR struct vi_s *vi) pos = vi_nextline(vi, pos); } - /* If there was not enough text to fill the display, clear the - * remaining lines (except for any possible error line at the - * bottom of the display). - */ - - for (; row < endrow; row++) + if (pos == vi->textsize && vi->text[pos-1] == '\n') { - /* Set the cursor position to the beginning of the row and clear to - * the end of the line. - */ - vi_setcursor(vi, row, 0); vi_clrtoeol(vi); + row++; + } + + /* If we are drawing to EOS, then draw trailing '~' */ + + if (vi->drawtoeos) + { + /* If there was not enough text to fill the display, clear the + * remaining lines (except for any possible error line at the + * bottom of the display). + */ + + for (; row < endrow; row++) + { + /* Set the cursor position to the beginning of the row and clear to + * the end of the line. + */ + + vi_setcursor(vi, row, 0); + if (row != endrow && row != 0) + { + vi_putch(vi, '~'); + } + vi_clrtoeol(vi); + } } /* Turn the cursor back on */ vi_cursoron(vi); + vi->fullredraw = false; + vi->drawtoeos = false; + vi->redrawline = false; +} + +/**************************************************************************** + * Name: vi_showlinecol + * + * Description: + * Update the current line/column on the display status line. + * + ****************************************************************************/ + +static void vi_showlinecol(FAR struct vi_s *vi) +{ + size_t len; + + /* Move to bototm line for display */ + + vi_cursoroff(vi); + vi_setcursor(vi, vi->display.row-1, vi->display.column-15); + + len = snprintf(vi->scratch, SCRATCH_BUFSIZE, "%d,%d", + vi->cursor.row + vi->vscroll + 1, + vi->cursor.column + vi->hscroll + 1); + vi_write(vi, vi->scratch, len); + + vi_clrtoeol(vi); + vi_cursoron(vi); } /**************************************************************************** @@ -1783,7 +2078,7 @@ static void vi_cusorup(FAR struct vi_s *vi, int nlines) * cursor position on the current line. */ - end = start + vi->cursor.column + vi->hscroll; + end = start + vi->reqcolumn; vi_windowpos(vi, start, end, NULL, &vi->curpos); } } @@ -1824,7 +2119,7 @@ static void vi_cursordown(FAR struct vi_s *vi, int nlines) * cursor position on the current line. */ - end = start + vi->cursor.column + vi->hscroll; + end = start + vi->reqcolumn; vi_windowpos(vi, start, end, NULL, &vi->curpos); } } @@ -1833,9 +2128,9 @@ static void vi_cursordown(FAR struct vi_s *vi, int nlines) * Name: vi_cursorleft * * Description: - * Move the cursor left 'ncolumns' columns in the text buffer (without moving - * to the preceding line). Note that a repetition count of 0 means to - * perform the movement once. + * Move the cursor left 'ncolumns' columns in the text buffer (without + * moving to the preceding line). Note that a repetition count of 0 means + * to perform the movement once. * ****************************************************************************/ @@ -1852,7 +2147,9 @@ static off_t vi_cursorleft(FAR struct vi_s *vi, off_t curpos, int ncolumns) for (remaining = (ncolumns < 1 ? 1 : ncolumns); curpos > 0 && remaining > 0 && vi->text[curpos - 1] != '\n'; - curpos--, remaining--); + curpos--, remaining--) + { + } return curpos; } @@ -1874,16 +2171,94 @@ static off_t vi_cursorright(FAR struct vi_s *vi, off_t curpos, int ncolumns) viinfo("curpos=%ld ncolumns=%d\n", curpos, ncolumns); /* Loop incrementing the cursor position for each repetition count. Break - * out early if we hit either the end of the text buffer, or the end of the line. + * out early if we hit either the end of the text buffer, or the end of + * the line. */ for (remaining = (ncolumns < 1 ? 1 : ncolumns); curpos < vi->textsize && remaining > 0 && vi->text[curpos] != '\n'; - curpos++, remaining--); + curpos++, remaining--) + { + } + +#if 0 + if (vi->text[curpos] == '\n' || (curpos == vi->textsize && + vi->mode != MODE_INSERT && vi->mode != MODE_REPLACE)) + { + curpos--; + } +#endif return curpos; } +/**************************************************************************** + * Name: vi_gotoscreenbottom + * + * Description: + * Move the cursor to the bottom of the screen or the bottom line of + * the file if it doesn't occupy the entire screen. + * + ****************************************************************************/ + +static int vi_gotoscreenbottom(FAR struct vi_s *vi, int rows) +{ + off_t pos; + int row; + int target = rows > 0 ? rows >> 1 : vi->display.row - 2; + + vi->curpos = vi->winpos; + row = 0; + while (row < target) + { + /* Get position of next row down */ + + pos = vi_nextline(vi, vi->curpos); + + /* Test for end of file before bottom of screen */ + + if (pos == vi->curpos) + { + break; + } + + if (pos == vi->textsize) + { + /* Test for empty line at the bottom */ + + row++; + vi->curpos = pos; + break; + } + + vi->curpos = pos; + row++; + } + + /* Report the location of the bottom row */ + + return row; +} + +/**************************************************************************** + * Name: vi_gotofirstnonwhite + * + * Description: + * Move the cursor to the first non-whitespace character on the + * current line. + * + ****************************************************************************/ + +static void vi_gotofirstnonwhite(FAR struct vi_s *vi) +{ + vi->curpos = vi_linebegin(vi, vi->curpos); + while (vi->curpos <= vi->textsize && (vi->text[vi->curpos] == ' ' || + vi->text[vi->curpos] == '\t')) + { + vi->curpos++; + } +} + /**************************************************************************** * Name: vi_delforward * @@ -1894,23 +2269,60 @@ static off_t vi_cursorright(FAR struct vi_s *vi, off_t curpos, int ncolumns) static void vi_delforward(FAR struct vi_s *vi) { + off_t start; off_t end; - size_t size; + bool at_end = false; viinfo("curpos=%ld value=%ld\n", (long)vi->curpos, vi->value); + /* Test for empty file */ + + if (vi->textsize == 0) + { + return; + } + + /* Test for empy line deletion and simply return */ + + if (vi->cursor.column == 0) + { + /* If at end of file, just return */ + + if (vi->curpos == vi->textsize || + vi->text[vi->curpos] == '\n') + { + return; + } + } + /* Get the cursor position as if we would have move the cursor right N * times (which might be curpos, vi->value); + /* Test for deletion at end of the line */ + + if (end == vi->curpos && vi->cursor.column > 0) + { + end++; + at_end = true; + } + /* The difference from the current position then is the number of * characters to be deleted. */ - size = end - vi->curpos; - vi_shrinktext(vi, vi->curpos, size); + start = vi->curpos; + + vi_yanktext(vi, start, end-1, true, true); + vi->curpos = start; + if (at_end) + { + vi->curpos--; + } + + vi->redrawline = true; } /**************************************************************************** @@ -1925,10 +2337,18 @@ static void vi_delbackward(FAR struct vi_s *vi) { off_t start; off_t end; - size_t size; + off_t x; viinfo("curpos=%ld value=%ld\n", (long)vi->curpos, vi->value); + /* Test if we are at beginning of line */ + + if (vi->curpos == 0 || vi->text[vi->curpos] == '\n' || + vi->text[vi->curpos-1] == '\n') + { + return; + } + /* Back up one character. This is where the deletion will end */ end = vi_cursorleft(vi, vi->curpos, 1); @@ -1937,14 +2357,32 @@ static void vi_delbackward(FAR struct vi_s *vi) * times (which might be value); + if (vi->value > 1) + { + start = vi_cursorleft(vi, end, vi->value -1); + } + else + { + start = end; + } + + for (x = end; x >= start; x--) + { + /* Test if \n' in the range. Don't delete through \n */ + + if (vi->text[x] == '\n') + { + start = x + 1; + break; + } + } /* The difference from the current position then is the number of * characters to be deleted. */ - size = end - start; - vi_shrinktext(vi, start, size); + vi_yanktext(vi, start, end, true, true); + vi->redrawline = true; } /**************************************************************************** @@ -1978,6 +2416,10 @@ static void vi_linerange(FAR struct vi_s *vi, off_t *start, off_t *end) } *end = vi_lineend(vi, next); + if (*end != vi->textsize) + { + (*end)++; + } } /**************************************************************************** @@ -1990,21 +2432,145 @@ static void vi_linerange(FAR struct vi_s *vi, off_t *start, off_t *end) static void vi_delline(FAR struct vi_s *vi) { - off_t delsize; - off_t start; - off_t end; + /* Yank and remove text from the buffer */ - /* Get the offset in the text buffer corresponding to the range of lines to - * be deleted - */ + vi_yank(vi, true); + vi->drawtoeos = true; +} - vi_linerange(vi, &start, &end); - viinfo("start=%ld end=%ld\n", (long)start, (long)end); +/**************************************************************************** + * Name: vi_deltoeol + * + * Description: + * Delete to end of line. + * + ****************************************************************************/ - /* Remove the text from the text buffer */ +static void vi_deltoeol(FAR struct vi_s *vi) +{ + int start; + int end; - delsize = end - start + 1; - vi_shrinktext(vi, start, delsize); + /* If we are at the end of the line, then return */ + + if (vi->curpos == vi->textsize || vi->text[vi->curpos] == '\n') + { + return; + } + + /* Determine start and end location */ + + start = vi->curpos; + end = vi_lineend(vi, vi->curpos); + if (end == vi->textsize || vi->text[end] == '\n') + { + end--; + } + + /* Yank and remove text from the buffer */ + + vi_yanktext(vi, start, end, true, true); + if (start != vi->textsize && vi->text[start] != '\n') + { + vi->curpos = start-1; + } + else + { + vi->curpos = start; + } + + vi->redrawline = true; +} + +/**************************************************************************** + * Name: vi_yanktext + * + * Description: + * Yank specified text from the text buffer and delete if requested. + * + ****************************************************************************/ + +static void vi_yanktext(FAR struct vi_s *vi, off_t start, off_t end, + bool yankcharmode, bool del_after_yank) +{ + int append_lf = 0; + size_t alloc; + size_t size; + + /* At end of file, in line yank mode, if there is no LF, we append one */ + + if (vi->text[end] != '\n' && !yankcharmode) + { + append_lf = 1; + } + + /* Allocate a yank buffer big enough to hold the lines */ + + size = end - start + 1; + alloc = size + append_lf; + + if (alloc < CONFIG_SYSTEM_VI_YANK_THRESHOLD) + { + alloc = CONFIG_SYSTEM_VI_YANK_THRESHOLD; + } + + /* Free any previously yanked lines */ + + if (vi->yank) + { + /* Free the buffer only if it is too small or if it is larger + * than the YANK_THRESHOLD and we need less than that. + */ + + if (alloc > vi->yankalloc || + (alloc == CONFIG_SYSTEM_VI_YANK_THRESHOLD && + vi->yankalloc > CONFIG_SYSTEM_VI_YANK_THRESHOLD)) + { + free(vi->yank); + vi->yank = NULL; + } + } + + /* Allocate buffer if not already allocated */ + + if (!vi->yank) + { + vi->yankalloc = alloc; + vi->yank = (FAR char *)malloc(vi->yankalloc); + } + + vi->yankcharmode = yankcharmode; + + if (!vi->yank) + { + vi_error(vi, g_fmtallocfail); + vi->yankalloc = 0; + vi->yanksize = 0; + return; + } + + /* Copy the block from the text buffer to the yank buffer */ + + vi->yanksize = size; + memcpy(vi->yank, &vi->text[start], size); + + /* Append \n if needed */ + + if (append_lf > 0) + { + vi->yank[vi->yanksize] = '\n'; + } + + /* Remove the yanked text from the text buffer */ + + if (del_after_yank) + { + vi_shrinktext(vi, start, vi->yanksize); + } + + /* Account for appended lf in yankalloc */ + + vi->yanksize += append_lf; } /**************************************************************************** @@ -2016,44 +2582,59 @@ static void vi_delline(FAR struct vi_s *vi) * ****************************************************************************/ -static void vi_yank(FAR struct vi_s *vi) +static void vi_yank(FAR struct vi_s *vi, bool del_after_yank) { off_t start; off_t end; + off_t yank_end; + off_t textsize; + int pos_increment = 0; /* Get the offset in the text buffer corresponding to the range of lines to * be yanked */ vi_linerange(vi, &start, &end); + textsize = vi->textsize; + + /* Do end of file bounds checking */ + + if (end >= textsize) + { + end = textsize - 1; + } + + if (start >= textsize) + { + start = textsize -1; + } + + /* When yanking last line with \n, don't delete the \n */ + + yank_end = end; + if (del_after_yank && end == textsize - 1 && start != end && + vi->text[end] == '\n') + { + yank_end--; + pos_increment = 1; + } + viinfo("start=%ld end=%ld\n", (long)start, (long)end); - /* Free any previously yanked lines */ + vi_yanktext(vi, start, yank_end, 0, del_after_yank); - if (vi->yank) + /* If the last line was yanked, then remove the '\n' on the + * previous line. + */ + + if (end + 1 == textsize && start != end && del_after_yank) { - free(vi->yank); + vi_shrinktext(vi, vi->textsize-1, 1); } - /* Allocate a yank buffer biggest enough to hold the lines */ + /* Place cursor at beginning of the line */ - vi->yankalloc = end - start + 1; - vi->yank = (FAR char *)malloc(vi->yankalloc); - - if (!vi->yank) - { - vi_error(vi, g_fmtallocfail); - vi->yankalloc = 0; - return; - } - - /* Copy the block from the text buffer to the yank buffer */ - - memcpy(vi->yank, &vi->text[start], vi->yankalloc); - - /* Remove the yanked text from the text buffer */ - - vi_shrinktext(vi, start, vi->yankalloc); + vi->curpos = vi_linebegin(vi, vi->curpos + pos_increment); } /**************************************************************************** @@ -2065,42 +2646,187 @@ static void vi_yank(FAR struct vi_s *vi) * ****************************************************************************/ -static void vi_paste(FAR struct vi_s *vi) +static void vi_paste(FAR struct vi_s *vi, bool paste_before) { off_t start; + off_t new_curpos; + int count; viinfo("curpos=%ld yankalloc=%d\n", (long)vi->curpos, (long)vi->yankalloc); /* Make sure there is something to be yanked */ - if (!vi->yank || vi->yankalloc <= 0) + if (!vi->yank || vi->yanksize <= 0) { return; } - /* Paste at the beginning of the next line */ + /* Get the command count */ - start = vi_nextline(vi, vi->curpos); - - /* Reallocate the text buffer to hold the yank buffer contents at the - * the beginning of the next line. - */ - - if (vi_extendtext(vi, start, vi->yankalloc)) + count = vi->value > 0 ? vi->value : 1; + if (count > 1) { - /* Copy the contents of the yank buffer into the text buffer at the - * position where the start of the next line was. - */ - - memcpy(&vi->text[start], vi->yank, vi->yankalloc); + vi->fullredraw = true; } - /* Free the yank buffer in any event */ + /* Test for char mode paste buffer */ - free(vi->yank); + while (count > 0) + { + if (vi->yankcharmode) + { + off_t pos; - vi->yank = NULL; - vi->yankalloc = 0; + /* Paste at next col to the right of cursor */ + + if (vi->text[vi->curpos] == '\n' || vi->curpos == vi->textsize || + paste_before) + { + pos = vi->curpos; + } + else + { + pos = vi->curpos + 1; + } + + if (vi_extendtext(vi, pos, vi->yanksize)) + { + /* Copy the contents of the yank buffer into the text buffer + * at the position where the start of the next line was. + */ + + memcpy(&vi->text[pos], vi->yank, vi->yanksize); + + /* Advance the cursor */ + + vi->curpos = vi->curpos + vi->yanksize; + if (vi->curpos > vi->textsize || vi->text[vi->curpos] == '\n') + { + vi->curpos--; + } + } + } + else + { + off_t size; + + /* Paste at the beginning of the next line */ + + if (paste_before) + { + start = vi_linebegin(vi, vi->curpos); + } + else + { + start = vi_nextline(vi, vi->curpos); + } + + size = vi->yanksize; + + /* Test if pasting at end of file */ + + new_curpos = start; + if (start >= vi->textsize && vi->text[vi->textsize-1] != '\n') + { + off_t textsize = vi->textsize; + + vi->curpos = vi->textsize; + vi_insertch(vi, '\n'); + start = vi->textsize; + new_curpos = start; + + /* Don't append the \n' in the yank buffer */ + + if (vi->text[textsize-1] != '\n') + { + size--; + } + } + + /* Ensure start <= textsize */ + + else if (start >= vi->textsize) + { + start = vi->textsize; + new_curpos = start + size; + vi->fullredraw = true; + } + + /* Reallocate the text buffer to hold the yank buffer contents at + * the beginning of the next line. + */ + + if (vi_extendtext(vi, start, size)) + { + /* Copy the contents of the yank buffer into the text buffer + * at the position where the start of the next line was. + */ + + memcpy(&vi->text[start], vi->yank, size); + + /* Advance to next line */ + + vi->curpos = new_curpos; + } + } + + count--; + } + + /* Redraw everything below this point */ + + vi->drawtoeos = true; +} + +/**************************************************************************** + * Name: vi_join + * + * Description: + * Join line below with current line. + * + ****************************************************************************/ + +static void vi_join(FAR struct vi_s *vi) +{ + off_t start; + off_t end; + + /* Test if we are at end of file */ + + if (vi->curpos + 1 >= vi->textsize) + { + return; + } + + start = vi_lineend(vi, vi->curpos); + + /* Ensure the line ends with '\n' */ + + if (vi->text[start+1] != '\n') + { + return; + } + + /* Convert the '\n' to a space */ + + vi->text[++start] = ' '; + end = start + 1; + + /* Skip all spaces and tabs on next line */ + + while ((vi->text[end] == ' ' || vi->text[end] == '\t') && + end < vi->textsize) + { + end++; + } + + if (start+1 != end) + { + vi_shrinktext(vi, start+1, end - (start+1)); + } + + vi->curpos = start; + vi->drawtoeos = true; } /**************************************************************************** @@ -2148,8 +2874,549 @@ static void vi_gotoline(FAR struct vi_s *vi) vi->curpos = vi_linebegin(vi, vi->textsize); } + + vi->fullredraw = true; } +/**************************************************************************** + * Name: vi_chartype + * + * Description: + * Determine and return the type of character (i.e. alpha, space, + * punctuation). + * + ****************************************************************************/ + +static int vi_chartype(char ch) +{ + int type; + + if (ch == ' ' || ch == '\t') + { + type = VI_CHAR_SPACE; + } + + /* Test for alpha, numeric or '_' */ + + else if (isalnum(ch) || ch == '_') + { + type = VI_CHAR_ALPHA; + } + + /* Test for CR or NL */ + + else if (ch == '\r' || ch == '\n') + { + type = VI_CHAR_CRLF; + } + + /* Must be punctuation */ + + else + { + type = VI_CHAR_PUNCT; + } + + return type; +} + +/**************************************************************************** + * Name: vi_findnextword + * + * Description: + * Find the position in the text buffer of the start of the next word. + * + ****************************************************************************/ + +static off_t vi_findnextword(FAR struct vi_s *vi) +{ + int srch_type; + int pos_type; + off_t pos; + + /* Get the type of character under the cursor so we know what the + * next "word" looks like. + */ + + srch_type = vi_chartype(vi->text[vi->curpos]); + pos = vi->curpos + 1; + + for (; pos < vi->textsize; pos++) + { + /* Get type of the next character */ + + pos_type = vi_chartype(vi->text[pos]); + + /* Skip CR and NL */ + + if (pos_type == VI_CHAR_CRLF) + { + /* Change to search type SPACE */ + + srch_type = VI_CHAR_SPACE; + continue; + } + + /* We we were over a space, then any non-space is the next word */ + + if (srch_type == VI_CHAR_SPACE && + pos_type != VI_CHAR_SPACE) + { + break; + } + + /* Test for next punctuation if search type is alpha */ + + if (srch_type == VI_CHAR_ALPHA && + pos_type == VI_CHAR_PUNCT) + { + break; + } + + /* Test for next alpha if search type is punctuation */ + + if (srch_type == VI_CHAR_PUNCT && + pos_type == VI_CHAR_ALPHA) + { + break; + } + + /* Test for alpha search followed by space. Then switch the search type + * to space so we find whatever is next. + */ + + if ((srch_type == VI_CHAR_ALPHA || srch_type == VI_CHAR_PUNCT) && + pos_type == VI_CHAR_SPACE) + { + srch_type = VI_CHAR_SPACE; + } + } + + /* Limit position to within the valid text range */ + + if (pos == vi->textsize) + { + pos--; + } + + /* Return the position of the next word */ + + return pos; +} + +/**************************************************************************** + * Name: vi_gotonextword + * + * Description: + * Position the cursor at the start of the next word. + * + ****************************************************************************/ + +static void vi_gotonextword(FAR struct vi_s *vi) +{ + int count; + int x; + off_t start = vi->curpos; + off_t end; + off_t pos; + bool crfound; + + /* Loop for the specified search count */ + + count = vi->value > 0 ? vi->value : 1; + for (x = 0; x < count; x++ ) + { + /* Get position of next word */ + + vi->curpos = vi_findnextword(vi); + } + + /* Test if yank or delete are armed */ + + if (vi->yankarm || vi->delarm) + { + /* Rewind so we don't yank skipped whitespace */ + + pos = vi->curpos; + crfound = false; + + while ((vi->text[pos-1] == ' ' || vi->text[pos-1] == '\t' || + vi->text[pos-1] == '\n') && pos > start) + { + /* We rewind only if '\n' found before non-space */ + + pos--; + if (vi->text[pos] == '\n') + { + crfound = true; + } + + } + + if (crfound) + { + vi->curpos = pos; + } + + /* If the yank / delete count is 1, then limit the yank/delete so it + * doesn't contain any '\n' characters. + */ + + if (count == 1) + { + /* Scan the text range and look for '\n' */ + + for (x = start; x < vi->curpos; x++) + { + /* Test for '\n' */ + + if (vi->text[x] == '\n') + { + /* Modify the yank / delete range */ + + vi->curpos = x; + break; + } + } + } + else + { + /* Multi-line delete? */ + + vi->fullredraw = vi->delarm; + } + + /* Perform the yank */ + + end = vi->curpos + 1 == vi->textsize ? vi->curpos : vi->curpos - 1; + vi_yanktext(vi, start, end, 1, vi->delarm); + if (vi->delarm) + { + /* Redraw line if text deleted */ + + vi->redrawline = true; + } + + /* Restore the original curpos */ + + vi->curpos = start; + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Setup command repeat */ + + if (vi->delarm) + { + vi_saverepeat(vi, 'd'); + vi_appendrepeat(vi, 'w'); + } +#endif + + vi->delarm = false; + vi->yankarm = false; + } +} + +/**************************************************************************** + * Name: vi_findprevword + * + * Description: + * Find the position in the text buffer of the start of the previous word. + * + ****************************************************************************/ + +static off_t vi_findprevword(FAR struct vi_s *vi) +{ + int srch_type; + int pos_type; + off_t pos; + + /* If we are basically at the beginning of the file, then the task + * is simple. + */ + + if (vi->curpos < 2) + { + return 0; + } + + /* Get the type of character under the cursor so we know what the + * next "word" looks like. + */ + + srch_type = vi_chartype(vi->text[vi->curpos]); + pos = vi->curpos - 1; + pos_type = vi_chartype(vi->text[pos]); + + /* Test if we are at the beginning of a word */ + + if (srch_type == pos_type) + { + /* Not at beginning of word. Find beginning of word. */ + + while (pos > 0) + { + pos_type = vi_chartype(vi->text[pos-1]); + + if (pos_type != srch_type && pos_type != VI_CHAR_CRLF) + { + break; + } + + pos--; + } + + if ((srch_type != VI_CHAR_SPACE && srch_type != VI_CHAR_CRLF) || + pos == 0) + { + return pos; + } + + /* We found the start of a string of spaces. Decrement to first + * non-space character. + */ + + pos_type = vi_chartype(vi->text[--pos]); + } + + /* If the previous char is space, then skip them */ + + while ((pos_type == VI_CHAR_SPACE || pos_type == VI_CHAR_CRLF) && pos > 0) + { + pos_type = vi_chartype(vi->text[--pos]); + } + + if (pos == 0) + { + return pos; + } + + /* Now find beginning of this new type */ + + srch_type = vi_chartype(vi->text[pos]); + while (pos > 0 && vi_chartype(vi->text[pos-1]) == srch_type) + { + pos--; + } + + /* Return the position of the next word */ + + return pos; +} + +/**************************************************************************** + * Name: vi_gotoprevword + * + * Description: + * Position the cursor at the start of the previous word. + * + ****************************************************************************/ + +static void vi_gotoprevword(FAR struct vi_s *vi) +{ + int count; + + /* Loop for the specified search count */ + + count = vi->value > 0 ? vi->value : 1; + while (count > 0) + { + /* Get position of next word */ + + vi->curpos = vi_findprevword(vi); + count--; + } +} + +/**************************************************************************** + * Name: vi_bottom_line_debug + * + * Description: + * Print text and paste buffers on bottom line for debug purposes + * + ****************************************************************************/ + +#ifdef ENABLE_BOTTOM_LINE_DEBUG +static void vi_bottom_line_debug(FAR struct vi_s *vi) +{ + off_t pos; + int column; + + vi_clearbottomline(vi); + + vi_putch(vi, '"'); + pos = 0; + column = 0; + + while (pos < vi->textsize && column < vi->display.column) + { + if (vi->text[pos] == '\n') + { + vi_putch(vi, '\\'); + vi_putch(vi, 'n'); + } + else if (vi->text[pos] == '\t') + { + vi_putch(vi, '\\'); + vi_putch(vi, 'n'); + } + else + { + vi_putch(vi, vi->text[pos]); + } + + pos++; + column++; + } + + vi_putch(vi, '"'); + if (!vi->yank) + { + return; + } + + vi_putch(vi, ' '); + vi_putch(vi, '"'); + pos = 0; + + while (pos < vi->yanksize && column < vi->display.column) + { + if (vi->yank[pos] == '\n') + { + vi_putch(vi, '\\'); + vi_putch(vi, 'n'); + } + else if (vi->yank[pos] == '\t') + { + vi_putch(vi, '\\'); + vi_putch(vi, 'n'); + } + else + { + vi_putch(vi, vi->yank[pos]); + } + + pos++; + } + + vi_putch(vi, '"'); +} +#endif /* ENABLE_BOTTOM_LINE_DEBUG */ + +/**************************************************************************** + * Name: vi_findnext + * + * Description: + * Perform find operation again in forward direction + * + ****************************************************************************/ + +void vi_findnext(FAR struct vi_s *vi) +{ + if (vi->curpos < vi->textsize) + { + vi->curpos++; + } + + /* Search for string */ + + if (!vi_findstring(vi)) + { + /* Restore original pos if not found */ + + vi->curpos--; + VI_BEL(vi); + } +} + +/**************************************************************************** + * Name: vi_findprev + * + * Description: + * Perform find operation again in reverse direction + * + ****************************************************************************/ + +void vi_findprev(FAR struct vi_s *vi) +{ + off_t pos = vi->curpos; + + /* Move the cursor to the left */ + + if (vi->curpos > 0) + { + vi->curpos--; + } + else + { + vi->curpos = vi->textsize - strlen(vi->findstr); + } + + /* Perform the search */ + + if (!vi_revfindstring(vi)) + { + /* Restore the position if not found */ + + vi->curpos = pos; + } +} + +/**************************************************************************** + * Name: vi_saverepeat + * + * Description: + * Save ch as the first command repeat entry. + * + ****************************************************************************/ + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT +static void vi_saverepeat(FAR struct vi_s *vi, uint16_t ch) +{ + /* If we are in command repeat mode, then don't initialize */ + + if (vi->cmdrepeat) + { + return; + } + + vi->cmdcount = 0; + vi->cmdindex = 0; + + if (ch < 256) + { + vi->cmdbuf[vi->cmdcount++] = ch; + vi->repeatvalue = vi->value; + } +} +#endif /* CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT */ + +/**************************************************************************** + * Name: vi_appendrepeat + * + * Description: + * Save ch as the next command repeat entry. + * + ****************************************************************************/ + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT +static void vi_appendrepeat(FAR struct vi_s *vi, uint16_t ch) +{ + /* If we are in command repeat mode, then don't append */ + + if (vi->cmdrepeat || vi->cmdcount == 0 || ch > 255) + { + return; + } + + /* Don't overflow the command repeat buffer */ + + if (vi->cmdcount < CMD_BUFSIZE) + { + vi->cmdbuf[vi->cmdcount++] = ch; + } +} +#endif /* CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT */ + /**************************************************************************** * Name: vi_cmd_mode * @@ -2172,29 +3439,35 @@ static void vi_cmd_mode(FAR struct vi_s *vi) /* Make sure that the display reflects the current state */ vi_showtext(vi); + vi_showlinecol(vi); vi_setcursor(vi, vi->cursor.row, vi->cursor.column); /* Get the next character from the input */ - ch = vi_getch(vi); +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Test for end of command repeat */ - /* Anything other than 'd' disarms line deletion */ - - if (ch != 'd') + if (vi->cmdrepeat && vi->cmdindex == vi->cmdcount) { - vi->delarm = false; + + /* Terminate the command repeat */ + + vi->cmdrepeat = false; } - /* Anything other than 'y' disarms line yanking */ + /* Test for active cmdrepeat */ - if (ch != 'y') + if (vi->cmdrepeat) { - vi->yankarm = false; + /* Read next command from command buffer */ + + ch = vi->cmdbuf[vi->cmdindex++]; + } + else +#endif + { + ch = vi_getch(vi); } - - /* Any key press clears the error message */ - - vi->error = false; /* Handle numeric input. Zero (0) with no preceding value is a * special case: It means to go to the beginning o the line. @@ -2216,6 +3489,60 @@ static void vi_cmd_mode(FAR struct vi_s *vi) continue; } + /* Allow the following during yank / delete modes */ + + if (ch != 'f' && ch != 't' && ch != 'w') + { + /* Anything other than 'd' disarms line deletion */ + + if (ch != 'd') + { + vi->delarm = false; + } + + /* Anything other than 'y' disarms line yanking */ + + if (ch != 'y') + { + vi->yankarm = false; + } + } + + /* Anything other than'g' disarms goto top */ + + if (ch != 'g') + { + vi->toparm = false; + } + + /* Anything other than'Z' disarms :wq */ + + if (ch != 'Z') + { + vi->wqarm = false; + } + + /* Test for empty file */ + + if (vi->textsize == 0) + { + /* We need some text before we can do anything. Only accept + * text insertion commands. + */ + + if (ch != KEY_CMDMODE_APPEND && ch != KEY_CMDMODE_INSERT && + ch != KEY_CMDMODE_OPENBELOW && ch != KEY_CMDMODE_APPENDEND && + ch != KEY_CMDMODE_INSBEGIN && ch != KEY_CMDMODE_OPENABOVE && + ch != KEY_CMDMODE_COLONMODE) + { + continue; + } + } + + /* Any key press clears the error message */ + + vi->error = false; + /* Then handle the non-numeric character. Normally the accumulated * value will be reset after processing the command. There are a few * exceptions; 'preserve' will be set to 'true' in those exceptional @@ -2223,78 +3550,177 @@ static void vi_cmd_mode(FAR struct vi_s *vi) */ preserve = false; + vi->updatereqcol = true; switch (ch) { case KEY_CMDMODE_UP: /* Move the cursor up one line */ + case KEY_UP: /* Move the cursor up one line */ { + vi->updatereqcol = false; vi_cusorup(vi, vi->value); } break; case KEY_CMDMODE_DOWN: /* Move the cursor down one line */ + case KEY_DOWN: /* Move the cursor down one line */ { + vi->updatereqcol = false; vi_cursordown(vi, vi->value); } break; case KEY_CMDMODE_LEFT: /* Move the cursor left N characters */ + case KEY_LEFT: /* Move the cursor left N characters */ { vi->curpos = vi_cursorleft(vi, vi->curpos, vi->value); } break; case KEY_CMDMODE_RIGHT: /* Move the cursor right one character */ + case KEY_RIGHT: /* Move the cursor right one character */ { - vi->curpos = vi_cursorright(vi, vi->curpos, vi->value); + if (vi->text[vi->curpos] != '\n' && + vi->text[vi->curpos+1] != '\n') + { + vi->curpos = vi_cursorright(vi, vi->curpos, vi->value); + if (vi->curpos >= vi->textsize) + { + vi->curpos = vi->textsize - 1; + } + } } break; case KEY_CMDMODE_BEGINLINE: /* Move cursor to start of current line */ + case KEY_HOME: { vi->curpos = vi_linebegin(vi, vi->curpos); } break; case KEY_CMDMODE_ENDLINE: /* Move cursor to end of current line */ + case KEY_END: { vi->curpos = vi_lineend(vi, vi->curpos); + vi->reqcolumn = 65535; + vi->updatereqcol = false; } break; case KEY_CMDMODE_PAGEUP: /* Move up (backward) one screen */ + case KEY_PPAGE: { + vi->updatereqcol = false; vi_cusorup(vi, vi->display.row); } break; case KEY_CMDMODE_PAGEDOWN: /* Move down (forward) one screen */ + case KEY_NPAGE: { + vi->updatereqcol = false; vi_cursordown(vi, vi->display.row); } break; case KEY_CMDMODE_HALFUP: /* Move up (backward) one screen */ { + vi->updatereqcol = false; vi_cusorup(vi, vi->display.row >> 1); } break; case KEY_CMDMODE_HALFDOWN: /* Move down (forward) one half screen */ { + vi->updatereqcol = false; vi_cursordown(vi, vi->display.row >> 1); } break; - case KEY_CMDMODE_DEL: /* Delete N characters at the cursor */ - case ASCII_DEL: + case KEY_CMDMODE_TOP: /* Move to top of screen */ { - vi_delforward(vi); + vi->curpos = vi->winpos; + } + break; + + case KEY_CMDMODE_BOTTOM: /* Move to bottom of screen */ + { + (void)vi_gotoscreenbottom(vi, 0); + } + break; + + case KEY_CMDMODE_MIDDLE: /* Move to middle of screen */ + { + /* Find bottom row number, then move to half that */ + + off_t pos = vi_gotoscreenbottom(vi, 0); + vi_gotoscreenbottom(vi, pos); + } + break; + + case KEY_CMDMODE_FIRSTCHAR: + { + vi_gotofirstnonwhite(vi); + } + break; + + case KEY_CMDMODE_GOTOTOP: /* Go to top of document */ + { + if (vi->toparm) + { + vi->curpos = 0; + vi->redrawline = true; + vi->toparm = false; + } + else + { + vi->toparm = true; + } + } + break; + + case KEY_CMDMODE_FINDNEXT: + { + if (vi->revfind) + { + vi_findprev(vi); + } + else + { + vi_findnext(vi); + } + break; + } + + case KEY_CMDMODE_FINDPREV: + { + if (vi->revfind) + { + vi_findnext(vi); + } + else + { + vi_findprev(vi); + } + break; } break; case ASCII_BS: /* Delete N characters before the cursor */ { - vi_delbackward(vi); + /* Move the cursor to the left */ + + if (vi->curpos > 0) + { + vi->curpos--; + + /* If we moved to \n on the previous line, skip it */ + + if (vi->curpos > 0 && vi->text[vi->curpos] == '\n') + { + vi->curpos--; + } + } } break; @@ -2302,6 +3728,10 @@ static void vi_cmd_mode(FAR struct vi_s *vi) { if (vi->delarm) { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); + vi_appendrepeat(vi, ch); +#endif vi_delline(vi); vi->delarm = false; } @@ -2313,11 +3743,29 @@ static void vi_cmd_mode(FAR struct vi_s *vi) } break; + case KEY_CMDMODE_DELTOEOL: /* Delete to end of current line */ + { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_deltoeol(vi); + } + break; + + case KEY_CMDMODE_DELBACKWARD: /* Delete from cursor forward */ + { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_delbackward(vi); + } + break; + case KEY_CMDMODE_YANK: /* Yank the current line(s) into the buffer */ { if (vi->yankarm) { - vi_yank(vi); + vi_yank(vi, false); vi->yankarm = false; } else @@ -2330,12 +3778,27 @@ static void vi_cmd_mode(FAR struct vi_s *vi) case KEY_CMDMODE_PASTE: /* Paste line(s) from into text after current line */ { - vi_paste(vi); +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_paste(vi, false); + } + break; + + case KEY_CMDMODE_PASTEBEFORE: /* Paste text before cursor position */ + { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_paste(vi, true); } break; case KEY_CMDMODE_REPLACECH: /* Replace character(s) under cursor */ { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif vi_setmode(vi, SUBMODE_REPLACECH, vi->value); preserve = true; } @@ -2343,15 +3806,38 @@ static void vi_cmd_mode(FAR struct vi_s *vi) case KEY_CMDMODE_REPLACE: /* Replace character(s) under cursor until ESC */ { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif vi_setmode(vi, MODE_REPLACE, 0); } break; /* Not implemented */ + case KEY_CMDMODE_FINDINLINE: /* Find character(s) in current line */ + case KEY_CMDMODE_TFINDINLINE: /* Find character(s) in current line */ + { + vi->tfind = ch == KEY_CMDMODE_TFINDINLINE; + vi->mode = MODE_FINDINLINE; + preserve = true; + } + break; + case KEY_CMDMODE_OPENBELOW: /* Enter insertion mode in new line below current */ { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_setmode(vi, MODE_INSERT, 0); + /* Go forward to the end of the current line */ vi->curpos = vi_lineend(vi, vi->curpos); + if (vi->curpos != vi->textsize) + { + /* Include the '\n' */ + + vi->curpos++; + } /* Insert a newline to break the line. The cursor now points * beginning of the new line. @@ -2361,73 +3847,269 @@ static void vi_cmd_mode(FAR struct vi_s *vi) /* Then enter insert mode */ - vi_setmode(vi, MODE_INSERT, 0); + vi->drawtoeos = true; } break; case KEY_CMDMODE_OPENABOVE: /* Enter insertion mode in new line above current */ { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif /* Back up to the beginning of the end of the previous line */ - off_t pos = vi_prevline(vi, vi->curpos); - vi->curpos = vi_lineend(vi, pos); + off_t pos = vi_linebegin(vi, vi->curpos); + if (pos == 0) + { + /* Insert newline at beginning of file, then move to previous + * line. + */ - /* Insert a newline to open the line. The cursor will now point to the - * beginning of newly openly line before the current line. - */ + vi->curpos = 0; + vi_insertch(vi, '\n'); + vi->curpos = vi_prevline(vi, vi->curpos); + } + else + { + /* Insert a newline to open the line. The cursor will now + * point to thebeginning of newly openly line before the + * current line. + */ - vi_insertch(vi, '\n'); + pos = vi_prevline(vi, pos); + vi->curpos = vi_lineend(vi, pos)+1; + vi_insertch(vi, '\n'); + } /* Then enter insert mode */ vi_setmode(vi, MODE_INSERT, 0); + vi->drawtoeos = true; } break; - case KEY_CMDMODE_APPEND: /* Enter insertion mode after the current cursor position */ + case KEY_CMDMODE_CHANGETOEOL: /* Delete to end of current line */ { - vi->curpos = vi_cursorright(vi, vi->curpos, 1); + /* First delete to end of line */ + + vi_deltoeol(vi); + } + + /* Now enter insert mode by falling through the case */ + + case KEY_CMDMODE_APPEND: /* Enter insertion mode after the current + * cursor position */ + { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif vi_setmode(vi, MODE_INSERT, 0); + + if (vi->curpos == vi->textsize) + { + vi->curpos = vi_cursorright(vi, vi->curpos, 1) + 1; + } + else + { + vi->curpos = vi_cursorright(vi, vi->curpos, 1); + } } break; case KEY_CMDMODE_APPENDEND: /* Enter insertion mode at the end of the current line */ { - vi->curpos = vi_lineend(vi, vi->curpos); +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif vi_setmode(vi, MODE_INSERT, 0); + vi->curpos = vi_lineend(vi, vi->curpos) + 1; } break; + case KEY_CMDMODE_SUBSTITUTE: + case KEY_CMDMODE_DEL: /* Delete N characters at the cursor */ + case KEY_DC: + case ASCII_DEL: + { + off_t pos = vi->curpos; + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + /* If we are at the end of the line, then delete backward */ + + if (vi->text[pos] == '\n') + { + /* Nothing to do */ + + break; + } + else if (pos+1 != vi->textsize && vi->text[pos+1] == '\n') + { + if (pos > 0) + { + vi_delforward(vi); + vi->curpos = vi_cursorleft(vi, vi->curpos, 1); + } + } + else + { + vi_delforward(vi); + vi->redrawline = true; + } + } + + /* For 's'ubstitute key, we go into insert mode */ + + if (ch == KEY_CMDMODE_SUBSTITUTE) + { + vi_setmode(vi, MODE_INSERT, 0); + } + + break; + case KEY_CMDMODE_INSBEGIN: /* Enter insertion mode at the beginning of the current line */ { vi->curpos = vi_linebegin(vi, vi->curpos); } + /* Fall through */ case KEY_CMDMODE_INSERT: /* Enter insertion mode before the current cursor position */ { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif vi_setmode(vi, MODE_INSERT, 0); } break; + case KEY_CMDMODE_JOIN: /* Join line below with current line */ + { +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + vi_saverepeat(vi, ch); +#endif + vi_join(vi); + } + break; + case KEY_CMDMODE_COLONMODE: /* Enter : command sub-mode */ { + vi->updatereqcol = false; vi_setsubmode(vi, SUBMODE_COLON, ':', 0); } break; + case KEY_CMDMODE_SAVEQUIT: /* Two of these is the same as :wq */ + { + if (vi->wqarm) + { + /* Emulate :wq */ + + strcpy(vi->scratch, "wq"); + vi->cmdlen = 2; + vi_parsecolon(vi); + + /* If save quit succeds, we won't return */ + } + else + { + vi->wqarm = true; + } + } + break; + case KEY_CMDMODE_FINDMODE: /* Enter / find sub-mode */ { + vi->updatereqcol = false; vi_setsubmode(vi, SUBMODE_FIND, '/', 0); } break; + case KEY_CMDMODE_REVFINDMODE: /* Enter / find sub-mode */ + { + vi->updatereqcol = false; + vi_setsubmode(vi, SUBMODE_REVFIND, '?', 0); + } + break; + + case KEY_CMDMODE_REPEAT: /* Repeat the last command */ + { + if (vi->cmdcount < CMD_BUFSIZE) + { + vi->cmdindex = 0; + vi->cmdrepeat = true; + vi->value = vi->value > 0 ? vi->value : vi->repeatvalue > 0 ? + vi->repeatvalue : 1; + preserve = true; + } + else + { + VI_BEL(vi); + } + } + break; + case KEY_CMDMODE_GOTO: /* Go to line specified by the accumulated value */ { vi_gotoline(vi); } break; + case KEY_CMDMODE_WORDFWD: /* Go to line specified by the accumulated value */ + { + vi_gotonextword(vi); + } + break; + + case KEY_CMDMODE_WORDBACK: /* Go to line specified by the accumulated value */ + { + vi_gotoprevword(vi); + } + break; + +#if defined(CONFIG_EOL_IS_CR) + case KEY_CMDMODE_NEXTLINE: + case '\r': /* CR terminates line */ + { + vi->curpos = vi_nextline(vi, vi->curpos); + vi_gotofirstnonwhite(vi); + } + break; + +#elif defined(CONFIG_EOL_IS_BOTH_CRLF) + case '\r': /* Wait for the LF */ + break; +#endif + +#if defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) + case KEY_CMDMODE_NEXTLINE: + case '\n': /* LF terminates line */ + { + vi->curpos = vi_nextline(vi, vi->curpos); + vi_gotofirstnonwhite(vi); + } + break; +#endif + +#ifdef CONFIG_EOL_IS_EITHER_CRLF + case KEY_CMDMODE_NEXTLINE: + case '\r': /* Either CR or LF terminates line */ + case '\n': + { + vi->curpos = vi_nextline(vi, vi->curpos); + vi_gotofirstnonwhite(vi); + } + break; +#endif + + case KEY_CMDMODE_PREVLINE: + { + vi->curpos = vi_prevline(vi, vi->curpos); + vi_gotofirstnonwhite(vi); + } + break; + /* Unimplemented and invalid commands */ case KEY_CMDMODE_REDRAW: /* Redraws the screen */ @@ -2497,6 +4179,10 @@ static void vi_cmdch(FAR struct vi_s *vi, char ch) /* And add the new character to the display */ vi_putch(vi, ch); + if (ch == '\n') + { + vi->drawtoeos = true; + } } /**************************************************************************** @@ -2553,6 +4239,15 @@ static void vi_parsecolon(FAR struct vi_s *vi) vi->scratch[vi->cmdlen] = '\0'; + /* Convert "wq" into "qw" */ + + if (vi->cmdlen > 1 && vi->scratch[0] == KEY_COLMODE_WRITE && + vi->scratch[1] == KEY_COLMODE_QUIT) + { + vi->scratch[0] = KEY_COLMODE_QUIT; + vi->scratch[1] = KEY_COLMODE_WRITE; + } + /* Then parse the contents of the scratch buffer */ for (col = 0; col < vi->cmdlen && !done; col++) @@ -2747,13 +4442,13 @@ static void vi_parsecolon(FAR struct vi_s *vi) * as unmodified. */ - strncpy(vi->filename, filename, MAX_STRING - 1); + strncpy(vi->filename, filename, MAX_FILENAME - 1); /* Make sure that the (possibly truncated) file name is NUL * terminated */ - vi->filename[MAX_STRING - 1] = '\0'; + vi->filename[MAX_FILENAME - 1] = '\0'; vi->modified = false; } else @@ -2777,13 +4472,13 @@ static void vi_parsecolon(FAR struct vi_s *vi) if (filename) { - strncpy(vi->filename, filename, MAX_STRING - 1); + strncpy(vi->filename, filename, MAX_FILENAME - 1); /* Make sure that the (possibly truncated) file name is NUL * terminated */ - vi->filename[MAX_STRING - 1] = '\0'; + vi->filename[MAX_FILENAME - 1] = '\0'; } /* If it is not a new file and if there are no changes to the text @@ -2792,6 +4487,12 @@ static void vi_parsecolon(FAR struct vi_s *vi) if (filename || vi->modified) { + vi_clearbottomline(vi); + vi_putch(vi, '"'); + vi_write(vi, vi->filename, strlen(vi->filename)); + vi_putch(vi, '"'); + vi_putch(vi, ' '); + /* Now, finally, we can save the file */ if (!vi_savetext(vi, vi->filename, 0, vi->textsize)) @@ -2807,6 +4508,11 @@ static void vi_parsecolon(FAR struct vi_s *vi) /* The text buffer contents are no longer modified */ + if (!IS_QUIT(cmd)) + { + vi_setcursor(vi, vi->cursor.row, vi->cursor.column); + } + vi->modified = false; } } @@ -2817,6 +4523,7 @@ static void vi_parsecolon(FAR struct vi_s *vi) { /* Yes... free resources and exit */ + vi_putch(vi, '\n'); vi_release(vi); exit(EXIT_SUCCESS); } @@ -2869,7 +4576,18 @@ static void vi_cmd_submode(FAR struct vi_s *vi) case ASCII_BS: /* Delete the character(s) before the cursor */ { - vi_cmdbackspace(vi); + if (vi->cmdlen == 0) + { + vi_exitsubmode(vi, MODE_COMMAND); + + /* Ensure bottom line is clared */ + + vi_clearbottomline(vi); + } + else + { + vi_cmdbackspace(vi); + } } break; @@ -2962,6 +4680,7 @@ static bool vi_findstring(FAR struct vi_s *vi) * matching sub-string. Stop loo */ + vi_clearbottomline(vi); for (pos = vi->curpos; pos + len <= vi->textsize; pos++) @@ -2980,9 +4699,96 @@ static bool vi_findstring(FAR struct vi_s *vi) } /* If we get here, then the search string was not found anywhere after the - * current cursor position. + * current cursor position. Start from beginning and search to curpos. */ + for (pos = 0; pos <= vi->curpos; pos++) + { + /* Check for the matching sub-string */ + + if (strncmp(vi->text + pos, vi->scratch, len) == 0) + { + vi_write(vi, g_fmtsrcbot, sizeof(g_fmtsrcbot)); + + /* Found it... save the cursor position and + * return success. + */ + + vi->curpos = pos; + return true; + } + } + + return false; +} + +/**************************************************************************** + * Name: vi_revfindstring + * + * Description: + * Find the string in the findstr buffer by searching for a matching + * sub-string in the text buffer, starting at the current cursor position. + * The search is performed backward through the file. + * + ****************************************************************************/ + +static bool vi_revfindstring(FAR struct vi_s *vi) +{ + off_t pos; + int len; + + viinfo("findstr: \"%s\"\n", vi->findstr); + + /* The search string is in the find buffer */ + + len = strlen(vi->findstr); + if (!len) + { + return false; + } + + /* Search from the current cursor position forward for a + * matching sub-string. Stop loo + */ + + vi_clearbottomline(vi); + for (pos = vi->curpos; + pos > 0; pos--) + { + /* Check for the matching sub-string */ + + if (strncmp(vi->text + pos, vi->scratch, len) == 0) + { + /* Found it... save the cursor position and + * return success. + */ + + vi->curpos = pos; + return true; + } + } + + /* If we get here, then the search string was not found anywhere before the + * current cursor position. Start from end and search to curpos. + */ + + for (pos = vi->textsize - len; pos > vi->curpos; pos--) + { + /* Check for the matching sub-string */ + + if (strncmp(vi->text + pos, vi->scratch, len) == 0) + { + vi_write(vi, g_fmtsrctop, sizeof(g_fmtsrctop)); + + /* Found it... save the cursor position and + * return success. + */ + + vi->curpos = pos; + return true; + } + } + return false; } @@ -2994,7 +4800,7 @@ static bool vi_findstring(FAR struct vi_s *vi) * ****************************************************************************/ -static void vi_parsefind(FAR struct vi_s *vi) +static void vi_parsefind(FAR struct vi_s *vi, bool revfind) { /* Make certain that the scratch buffer contents are NUL terminated */ @@ -3012,16 +4818,24 @@ static void vi_parsefind(FAR struct vi_s *vi) strncpy(vi->findstr, vi->scratch, MAX_STRING - 1); - /* Make sure that the (possibly truncated) search string is NUL - * terminated - */ + /* Make sure that the (possibly truncated) search string is NUL + * terminated + */ vi->findstr[MAX_STRING - 1] = '\0'; } /* Then attempt to find the string */ - (void)vi_findstring(vi); + vi->revfind = revfind; + if (revfind) + { + (void)vi_revfindstring(vi); + } + else + { + (void)vi_findstring(vi); + } /* Exit the sub-mode and revert to command mode */ @@ -3036,7 +4850,7 @@ static void vi_parsefind(FAR struct vi_s *vi) * ****************************************************************************/ -static void vi_find_submode(FAR struct vi_s *vi) +static void vi_find_submode(FAR struct vi_s *vi, bool revfind) { int ch; @@ -3044,7 +4858,7 @@ static void vi_find_submode(FAR struct vi_s *vi) /* Loop while we are in find mode */ - while (vi->mode == SUBMODE_FIND) + while (vi->mode == SUBMODE_FIND || vi->mode == SUBMODE_REVFIND) { /* Get the next character from the input */ @@ -3064,7 +4878,18 @@ static void vi_find_submode(FAR struct vi_s *vi) case ASCII_BS: /* Delete the character before the cursor */ { - vi_cmdbackspace(vi); + if (vi->cmdlen == 0) + { + vi_exitsubmode(vi, MODE_COMMAND); + + /* Ensure bottom line is clared */ + + vi_clearbottomline(vi); + } + else + { + vi_cmdbackspace(vi); + } } break; @@ -3079,7 +4904,7 @@ static void vi_find_submode(FAR struct vi_s *vi) #if defined(CONFIG_EOL_IS_CR) case '\r': /* CR terminates line */ { - vi_parsefind(vi); + vi_parsefind(vi, revfind); } break; @@ -3091,7 +4916,7 @@ static void vi_find_submode(FAR struct vi_s *vi) #if defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) case '\n': /* LF terminates line */ { - vi_parsefind(vi); + vi_parsefind(vi, revfind); } break; #endif @@ -3100,7 +4925,7 @@ static void vi_find_submode(FAR struct vi_s *vi) case '\r': /* Either CR or LF terminates line */ case '\n': { - vi_parsefind(vi); + vi_parsefind(vi, revfind); } break; #endif @@ -3149,12 +4974,14 @@ static void vi_replacech(FAR struct vi_s *vi, char ch) /* Yes, then insert the new character before the newline */ vi_insertch(vi, ch); + vi->drawtoeos = true; } else { /* No, just replace the character and increment the cursor position */ vi->text[vi->curpos++] = ch; + vi->redrawline = true; } } @@ -3181,7 +5008,7 @@ static void vi_replacech_submode(FAR struct vi_s *vi) /* Are there that many characters left on the line to be replaced? */ - end = vi_lineend(vi, vi->curpos); + end = vi_lineend(vi, vi->curpos) + 1; if (vi->curpos + nchars > end) { vi_error(vi, g_fmtnotvalid); @@ -3198,6 +5025,7 @@ static void vi_replacech_submode(FAR struct vi_s *vi) /* Handle the newly received character */ + vi->updatereqcol = true; switch (ch) { case KEY_FINDMODE_QUOTE: /* Quoted character follows */ @@ -3212,6 +5040,10 @@ static void vi_replacech_submode(FAR struct vi_s *vi) case ASCII_ESC: /* Escape exits replace mode */ { vi_setmode(vi, MODE_COMMAND, 0); + if (vi->curpos > 0) + { + --vi->curpos; + } } break; @@ -3261,6 +5093,7 @@ static void vi_replacech_submode(FAR struct vi_s *vi) VI_BEL(vi); } } + break; } } @@ -3270,6 +5103,121 @@ static void vi_replacech_submode(FAR struct vi_s *vi) for (; nchars > 0; nchars--) { vi_replacech(vi, ch); + vi->redrawline = true; + } + + /* Revert to command mode */ + + vi_setmode(vi, MODE_COMMAND, 0); +} + +/**************************************************************************** + * Name: vi_findinline_mode + * + * Description: + * Find character in current line. + * + ****************************************************************************/ + +static void vi_findinline_mode(FAR struct vi_s *vi) +{ + int count; + off_t pos; + int ch = -1; + + /* Get the next character from the input */ + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Test for active cmdrepeat */ + + if (vi->cmdrepeat) + { + /* Read next command from command buffer */ + + ch = vi->cmdbuf[vi->cmdindex++]; + } + else +#endif + { + while (ch == -1) + { + ch = vi_getch(vi); + } + } + + /* Ignore all but printable characters and tab */ + + if (!isprint(ch)) + { + VI_BEL(vi); + vi_setmode(vi, MODE_COMMAND, 0); + return; + } + + vi->updatereqcol = true; + + /* Now find the character */ + + pos = vi->curpos + 1; + count = vi->value > 0 ? vi->value : 1; + + while (count > 0 && pos < vi->textsize-1 && vi->text[pos] != '\n') + { + /* Increment to next character */ + + pos++; + + /* Test if this character matches */ + + if (vi->text[pos] == ch) + { + count--; + } + } + + /* Test if found */ + + if (count == 0) + { + if (vi->tfind) + { + pos--; + } + + /* Test if yank or del armed */ + + if (vi->yankarm || vi->delarm) + { + /* Yank the text and possibly delete */ + + vi_yanktext(vi, vi->curpos, pos, 1, vi->delarm); + if (vi->delarm) + { + /* Redraw line if text deleted */ + + vi->redrawline = true; + } + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Setup command repeat */ + + if (vi->delarm) + { + vi_saverepeat(vi, 'd'); + vi_appendrepeat(vi, vi->tfind ? 't' : 'f'); + vi_appendrepeat(vi, ch); + } +#endif + + vi->delarm = false; + vi->yankarm = false; + } + else + { + /* Simply move to the specified location */ + + vi->curpos = pos; + } } /* Revert to command mode */ @@ -3313,26 +5261,110 @@ static void vi_insertch(FAR struct vi_s *vi, char ch) static void vi_insert_mode(FAR struct vi_s *vi) { + off_t start = vi->curpos; int ch; viinfo("Enter insert mode\n"); + /* Print insert message */ + + vi_clearbottomline(vi); + vi_write(vi, g_fmtinsert, sizeof(g_fmtinsert)); + vi_setcursor(vi, vi->cursor.row, vi->cursor.column); + vi->redrawline = true; + /* Loop while we are in insert mode */ - while (vi->mode == MODE_INSERT) + while (vi->mode == MODE_INSERT || vi->mode == MODE_REPLACE) { /* Make sure that the display reflects the current state */ - vi_showtext(vi); + if (vi->redrawline || vi->drawtoeos || vi->fullredraw) + { + vi_showtext(vi); + } + + /* Display the line and col number */ + + vi_showlinecol(vi); vi_setcursor(vi, vi->cursor.row, vi->cursor.column); /* Get the next character from the input */ - ch = vi_getch(vi); +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Test for active cmdrepeat */ + + if (vi->cmdrepeat) + { + /* Read next command from command buffer */ + + ch = vi->cmdbuf[vi->cmdindex++]; + } + else +#endif + { + ch = vi_getch(vi); + +#ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT + /* Any arrow, pgup, pgdn, etc. key resets command repeat */ + + if (ch == KEY_UP || ch == KEY_DOWN || ch == KEY_LEFT || + ch == KEY_RIGHT || ch == KEY_HOME || ch == KEY_END || + ch == KEY_PPAGE || ch == KEY_NPAGE) + { + vi->cmdcount = 1; + vi->redrawline = true; + } + else + { + vi_appendrepeat(vi, ch); + } +#endif + } + + /* Test for printable character first since we will get mostly those, + * and this will give better performance. + */ + + vi->updatereqcol = true; + if (!iscntrl(ch) || ch == '\t') + { + /* Insert the filtered character into the buffer */ + + if (vi->mode == MODE_INSERT) + { + vi_insertch(vi, ch); + } + else + { + vi_replacech(vi, ch); + } + + /* If we don't have to scroll the screen to display the + * character, then do a simple putch, otherwise request + * a line redraw. + */ + + if (vi->cursor.column + 1 < vi->display.column && ch != '\t' && + (vi->curpos+1 == vi->textsize || + vi->text[vi->curpos+1] == '\n')) + { + vi_putch(vi, ch); + } + else + { + vi->redrawline = true; + } + + vi->cursor.column++; + + continue; + } /* Any key press clears the error message */ vi->error = false; + vi->redrawline = true; /* Handle the newly received character */ @@ -3350,6 +5382,11 @@ static void vi_insert_mode(FAR struct vi_s *vi) { if (vi->curpos < vi->textsize) { + if (vi->text[vi->curpos] == '\n') + { + vi->drawtoeos = true; + } + vi_shrinktext(vi, vi->curpos, 1); } } @@ -3357,9 +5394,30 @@ static void vi_insert_mode(FAR struct vi_s *vi) case ASCII_BS: { - if (vi->curpos > 0) + /* Backspace changes based on mode */ + + if (vi->mode == MODE_INSERT) { - vi_shrinktext(vi, --vi->curpos, 1); + /* In insert mode, we remove characters */ + + if (vi->curpos > 0) + { + if (vi->text[vi->curpos-1] == '\n') + { + vi->drawtoeos = true; + } + + vi_shrinktext(vi, vi->curpos-1, 1); + } + } + else + { + /* In replace mode, we simply move the cursor left */ + + if (vi->curpos > start) + { + vi->curpos = vi_cursorleft(vi, vi->curpos, 1); + } } } break; @@ -3367,6 +5425,14 @@ static void vi_insert_mode(FAR struct vi_s *vi) case ASCII_ESC: /* Escape exits insert mode */ { vi_setmode(vi, MODE_COMMAND, 0); + vi->updatereqcol = true; + + /* Move cursor 1 space to the left when exiting insert mode */ + + if (vi->curpos > 0 && vi->text[vi->curpos-1] != '\n') + { + --vi->curpos; + } } break; @@ -3375,7 +5441,16 @@ static void vi_insert_mode(FAR struct vi_s *vi) #if defined(CONFIG_EOL_IS_CR) case '\r': /* CR terminates line */ { - vi_insertch(vi, '\n'); + if (vi->mode == MODE_INSERT) + { + vi_insertch(vi, '\n'); + } + else + { + vi_replacech(vi, '\n'); + } + + vi->drawtoeos = true; } break; @@ -3387,7 +5462,16 @@ static void vi_insert_mode(FAR struct vi_s *vi) #if defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) case '\n': /* LF terminates line */ { - vi_insertch(vi, '\n'); + if (vi->mode == MODE_INSERT) + { + vi_insertch(vi, '\n'); + } + else + { + vi_replacech(vi, '\n'); + } + + vi->drawtoeos = true; } break; #endif @@ -3396,20 +5480,85 @@ static void vi_insert_mode(FAR struct vi_s *vi) case '\r': /* Either CR or LF terminates line */ case '\n': { - vi_insertch(vi, '\n'); + if (vi->mode == MODE_INSERT) + { + vi_insertch(vi, '\n'); + } + else + { + vi_replacech(vi, '\n'); + } + + vi_putch(vi, ' '); + vi_clrtoeol(vi); + vi->drawtoeos = true; } break; #endif + case KEY_UP: /* Move the cursor up one line */ + { + vi->updatereqcol = false; + vi_cusorup(vi, 1); + } + break; + + case KEY_DOWN: /* Move the cursor down one line */ + { + vi->updatereqcol = false; + vi_cursordown(vi, 1); + } + break; + + case KEY_LEFT: /* Move the cursor left N characters */ + { + vi->curpos = vi_cursorleft(vi, vi->curpos, 1); + } + break; + + case KEY_RIGHT: /* Move the cursor right one character */ + { + vi->curpos = vi_cursorright(vi, vi->curpos, 1); + if (vi->curpos >= vi->textsize) + { + vi->curpos = vi->textsize - 1; + } + } + break; + + case KEY_HOME: + { + vi->curpos = vi_linebegin(vi, vi->curpos); + } + break; + + case KEY_END: + { + vi->curpos = vi_lineend(vi, vi->curpos); + } + break; + + case KEY_PPAGE: + { + vi->updatereqcol = false; + vi_cusorup(vi, vi->display.row); + } + break; + + case KEY_NPAGE: + { + vi->updatereqcol = false; + vi_cursordown(vi, vi->display.row); + } + break; + default: { - /* Ignore all control characters except for tab and newline */ + /* Don't print BEL if char is -1 from termcurses */ - if (!iscntrl(ch) || ch == '\t') + if (ch == -1) { - /* Insert the filtered character into the buffer */ - - vi_insertch(vi, ch); + continue; } else { @@ -3419,116 +5568,8 @@ static void vi_insert_mode(FAR struct vi_s *vi) break; } } -} -/**************************************************************************** - * Name: vi_replace_mode - * - * Description: - * Replace mode loop - * - ****************************************************************************/ - -static void vi_replace_mode(FAR struct vi_s *vi) -{ - off_t start = vi->curpos; - int ch; - - viinfo("Enter replace mode\n"); - - /* Loop until ESC is pressed */ - - while (vi->mode == MODE_REPLACE) - { - /* Make sure that the display reflects the current state */ - - vi_showtext(vi); - vi_setcursor(vi, vi->cursor.row, vi->cursor.column); - - /* Get the next character from the input */ - - ch = vi_getch(vi); - - /* Any key press clears the error message */ - - vi->error = false; - - /* Handle the newly received character */ - - switch (ch) - { - case KEY_FINDMODE_QUOTE: /* Quoted character follows */ - { - /* Replace the next character unconditionally */ - - vi_replacech(vi, ch); - } - break; - - case ASCII_BS: /* Delete the character before the cursor */ - { - if (vi->curpos > start) - { - vi->curpos = vi_cursorleft(vi, vi->curpos, 1); - } - } - break; - - case ASCII_ESC: /* Escape exits find mode */ - { - vi_setmode(vi, MODE_COMMAND, 0); - } - break; - - /* What do we do with carriage returns? line feeds? */ - -#if defined(CONFIG_EOL_IS_CR) - case '\r': /* CR terminates line */ - { - vi_replacech(vi, '\n'); - } - break; - -#elif defined(CONFIG_EOL_IS_BOTH_CRLF) - case '\r': /* Wait for the LF */ - break; -#endif - -#if defined(CONFIG_EOL_IS_LF) || defined(CONFIG_EOL_IS_BOTH_CRLF) - case '\n': /* LF terminates line */ - { - vi_replacech(vi, '\n'); - } - break; -#endif - -#ifdef CONFIG_EOL_IS_EITHER_CRLF - case '\r': /* Either CR or LF terminates line */ - case '\n': - { - vi_replacech(vi, '\n'); - } - break; -#endif - - default: - { - /* Ignore all but printable characters and TABs */ - - if (isprint(ch) || ch == '\t') - { - /* Insert the filtered character into the text buffer */ - - vi_replacech(vi, ch); - } - else - { - VI_BEL(vi); - } - } - break; - } - } + vi_clearbottomline(vi); } /**************************************************************************** @@ -3557,6 +5598,11 @@ static void vi_release(FAR struct vi_s *vi) free(vi->yank); } + if (vi->tcurs) + { + termcurses_deinitterm(vi->tcurs); + } + free(vi); } } @@ -3614,11 +5660,12 @@ int vi_main(int argc, char **argv) { FAR struct vi_s *vi; int option; + int ret; /* Allocate a vi state structure */ vi = (FAR struct vi_s *)zalloc(sizeof(struct vi_s)); - if (!vi) + if (vi == NULL) { vi_error(vi, g_fmtallocfail); return EXIT_FAILURE; @@ -3690,13 +5737,39 @@ int vi_main(int argc, char **argv) } } + /* Initialze termcurses */ + + ret = termcurses_initterm(NULL, 0, 1, &vi->tcurs); + if (ret == OK) + { + struct winsize winsz; + + ret = termcurses_getwinsize(vi->tcurs, &winsz); + if (ret == OK) + { + vi->display.row = winsz.ws_row; + vi->display.column = winsz.ws_col; + } + } + /* There may be one additional argument on the command line: The filename */ if (optind < argc) { /* Copy the file name into the file name buffer */ - strncpy(vi->filename, argv[optind], MAX_STRING - 1); + if (argv[optind][0] == '/') + { + strncpy(vi->filename, argv[optind], MAX_STRING - 1); + } + else + { + /* Make file relative to current working directory */ + + getcwd(vi->filename, MAX_STRING-1); + strncat(vi->filename, "/", MAX_STRING - 1); + strncat(vi->filename, argv[optind], MAX_STRING - 1); + } /* Make sure that the (possibly truncated) file name is NUL terminated */ @@ -3705,12 +5778,22 @@ int vi_main(int argc, char **argv) /* Load the file into memory */ (void)vi_insertfile(vi, 0, vi->filename); + vi->modified = false; /* Skip over the filename argument. There should nothing after this */ optind++; } + /* If no file loaded, create an empty buffer for editing */ + + if (vi->text == NULL) + { + vi_extendtext(vi, 0, TEXT_GULP_SIZE); + vi->textsize = 0; + vi->modified = 0; + } + if (optind != argc) { fprintf(stderr, "ERROR: Too many arguments\n"); @@ -3719,7 +5802,8 @@ int vi_main(int argc, char **argv) /* The editor loop */ - for (;;) + vi->fullredraw = true; + for (; ; ) { /* We loop, processing each mode change */ @@ -3736,8 +5820,9 @@ int vi_main(int argc, char **argv) vi_cmd_submode(vi); break; + case SUBMODE_REVFIND: case SUBMODE_FIND: /* Find data entry in command mode */ - vi_find_submode(vi); + vi_find_submode(vi, vi->mode == SUBMODE_REVFIND); break; case SUBMODE_REPLACECH: /* Replace characters in command mode */ @@ -3745,12 +5830,14 @@ int vi_main(int argc, char **argv) break; case MODE_INSERT: /* Insert mode */ + case MODE_REPLACE: /* Replace character(s) under cursor until ESC */ vi_insert_mode(vi); break; - case MODE_REPLACE: /* Replace character(s) under cursor until ESC */ - vi_replace_mode(vi); + case MODE_FINDINLINE: /* Insert mode */ + vi_findinline_mode(vi); break; + } }