/**************************************************************************** * apps/system/vi/vi.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_SYSTEM_VI_ROWS # define CONFIG_SYSTEM_VI_ROWS 16 #endif #ifndef CONFIG_SYSTEM_VI_COLS # define CONFIG_SYSTEM_VI_COLS 64 #endif #ifndef CONFIG_SYSTEM_VI_YANK_THRESHOLD #define CONFIG_SYSTEM_VI_YANK_THRESHOLD 128 #endif /* Control characters */ #undef CTRL #define CTRL(a) ((a) & 0x1f) #define VI_BEL(vi) vi_putch(vi,CTRL('G')) /* 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 VI_TABSIZE 8 /* A TAB is eight characters */ #define TABMASK 7 /* Mask for TAB alignment */ #define NEXT_TAB(p) (((p) + VI_TABSIZE) & ~TABMASK) /* Parsed command action bits */ #define CMD_READ (1 << 0) /* Bit 0: Read */ #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=Quit operation */ # define CMD_QUIT (1 << 3) /* 01=Quit if saved */ # define CMD_DISCARD (3 << 3) /* 11=Quit without saving */ #define CMD_NONE (0) /* No command */ #define CMD_WRITE_QUIT (CMD_WRITE | CMD_QUIT) /* Write file and quit command */ #define CMD_OWRITE_QUIT (CMD_OWRITE | CMD_QUIT) /* Overwrite file and quit command */ #define IS_READ(a) (((uint8_t)(a) & CMD_READ) != 0) #define IS_WRITE(a) (((uint8_t)(a) & CMD_WRITE) != 0) #define IS_OWRITE(a) (((uint8_t)(a) & CMD_WRITE_MASK) == CMD_OWRITE) #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 && !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__) #define vi_message(vi, fmt, ...) vi_printf(vi, NULL, fmt, ##__VA_ARGS__) /* Debug */ #ifndef CONFIG_SYSTEM_VI_DEBUGLEVEL # define CONFIG_SYSTEM_VI_DEBUGLEVEL 0 #endif #if CONFIG_SYSTEM_VI_DEBUGLEVEL > 0 # define vidbg(format, ...) \ syslog(LOG_DEBUG, EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__) # define vvidbg(format, ap) \ vsyslog(LOG_DEBUG, format, ap) #else # define vidbg(x...) # define vvidbg(x...) #endif #if CONFIG_SYSTEM_VI_DEBUGLEVEL > 1 # define viinfo(format, ...) \ syslog(LOG_DEBUG, EXTRA_FMT format EXTRA_ARG, ##__VA_ARGS__) #else # define viinfo(x...) #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_CHANGE = 'c', /* Delete text and enter insert mode */ 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_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 */ KEY_CMDMODE_PAGEDOWN = CTRL('f'), /* Move forward one screen */ KEY_CMDMODE_REDRAW = CTRL('l'), /* Redraws the screen */ KEY_CMDMODE_REDRAW2 = CTRL('r'), /* Redraws the screen, removing deleted lines */ KEY_CMDMODE_HALFUP = CTRL('u') /* Move up (back) one half screen */ }; enum vi_insmode_key_e { KEY_INSMODE_QUOTE = '\\', /* The next character is quote (use literal value) */ }; enum vi_colonmode_key_e { KEY_COLMODE_READ = 'r', /* Read file */ KEY_COLMODE_QUIT = 'q', /* Quit vi */ KEY_COLMODE_WRITE = 'w', /* Write file */ KEY_COLMODE_FORCE = '!', /* Force operation */ KEY_COLMODE_QUOTE = '\\' /* The next character is quote (use literal value) */ }; enum vi_findmode_key_e { KEY_FINDMODE_QUOTE = '\\' /* The next character is quote (use literal value) */ }; /* VI modes */ 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_FINDINLINE /* f Find in line sub-mode */ }; /* This structure represents a cursor position */ struct vi_pos_s { uint16_t row; 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 { 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 display 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; /* Delete text arm flag */ bool chgarm; /* Change text arm flag */ bool yankarm; /* Yank text arm flag */ 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 */ FAR char *text; /* Dynamically allocated text buffer */ 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_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 }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Low-level display and data entry functions */ 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 int vi_getch(FAR struct vi_s *vi); #if 0 /* Not used */ static void vi_blinkon(FAR struct vi_s *vi); #endif static void vi_boldon(FAR struct vi_s *vi); static void vi_reverseon(FAR struct vi_s *vi); static void vi_attriboff(FAR struct vi_s *vi); static void vi_cursoron(FAR struct vi_s *vi); static void vi_cursoroff(FAR struct vi_s *vi); #if 0 /* Not used */ static void vi_cursorhome(FAR struct vi_s *vi); #endif static void vi_setcursor(FAR struct vi_s *vi, uint16_t row, uint16_t column); static void vi_clrtoeol(FAR struct vi_s *vi); #if 0 /* Not used */ static void vi_clrscreen(FAR struct vi_s *vi); #endif /* Final Line display */ static void vi_printf(FAR struct vi_s *vi, FAR const char *prefix, FAR const char *fmt, ...) printf_like(3, 4); /* Line positioning */ static off_t vi_linebegin(FAR struct vi_s *vi, off_t pos); static off_t vi_prevline(FAR struct vi_s *vi, off_t pos); static off_t vi_lineend(FAR struct vi_s *vi, off_t pos); static off_t vi_nextline(FAR struct vi_s *vi, off_t pos); /* Text buffer management */ static bool vi_extendtext(FAR struct vi_s *vi, off_t pos, size_t increment); 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 */ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, FAR const char *filename); static bool vi_savetext(FAR struct vi_s *vi, FAR const char *filename, off_t pos, size_t size); static bool vi_checkfile(FAR struct vi_s *vi, FAR const char *filename); /* Mode management */ static void vi_setmode(FAR struct vi_s *vi, uint8_t mode, long value); static void vi_setsubmode(FAR struct vi_s *vi, uint8_t mode, char prompt, long value); static void vi_exitsubmode(FAR struct vi_s *vi, uint8_t mode); /* Display management */ 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 */ static void vi_cusorup(FAR struct vi_s *vi, int nlines); static void vi_cursordown(FAR struct vi_s *vi, int nlines); static off_t vi_cursorleft(FAR struct vi_s *vi, off_t curpos, int ncolumns); static off_t vi_cursorright(FAR struct vi_s *vi, off_t curpos, int ncolumns); 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_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 */ static void vi_cmdch(FAR struct vi_s *vi, char ch); static void vi_cmdbackspace(FAR struct vi_s *vi); 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 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); /* 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 ****************************************************************************/ /* VT100 escape sequences */ static const char g_cursoron[] = VT100_CURSORON; static const char g_cursoroff[] = VT100_CURSOROFF; #if 0 /* Not used */ static const char g_cursorhome[] = VT100_CURSORHOME; #endif static const char g_erasetoeol[] = VT100_CLEAREOL; #if 0 /* Not used */ static const char g_clrscreen[] = VT100_CLEARSCREEN; #endif static const char g_index[] = VT100_INDEX; static const char g_revindex[] = VT100_REVINDEX; static const char g_attriboff[] = VT100_MODESOFF; static const char g_boldon[] = VT100_BOLD; static const char g_reverseon[] = VT100_REVERSE; #if 0 /* Not used */ static const char g_blinkon[] = VT100_BLINK; static const char g_boldoff[] = VT100_BOLDOFF; static const char g_reverseoff[] = VT100_REVERSEOFF; static const char g_blinkoff[] = VT100_BLINKOFF; #endif static const char g_fmtcursorpos[] = VT100_FMT_CURSORPOS; /* Error format strings */ static const char g_fmtallocfail[] = "Failed to allocate memory"; static const char g_fmtcmdfail[] = "%s failed: %d"; static const char g_fmtnotfile[] = "%s is not a regular file"; 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 ****************************************************************************/ /**************************************************************************** * Low-level display and data entry functions ****************************************************************************/ /**************************************************************************** * Name: vi_write * * Description: * Write a sequence of bytes to the console device (stdout, fd = 1). * ****************************************************************************/ static void vi_write(FAR struct vi_s *vi, FAR const char *buffer, size_t buflen) { ssize_t nwritten; size_t nremaining = buflen; /* Loop until all bytes have been successfully written (or until a * unrecoverable error is encountered) */ do { /* Take the next gulp */ nwritten = write(1, buffer, buflen); /* Handle write errors. write() should neve return 0. */ if (nwritten <= 0) { /* EINTR is not really an error; it simply means that a signal was * received while waiting for write. */ int errcode = errno; if (nwritten == 0 || errcode != EINTR) { fprintf(stderr, "ERROR: write to stdout failed: %d\n", errcode); exit(EXIT_FAILURE); } } /* Decrement the count of bytes remaining to be sent (to handle the * case of a partial write) */ else { nremaining -= nwritten; } } while (nremaining > 0); } /**************************************************************************** * Name: vi_putch * * Description: * Write a single character to the console device. * ****************************************************************************/ static void vi_putch(FAR struct vi_s *vi, char ch) { vi_write(vi, &ch, 1); } /**************************************************************************** * Name: vi_getch * * Description: * Get a single character from the console device (stdin, fd = 0). * ****************************************************************************/ static int vi_getch(FAR struct vi_s *vi) { char buffer; ssize_t nread; /* Loop until we successfully read a character (or until an unexpected * error occurs). */ if (vi->tcurs != NULL) { int specialkey; int modifiers; /* Get key from termcurses */ return termcurses_getkeycode(vi->tcurs, &specialkey, &modifiers); } else { do { /* Read one character from the incoming stream */ nread = read(0, &buffer, 1); /* Check for error or end-of-file. */ if (nread <= 0) { /* 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); } /* On success, return the character that was read */ viinfo("Returning: %c[%02x]\n", isprint(buffer) ? buffer : '.', buffer); return buffer; } /**************************************************************************** * Name: vi_clearbottomline * * Description: * Clear the bottom statusline. * ****************************************************************************/ static void vi_clearbottomline(FAR struct vi_s *vi) { vi_setcursor(vi, vi->display.row - 1, 0); vi_clrtoeol(vi); } /**************************************************************************** * Name: vi_boldon * * Description: * Enable the blinking attribute at the current cursor location * ****************************************************************************/ static void vi_boldon(FAR struct vi_s *vi) { /* Send the VT100 BOLDON command */ vi_write(vi, g_boldon, sizeof(g_boldon)); } /**************************************************************************** * Name: vi_reverseon * * Description: * Enable the blinking attribute at the current cursor location * ****************************************************************************/ static void vi_reverseon(FAR struct vi_s *vi) { /* Send the VT100 REVERSON command */ vi_write(vi, g_reverseon, sizeof(g_reverseon)); } /**************************************************************************** * Name: vi_attriboff * * Description: * Disable all previously selected attributes. * ****************************************************************************/ static void vi_attriboff(FAR struct vi_s *vi) { /* Send the VT100 ATTRIBOFF command */ vi_write(vi, g_attriboff, sizeof(g_attriboff)); } /**************************************************************************** * Name: vi_cursoron * * Description: * Turn on the cursor * ****************************************************************************/ static void vi_cursoron(FAR struct vi_s *vi) { /* Send the VT100 CURSORON command */ vi_write(vi, g_cursoron, sizeof(g_cursoron)); } /**************************************************************************** * Name: vi_cursoroff * * Description: * Turn off the cursor * ****************************************************************************/ static void vi_cursoroff(FAR struct vi_s *vi) { /* Send the VT100 CURSOROFF command */ vi_write(vi, g_cursoroff, sizeof(g_cursoroff)); } /**************************************************************************** * Name: vi_setcursor * * Description: * Move the current cursor position to position (row,col) * ****************************************************************************/ static void vi_setcursor(FAR struct vi_s *vi, uint16_t row, uint16_t column) { char buffer[16]; int len; viinfo("row=%d column=%d\n", row, column); /* Format the cursor position command. The origin is (1,1). */ len = snprintf(buffer, sizeof(buffer), g_fmtcursorpos, row + 1, column + 1); /* Send the VT100 CURSORPOS command */ vi_write(vi, buffer, MIN(len, sizeof(buffer))); } /**************************************************************************** * Name: vi_clrtoeol * * Description: * Clear the display from the current cursor position to the end of the * current line. * ****************************************************************************/ static void vi_clrtoeol(FAR struct vi_s *vi) { /* Send the VT100 ERASETOEOL command */ vi_write(vi, g_erasetoeol, sizeof(g_erasetoeol)); } /**************************************************************************** * Name: vi_scrollup * * Description: * Scroll the display up 'nlines' by sending the VT100 INDEX command. * ****************************************************************************/ static void vi_scrollup(FAR struct vi_s *vi, uint16_t nlines) { viinfo("nlines=%d\n", nlines); /* Scroll for the specified number of lines */ for (; nlines; nlines--) { /* Send the VT100 INDEX command */ vi_write(vi, g_index, sizeof(g_index)); } /* Ensure bottom line is cleared */ vi_setcursor(vi, vi->display.row - 1, 0); vi_clrtoeol(vi); } /**************************************************************************** * Name: vi_scrolldown * * Description: * Scroll the display down 'nlines' by sending the VT100 REVINDEX command. * ****************************************************************************/ 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--) { /* Send the VT100 REVINDEX command */ vi_write(vi, g_revindex, sizeof(g_revindex)); } } /**************************************************************************** * Name: vi_printf * * Description: * Show a highlighted message at the final line of the display. * ****************************************************************************/ static void vi_printf(FAR struct vi_s *vi, FAR const char *prefix, FAR const char *fmt, ...) { struct vi_pos_s cursor; va_list ap; int len; /* Save the current cursor position */ cursor.row = vi->cursor.row; cursor.column = vi->cursor.column; /* Set up for a reverse text message on the final line */ vi_setcursor(vi, vi->display.row - 1, 0); vi_reverseon(vi); /* Expand the prefix message in the scratch buffer */ len = prefix ? snprintf(vi->scratch, sizeof(vi->scratch), "%s", prefix) : 0; len = MIN(len, sizeof(vi->scratch)); va_start(ap, fmt); len += vsnprintf(vi->scratch + len, sizeof(vi->scratch) - len, fmt, ap); len = MIN(len, sizeof(vi->scratch)); vvidbg(fmt, ap); va_end(ap); /* Write the error message to the display in reverse text */ vi_write(vi, vi->scratch, len); /* Restore normal attributes */ vi_attriboff(vi); /* Reposition the cursor */ vi_setcursor(vi, cursor.row, cursor.column); /* Remember that there is an error message on the last line of the display. * When the display is refreshed, the last line will not be altered until * the error is cleared. */ vi->error = true; VI_BEL(vi); } /**************************************************************************** * Line positioning ****************************************************************************/ /**************************************************************************** * Name: vi_linebegin * * Description: * Search backward for the beginning of the current line * ****************************************************************************/ static off_t vi_linebegin(FAR struct vi_s *vi, off_t pos) { /* Search backward to find the previous newline character (or, possibly, * the beginning of the text buffer). */ while (pos && vi->text[pos - 1] != '\n') { pos--; } viinfo("Return pos=%ld\n", (long)pos); return pos; } /**************************************************************************** * Name: vi_prevline * * Description: * Search backward for the beginning of the previous line * ****************************************************************************/ static off_t vi_prevline(FAR struct vi_s *vi, off_t pos) { /* Find the beginning the of current line */ pos = vi_linebegin(vi, pos); /* If this not the first line, then back up one more character to position * at the last byte of the previous line. */ if (pos > 0) { pos = vi_linebegin(vi, pos - 1); } viinfo("Return pos=%ld\n", (long)pos); return pos; } /**************************************************************************** * Name: vi_lineend * * Description: * Search forward for the end of the current line * ****************************************************************************/ static off_t vi_lineend(FAR struct vi_s *vi, off_t pos) { /* Search forward to find the next newline character. (or, possibly, * the end of the text buffer). */ while (pos < vi->textsize && vi->text[pos] != '\n') { pos++; } if (vi->text[pos] == '\n') { pos--; } viinfo("Return pos=%ld\n", (long)pos); return pos; } /**************************************************************************** * Name: vi_nextline * * Description: * Search backward for the start of the next line * ****************************************************************************/ 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) + 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. */ if (pos < vi->textsize) { pos++; } viinfo("Return pos=%ld\n", (long)pos); return pos; } /**************************************************************************** * Text buffer management ****************************************************************************/ /**************************************************************************** * Name: vi_extendtext * * Description: * Reallocate the in-memory file memory by (at least) 'increment' and make * space for new text of size 'increment' at the specified cursor position. * ****************************************************************************/ static bool vi_extendtext(FAR struct vi_s *vi, off_t pos, size_t increment) { FAR char *alloc; int i; viinfo("pos=%ld increment=%ld\n", (long)pos, (long)increment); /* Check if we need to reallocate */ if (!vi->text || vi->textsize + increment > vi->txtalloc) { /* Allocate in chunksize so that we do not have to reallocate so * often. */ size_t allocsize = ALIGN_GULP(vi->textsize + increment); alloc = realloc(vi->text, allocsize); if (alloc == NULL) { /* Reallocation failed */ vi_error(vi, g_fmtallocfail); return false; } /* Save the new buffer information */ vi->text = alloc; vi->txtalloc = allocsize; } /* Move text to make space for new text of size 'increment' at the current * cursor position */ for (i = vi->textsize - 1; i >= pos; i--) { vi->text[i + increment] = vi->text[i]; } /* Adjust end of file position */ vi->textsize += increment; vi->modified = true; return true; } /**************************************************************************** * Name: vi_shrinkpos * * Description: * This is really part of vi_shrinktext. When any text is deleted, any * positions lying beyond the deleted region in the text buffer must be * adjusted. * * Input Parameters: * delpos The position where text was deleted * delsize The number of bytes deleted. * pos A pointer to a position that may need to be adjusted. * ****************************************************************************/ 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); /* Check if the position is beyond the deleted region */ if (*pos > delpos + delsize) { /* Yes... just subtract the size of the deleted region */ *pos -= delsize; } /* What if the position is within the deleted region? Set it to the * beginning of the deleted region. */ else if (*pos > delpos) { *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; } } /**************************************************************************** * Name: vi_shrinktext * * Description: * Delete a region in the text buffer by copying the end of the text buffer * over the deleted region and adjusting the size of the region. The text * region may be reallocated in order to recover the unused memory. * ****************************************************************************/ static void vi_shrinktext(FAR struct vi_s *vi, off_t pos, size_t size) { FAR char *alloc; size_t allocsize; int i; viinfo("pos=%ld size=%ld\n", (long)pos, (long)size); /* Close up the gap to remove 'size' characters at 'pos' */ for (i = pos + size; i < vi->textsize; i++) { 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(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); if (!alloc) { vi_error(vi, g_fmtallocfail); return; } /* Save the new buffer information */ vi->text = alloc; vi->txtalloc = allocsize; } } /**************************************************************************** * File access ****************************************************************************/ /**************************************************************************** * Name: vi_insertfile * * Description: * Insert the contents of a file into the text buffer * ****************************************************************************/ static bool vi_insertfile(FAR struct vi_s *vi, off_t pos, FAR const char *filename) { struct stat buf; FILE *stream; off_t filesize; size_t nread; int result; bool ret; viinfo("pos=%ld filename=\"%s\"\n", (long)pos, filename); /* Get the size of the file */ result = stat(filename, &buf); if (result < 0) { vi_message(vi, "\"%s\" [New File]", filename); return false; } /* Check for zero-length file */ filesize = buf.st_size; if (filesize < 1) { return false; } /* Open the file for reading */ stream = fopen(filename, "r"); if (!stream) { vi_error(vi, g_fmtcmdfail, "open", errno); return false; } /* [Re]allocate the text buffer to hold the file contents at the current * cursor position. */ ret = false; if (vi_extendtext(vi, pos, filesize)) { /* Read the contents of the file into the text buffer at the * current cursor position. */ nread = fread(vi->text + pos, 1, filesize, stream); if (nread < filesize) { /* Report the error (or partial read), EINTR is not handled */ vi_error(vi, g_fmtcmdfail, "fread", errno); vi_shrinktext(vi, pos, filesize); } else { ret = true; } } vi->fullredraw = true; fclose(stream); return ret; } /**************************************************************************** * Name: vi_savetext * * Description: * Save a region of the text buffer to 'filename' * ****************************************************************************/ static bool vi_savetext(FAR struct vi_s *vi, FAR const char *filename, off_t pos, size_t size) { FAR FILE *stream; size_t nwritten; int len; viinfo("filename=\"%s\" pos=%ld size=%ld\n", filename, (long)pos, (long)size); /* Open the file for writing */ stream = fopen(filename, "w"); if (!stream) { vi_error(vi, g_fmtcmdfail, "fopen", errno); return false; } /* Write the region of the text buffer beginning at pos and extending * through pos + size -1. */ nwritten = fwrite(vi->text + pos, 1, size, stream); if (nwritten < size) { /* Report the error (or partial write). EINTR is not handled. */ vi_error(vi, g_fmtcmdfail, "fwrite", errno); fclose(stream); return false; } fclose(stream); len = snprintf(vi->scratch, sizeof(vi->scratch), "%dC written", nwritten); vi_write(vi, vi->scratch, MIN(len, sizeof(vi->scratch))); return true; } /**************************************************************************** * Name: vi_checkfile * * Description: * Check if a file by this name already exists. * ****************************************************************************/ static bool vi_checkfile(FAR struct vi_s *vi, FAR const char *filename) { struct stat buf; int ret; viinfo("filename=\"%s\"\n", filename); /* Get the size of the file */ ret = stat(filename, &buf); if (ret < 0) { /* The file does not exist */ return false; } /* It exists, but is it a regular file */ if (!S_ISREG(buf.st_mode)) { /* Report the error... there is really no good return value in * this case. */ vi_error(vi, g_fmtnotfile, filename); } return true; } /**************************************************************************** * Mode Management Functions ****************************************************************************/ /**************************************************************************** * Name: vi_setmode * * Description: * Set the new mode (or command sub-mode) and reset all other common state * variables. NOTE that a numeric value may be passed to the new mode in * the value field. * ****************************************************************************/ static void vi_setmode(FAR struct vi_s *vi, uint8_t mode, long value) { viinfo("mode=%d value=%ld\n", mode, value); /* Set the mode and clear mode-dependent states that are not preserved * across mode changes. */ vi->mode = mode; vi->delarm = false; vi->yankarm = false; vi->toparm = false; vi->chgarm = false; vi->wqarm = false; vi->value = value; vi->cmdlen = 0; } /**************************************************************************** * Name: vi_setsubmode * * Description: * Set up one of the data entry sub-modes of the command mode. These are * modes in which commands or search data will be entered on the final line * of the display. * ****************************************************************************/ static void vi_setsubmode(FAR struct vi_s *vi, uint8_t mode, char prompt, long value) { viinfo("mode=%d prompt='%c' value=%ld\n", mode, prompt, value); /* Set up the new mode */ vi_setmode(vi, mode, value); /* Save the previous cursor position (not required by all modes) */ vi->cursave.row = vi->cursor.row; vi->cursave.column = vi->cursor.column; /* Set up for data entry on the final line */ vi->cursor.row = vi->display.row - 1; vi->cursor.column = 0; vi_setcursor(vi, vi->cursor.row, vi->cursor.column); /* Output the prompt character in bold text */ vi_boldon(vi); vi_putch(vi, prompt); vi_attriboff(vi); /* Clear to the end of the line */ vi_clrtoeol(vi); /* Update the cursor position */ vi->cursor.column = 1; } /**************************************************************************** * Name: vi_exitsubmode * * Description: * Exit the data entry sub-mode and return to normal command mode. * ****************************************************************************/ static void vi_exitsubmode(FAR struct vi_s *vi, uint8_t mode) { viinfo("mode=%d\n", mode); /* Set up the new mode */ vi_setmode(vi, mode, 0); /* Restore the saved cursor position */ vi->cursor.row = vi->cursave.row; vi->cursor.column = vi->cursave.column; } /**************************************************************************** * Display Management ****************************************************************************/ /**************************************************************************** * Name: vi_windowpos * * Description: * Based on the position of the cursor in the text buffer, determine the * horizontal display cursor position, performing TAB expansion as * necessary. * ****************************************************************************/ static void vi_windowpos(FAR struct vi_s *vi, off_t start, off_t end, uint16_t *pcolumn, off_t *ppos) { uint16_t column; off_t pos; viinfo("start=%ld end=%ld\n", (long)start, (long)end); /* Make sure that the end position is not beyond the end of the text. We * assume that the start position is okay. */ if (end > vi->textsize) { end = vi->textsize; } /* Loop incrementing the text buffer position while text buffer position * is within range. */ for (pos = start, column = 0; pos < end && (column < vi->reqcolumn || vi->updatereqcol); pos++) { /* Is there a newline terminator at this position? */ if (vi->text[pos] == '\n') { /* Yes... break out of the loop return the cursor column */ if (vi->mode != MODE_INSERT && vi->mode != MODE_REPLACE && pos != start) { pos--; column--; } break; } /* No... Is there a TAB at this position? */ else if (vi->text[pos] == '\t') { /* Yes.. expand the TAB */ column = NEXT_TAB(column); } /* No, then just increment the cursor column by one character */ else { column++; } } /* 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) { *ppos = pos; } if (pcolumn) { *pcolumn = column; } if (vi->updatereqcol) { vi->reqcolumn = column; vi->updatereqcol = false; } } /**************************************************************************** * Name: vi_scrollcheck * * Description: * Check if any operations will require that we scroll the display. * ****************************************************************************/ static void vi_scrollcheck(FAR struct vi_s *vi) { off_t curline; off_t pos; uint16_t tmp; int column; int nlines; /* Sanity test */ if (vi->curpos > vi->textsize) { vi->curpos = vi->textsize; } /* Get the text buffer offset to the beginning of the current line */ curline = vi_linebegin(vi, vi->curpos); /* Check if the current line is above the first line on the display */ while (curline < vi->winpos) { /* Yes.. move the window position up to the beginning of the previous * 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 * top of the display. */ vi->cursor.row = 0; for (pos = vi->winpos; pos < curline; pos = vi_nextline(vi, pos)) { vi->cursor.row++; } /* Check if the cursor row position is below the bottom of the display */ 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 * unrestricted column number of cursor. hscroll is the horizontal offset * in characters. */ vi_windowpos(vi, curline, vi->curpos, &tmp, NULL); column = (int)tmp - (int)vi->hscroll; /* Force the cursor column to lie on the display. First check if the * column lies to the left of the horizontal scrolling position. If it * does, move the scroll position to the left by tabs until the cursor * lies on the display. */ while (column < 0) { column += VI_TABSIZE; vi->hscroll -= VI_TABSIZE; vi->fullredraw = true; } /* If the cursor column lies to the right of the display, then adjust * the horizontal scrolling position so that the cursor position does * lie on the display. */ while (column >= vi->display.column) { column -= VI_TABSIZE; vi->hscroll += VI_TABSIZE; vi->fullredraw = true; } /* That final adjusted position is the display cursor column */ vi->cursor.column = column; /* Check if new window position is below the previous position. * In this case, we will need to scroll up until the new window * position is at the top of the display. */ if (vi->winpos > vi->prevpos) { /* We will need to scroll up. Count how many lines we * need to scroll. */ for (nlines = 0, pos = vi->prevpos; pos != vi->winpos && nlines < vi->display.row - 1; nlines++) { pos = vi_nextline(vi, pos); } /* Then scroll up that number of lines */ if (nlines < vi->display.row - 1) { vi_scrollup(vi, nlines); vi->fullredraw = true; } } /* Check if new window position is above the previous position. * In this case, we will need to scroll down until the new window * position is at the top of the display. */ else if (vi->winpos < vi->prevpos) { for (nlines = 0, pos = vi->prevpos; pos != vi->winpos && nlines < vi->display.row - 1; nlines++) { pos = vi_prevline(vi, pos); } /* Then scroll down that number of lines */ if (nlines < vi->display.row - 1) { vi_scrolldown(vi, nlines); vi->fullredraw = true; } } /* Save the previous top-of-display position for next time around. * This can be modified asynchronously by text deletion operations. */ vi->prevpos = vi->winpos; viinfo("winpos=%ld hscroll=%d\n", (long)vi->winpos, (long)vi->hscroll); } /**************************************************************************** * Name: vi_showtext * * Description: * Update the display based on the last operation. This function is * called at the beginning of the processing loop in Command and Insert * modes (and also in the continuous replace mode). * ****************************************************************************/ 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. */ 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 - 1; /* Make sure that all character attributes are disabled; Turn off the * cursor during the update. */ 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->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. */ endcol = vi->display.column; if (row >= vi->display.row - 1) { endcol--; } /* Get the position into this line corresponding to display column 0, * accounting for horizontal scrolling and tab expansion. Add that to * the line start offset to get the first offset to consider for * display. */ vi_windowpos(vi, pos, pos + vi->hscroll, NULL, &pos); /* Set the cursor position to the beginning of this row and clear to * the end of the line. */ if (redraw_line) { vi_setcursor(vi, row, 0); /* Loop for each column */ writefrom = pos; for (column = 0; pos < vi->textsize && column < endcol; pos++) { /* Break out of the loop if we encounter the newline before the * last column is encountered. */ if (vi->text[pos] == '\n') { break; } /* Perform TAB expansion */ else if (vi->text[pos] == '\t') { /* Write collected characters */ if (writefrom != pos) { 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 { column++; } } /* Write collected characters */ if (writefrom != pos) { vi_write(vi, &vi->text[writefrom], pos - writefrom); } vi_clrtoeol(vi); } /* Skip to the beginning of the next line */ pos = vi_nextline(vi, pos); } if (pos == vi->textsize && vi->text[pos - 1] == '\n') { 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, sizeof(vi->scratch), "%jd,%d", (uintmax_t)(vi->cursor.row + vi->vscroll + 1), vi->cursor.column + vi->hscroll + 1); vi_write(vi, vi->scratch, MIN(len, sizeof(vi->scratch))); vi_clrtoeol(vi); vi_cursoron(vi); } /**************************************************************************** * Command Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_cusorup * * Description: * Move the cursor up one line in the text buffer. * ****************************************************************************/ static void vi_cusorup(FAR struct vi_s *vi, int nlines) { int remaining; off_t start; off_t end; viinfo("nlines=%d\n", nlines); /* How many lines do we need to move? Zero means 1 (so does 1) */ remaining = (nlines < 1 ? 1 : nlines); /* Get the offset to the start of the current line */ start = vi_linebegin(vi, vi->curpos); /* Now move the cursor position back the correct number of lines */ for (; remaining > 0; remaining--) { /* Get the start position of the previous line */ start = vi_prevline(vi, start); /* Find the cursor position on the next line corresponding to the * cursor position on the current line. */ end = start + vi->reqcolumn; vi_windowpos(vi, start, end, NULL, &vi->curpos); } } /**************************************************************************** * Name: vi_cursordown * * Description: * Move the cursor down one line in the text buffer * ****************************************************************************/ static void vi_cursordown(FAR struct vi_s *vi, int nlines) { int remaining; off_t start; off_t end; viinfo("nlines=%d\n", nlines); /* How many lines do we need to move? Zero means 1 (so does 1) */ remaining = (nlines < 1 ? 1 : nlines); /* Get the offset to the start of the current line */ start = vi_linebegin(vi, vi->curpos); /* Now move the cursor position forward the correct number of lines */ for (; remaining > 0; remaining--) { /* Get the start of the next line. */ start = vi_nextline(vi, start); /* Find the cursor position on the next line corresponding to the * cursor position on the current line. */ end = start + vi->reqcolumn; vi_windowpos(vi, start, end, NULL, &vi->curpos); } } /**************************************************************************** * 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. * ****************************************************************************/ static off_t vi_cursorleft(FAR struct vi_s *vi, off_t curpos, int ncolumns) { int remaining; viinfo("curpos=%ld ncolumns=%d\n", curpos, ncolumns); /* Loop decrementing the cursor position for each repetition count. Break * out early if we hit either the beginning of the text buffer, or the end * of the previous line. */ for (remaining = (ncolumns < 1 ? 1 : ncolumns); curpos > 0 && remaining > 0 && vi->text[curpos - 1] != '\n'; curpos--, remaining--) { } return curpos; } /**************************************************************************** * Name: vi_cursorright * * Description: * Move the cursor right 'ncolumns' columns in the text buffer (without * moving to the next line). Note that a repetition count of 0 means to * perform the movement once. * ****************************************************************************/ static off_t vi_cursorright(FAR struct vi_s *vi, off_t curpos, int ncolumns) { int remaining; 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. */ for (remaining = (ncolumns < 1 ? 1 : ncolumns); curpos < vi->textsize && remaining > 0 && vi->text[curpos] != '\n'; 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 * * Description: * Delete characters from the current cursor position forward * ****************************************************************************/ static void vi_delforward(FAR struct vi_s *vi) { off_t start; off_t end; 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 empty 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. */ start = vi->curpos; vi_yanktext(vi, start, end - 1, true, true); vi->curpos = start; if (at_end) { vi->curpos--; } vi->redrawline = true; } /**************************************************************************** * Name: vi_delbackward * * Description: * Delete characters before the current cursor position * ****************************************************************************/ static void vi_delbackward(FAR struct vi_s *vi) { off_t start; off_t end; 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); /* Get the cursor position as if we would have move the cursor left N * times (which might be 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. */ vi_yanktext(vi, start, end, true, true); vi->redrawline = true; } /**************************************************************************** * Name: vi_linerange * * Description: * Return the start and end positions for N lines in the text buffer, * beginning at the current line. This is logic common to yanking and * deleting lines. * ****************************************************************************/ static void vi_linerange(FAR struct vi_s *vi, off_t *start, off_t *end) { off_t next; int nlines; /* Get the offset in the text buffer to the beginning of the current line */ *start = vi_linebegin(vi, vi->curpos); /* Move one line unless a repetition count was provided */ nlines = (vi->value > 0 ? vi->value : 1); /* Search ahead to find the end of the last line to yank */ for (next = *start; nlines > 1; nlines--) { next = vi_nextline(vi, next); } *end = vi_lineend(vi, next); if (*end != vi->textsize) { (*end)++; } } /**************************************************************************** * Name: vi_delline * * Description: * Delete N lines from the text buffer, beginning at the current line. * ****************************************************************************/ static void vi_delline(FAR struct vi_s *vi) { /* Yank and remove text from the buffer */ vi_yank(vi, true); vi->drawtoeos = true; } /**************************************************************************** * Name: vi_deltoeol * * Description: * Delete to end of line. * ****************************************************************************/ static void vi_deltoeol(FAR struct vi_s *vi) { int start; int end; /* 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 > 0 && start != vi->textsize && vi->text[start - 1] != '\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; } /**************************************************************************** * Name: vi_yank * * Description: * Remove N lines from the text buffer, beginning at the current line and * copy the lines to an allocated yank buffer. * ****************************************************************************/ 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; bool empty_last_line = false; /* 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); /* Test if deleting last line with empty line above it */ if ((end > 0 && start == end && end == vi->textsize -1 && vi->text[end - 1] == '\n') || (start > 1 && end + 1 == vi->textsize && vi->text[start - 2] == '\n')) { empty_last_line = true; } vi_yanktext(vi, start, yank_end, 0, del_after_yank); /* If the last line was yanked, then remove the '\n' on the * previous line. */ if (end + 1 == textsize && start != end && del_after_yank) { vi_shrinktext(vi, vi->textsize - 1, 1); } /* Place cursor at beginning of the line */ if (del_after_yank) { if (empty_last_line) { vi->curpos = vi->textsize; } else { vi->curpos = vi_linebegin(vi, vi->curpos + pos_increment); } } } /**************************************************************************** * Name: vi_paste * * Description: * Copy line(s) from the yank buffer, and past them after the current line. * The contents of the yank buffer are released. * ****************************************************************************/ 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 pasted */ if (!vi->yank || vi->yanksize <= 0) { return; } /* Get the command count */ count = vi->value > 0 ? vi->value : 1; if (count > 1) { vi->fullredraw = true; } /* Test for char mode paste buffer */ while (count > 0) { if (vi->yankcharmode) { off_t pos; /* 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') || vi->curpos == vi->textsize) { off_t textsize = vi->textsize; bool at_end = vi->curpos == 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' || at_end) { size--; } } /* Ensure start <= textsize */ else if (start >= vi->textsize) { start = vi->textsize; new_curpos = start; 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; } /**************************************************************************** * Name: vi_gotoline * * Description: * Position the cursor at the line specified by vi->value. If * vi->value is zero, then the cursor is position at the end of the text * buffer. * ****************************************************************************/ static void vi_gotoline(FAR struct vi_s *vi) { viinfo("curpos=%ld value=%ld\n", (long)vi->curpos, vi->value); /* Special case the first line */ if (vi->value == 1) { vi->curpos = 0; } /* Work harder to position to lines in the middle */ else if (vi->value > 0) { uint32_t line; /* Got to the line == value */ for (line = vi->value, vi->curpos = 0; --line > 0 && vi->curpos < vi->textsize; ) { vi->curpos = vi_nextline(vi, vi->curpos); } } /* No value means to go to beginning of the last line */ else { /* Get the beginning of the last line */ 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, delete or change are armed */ if (vi->yankarm || vi->delarm || vi->chgarm) { /* 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 || vi->chgarm; } /* Perform the yank */ end = vi->curpos + 1 == vi->textsize ? vi->curpos : vi->curpos - 1; if (vi->chgarm) { end--; } /* Yank text if it isn't a single \n character */ if (!(start == end && vi->text[start] == '\n')) { vi_yanktext(vi, start, end, 1, vi->delarm | vi->chgarm); } if (vi->delarm | vi->chgarm) { /* 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->chgarm) { vi_saverepeat(vi, vi->delarm ? 'd' : 'c'); vi_appendrepeat(vi, 'w'); } #endif /* If change text is armed, then enter insert mode */ if (vi->chgarm) { vi_setmode(vi, MODE_INSERT, 0); } vi->delarm = false; vi->chgarm = 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 * * Description: * Command mode loop * ****************************************************************************/ static void vi_cmd_mode(FAR struct vi_s *vi) { viinfo("Enter command mode\n"); /* Loop while we are in command mode */ while (vi->mode == MODE_COMMAND) { bool preserve; int ch; /* 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 */ #ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT /* Test for end of command repeat */ if (vi->cmdrepeat && vi->cmdindex == vi->cmdcount) { /* Terminate the command repeat */ vi->cmdrepeat = false; } /* Test for active cmdrepeat */ if (vi->cmdrepeat) { /* Read next command from command buffer */ ch = vi->cmdbuf[vi->cmdindex++]; } else #endif { ch = vi_getch(vi); } /* Handle numeric input. Zero (0) with no preceding value is a * special case: It means to go to the beginning o the line. */ if (isdigit(ch) && (vi->value > 0 || ch != '0')) { uint32_t tmp = 10 * vi->value + (ch - '0'); if (tmp > UINT16_MAX) { tmp = UINT16_MAX; } /* Update the command repetition count */ vi->value = tmp; viinfo("Value=%ld\n", vi->value); 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 'c' disarms line yanking */ if (ch != 'c') { vi->chgarm = 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 * cases. */ 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 */ { 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_TOP: /* Move to top of screen */ { vi->curpos = vi->winpos; } break; case KEY_CMDMODE_BOTTOM: /* Move to bottom of screen */ { 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 */ { /* 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; case KEY_CMDMODE_DEL_LINE: /* Delete the current line */ { 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; } else { vi->delarm = true; preserve = true; } } break; case KEY_CMDMODE_CHANGE: { if (vi->chgarm) { #ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT vi_saverepeat(vi, ch); vi_appendrepeat(vi, ch); #endif vi_gotofirstnonwhite(vi); vi_deltoeol(vi); 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); } vi->chgarm = false; } else { vi->chgarm = true; preserve = true; } } 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, false); vi->yankarm = false; } else { vi->yankarm = true; preserve = true; } } break; case KEY_CMDMODE_PASTE: /* Paste line(s) from into text after current line */ { #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; } break; 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. */ vi_insertch(vi, '\n'); /* Then enter insert mode */ 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_linebegin(vi, vi->curpos); if (pos == 0) { /* Insert newline at beginning of file, then move to previous * 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. */ 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_CHANGETOEOL: /* Delete to end of current line */ { /* 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 */ { #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 */ strlcpy(vi->scratch, "wq", sizeof(vi->scratch)); vi->cmdlen = 2; vi_parsecolon(vi); /* If save quit succeeds, 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; #ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT 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; #endif 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; case KEY_CMDMODE_NEXTLINE: case '\n': /* LF terminates line */ { vi->curpos = vi_nextline(vi, vi->curpos); vi_gotofirstnonwhite(vi); } break; 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 */ case KEY_CMDMODE_REDRAW2: /* Redraws the screen, removing deleted lines */ case KEY_CMDMODE_MARK: /* Place a mark beginning at the current cursor position */ default: { if (ch == -1) { continue; } else { VI_BEL(vi); } } break; } /* Any non-numeric input will reset the accumulated value (after it has * been used). There are a few exceptions: * * - For the double character sequences, we need to retain the value * until the next character is entered. * - If we are changing modes, then we may need to preserve the 'value' * as well; in some cases settings are passed to the new mode in * 'value' (vi_setmode() will have set or cleared 'value' * appropriately). */ if (!preserve) { vi->value = 0; } } } /**************************************************************************** * Common Sub-Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_cmdch * * Description: * Insert one character into the data entry line * ****************************************************************************/ static void vi_cmdch(FAR struct vi_s *vi, char ch) { int index = vi->cmdlen; int next = index + 1; viinfo("cmdlen=%d ch=%c[%02x]\n", vi->cmdlen, isprint(ch) ? ch : '.', ch); /* Abort gracelessly if the scratch buffer becomes full */ if (next >= SCRATCH_BUFSIZE) { vi_exitsubmode(vi, MODE_COMMAND); return; } /* Add the new character to the scratch buffer */ vi->scratch[index] = ch; vi->cmdlen = next; /* Update the cursor position */ vi->cursor.column = next + 1; /* And add the new character to the display */ vi_putch(vi, ch); if (ch == '\n') { vi->drawtoeos = true; } } /**************************************************************************** * Name: vi_cmdbackspace * * Description: * Process a backspace character in the data entry line * ****************************************************************************/ static void vi_cmdbackspace(FAR struct vi_s *vi) { viinfo("cmdlen=%d\n", vi->cmdlen); if (vi->cmdlen > 0) { vi_setcursor(vi, vi->display.row - 1, vi->cmdlen); vi_clrtoeol(vi); /* Update the command index and cursor position */ vi->cursor.column = vi->cmdlen; /* Decrement the number of characters on in the command */ vi->cmdlen--; } } /**************************************************************************** * Colon Data Entry Sub-Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_parsecolon * * Description: * Parse the colon command collected in the scratch buffer * ****************************************************************************/ static void vi_parsecolon(FAR struct vi_s *vi) { FAR const char *filename = NULL; uint8_t cmd = CMD_NONE; bool done = false; bool forced; int col; int ch; viinfo("Parse: \"%s\"\n", vi->scratch); /* NUL terminate the command */ 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++) { /* Get the next command character from the scratch buffer */ ch = vi->scratch[col]; /* Check if the next after that is KEY_COLMODE_FORCE */ forced = false; if (col < vi->cmdlen && vi->scratch[col + 1] == KEY_COLMODE_FORCE) { /* Yes.. the operation is forced */ forced = true; col++; } /* Then process the command character */ switch (ch) { case KEY_COLMODE_READ: { /* Reading a file should not be forced */ if (cmd == CMD_NONE && !forced) { cmd = CMD_READ; } else { /* The read operation is not compatible with writing or * quitting */ goto errout_bad_command; } } break; case KEY_COLMODE_WRITE: { /* Are we just writing? Or writing then quitting? */ if (cmd == CMD_NONE) { /* Just writing.. do we force overwriting? */ cmd = (forced ? CMD_OWRITE : CMD_WRITE); } else if (cmd == CMD_QUIT) { /* Both ... do we force overwriting the file? */ cmd = (forced ? CMD_OWRITE_QUIT : CMD_WRITE_QUIT); } else { /* Anything else, * including a forced quit is a syntax error */ goto errout_bad_command; } } break; case KEY_COLMODE_QUIT: { /* Are we just quitting? Or writing then quitting? */ if (cmd == CMD_NONE) { /* Just quitting... should we discard any changes? */ cmd = (forced ? CMD_DISCARD : CMD_QUIT); } /* If we are also writing, then it makes no sense to force the * quit operation. */ else if (cmd == CMD_WRITE && !forced) { cmd = CMD_WRITE_QUIT; } else if (cmd == CMD_OWRITE && !forced) { cmd = CMD_OWRITE_QUIT; } else { /* Quit is not compatible with reading */ goto errout_bad_command; } } break; default: { /* Ignore whitespace */ if (ch != ' ') { /* Anything else terminates the loop */ done = true; /* If there is anything else on the line, then it must be * a file name. If we are writing (or reading with an * empty text buffer), then we will need to copy the file * into the filename storage area. */ if (ch != '\0') { /* For now, just remember where the file is in the * scratch buffer. */ filename = &vi->scratch[col]; } } } break; } } /* Did we find any valid command? A read command requires a filename. * A filename where one is not needed is also an error. */ viinfo("cmd=%d filename=\"%s\"\n", cmd, vi->filename); if (cmd == CMD_NONE || (IS_READ(cmd) && !filename) || (!USES_FILE(cmd) && filename)) { goto errout_bad_command; } /* Are we writing to a new filename? If we are not forcing the write, * then we have to check if the file exists. */ if (filename && IS_NOWRITE(cmd)) { /* Check if the file exists */ if (vi_checkfile(vi, filename)) { /* It does... show an error and exit */ vi_error(vi, g_fmtfileexists); goto errout; } } /* Check if we are trying to quit with un-saved changes. The user must * force quitting in this case. */ if (vi->modified && IS_NDISCARD(cmd)) { /* Show an error and exit */ vi_error(vi, g_fmtmodified); goto errout; } /* Are we now committed to reading the file? */ if (IS_READ(cmd)) { /* Was the text buffer empty? */ bool empty = (vi->textsize == 0); /* Yes.. get the cursor position of the beginning of the next line */ off_t pos = vi_nextline(vi, vi->curpos); /* Then read the file into the text buffer at that position. */ if (vi_insertfile(vi, pos, filename)) { /* Was the text buffer empty before we inserted the file? */ if (empty) { /* Yes.. then we want to save the filename and mark the text * as unmodified. */ strlcpy(vi->filename, filename, MAX_FILENAME); vi->modified = false; } else { /* No.. then we want to retain the filename and mark the text * as modified. */ vi->modified = true; } } } /* Are we now committed to writing the file? */ if (IS_WRITE(cmd)) { /* If we are writing to a new file, then we need to copy the filename * from the scratch buffer to the filename buffer. */ if (filename) { strlcpy(vi->filename, filename, MAX_FILENAME); } /* If it is not a new file and if there are no changes to the text * buffer, then ignore the write. */ 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)) { /* An error occurred while saving the file and we are * not forcing the quit operation. So error out without * quitting until the user decides what to do about * the save failure. */ goto errout; } /* The text buffer contents are no longer modified */ if (!IS_QUIT(cmd)) { vi_setcursor(vi, vi->cursor.row, vi->cursor.column); } vi->modified = false; } } /* Are we committed to exit-ing? */ if (IS_QUIT(cmd)) { /* Yes... free resources and exit */ vi_putch(vi, '\n'); vi_release(vi); exit(EXIT_SUCCESS); } /* Otherwise, revert to command mode */ vi_exitsubmode(vi, MODE_COMMAND); return; errout_bad_command: vi_error(vi, g_fmtnotcmd, vi->scratch); errout: vi_exitsubmode(vi, MODE_COMMAND); } /**************************************************************************** * Name: vi_cmd_submode * * Description: * Colon command sub-mode of the command mode processing * ****************************************************************************/ static void vi_cmd_submode(FAR struct vi_s *vi) { int ch; viinfo("Enter colon command sub-mode\n"); /* Loop while we are in colon command mode */ while (vi->mode == SUBMODE_COLON) { /* Get the next character from the input */ ch = vi_getch(vi); /* Handle the newly received character */ switch (ch) { case KEY_COLMODE_QUOTE: /* Quoted character follows */ { /* Insert the next character unconditionally */ vi_cmdch(vi, vi_getch(vi)); } break; case ASCII_BS: /* Delete the character(s) before the cursor */ { if (vi->cmdlen == 0) { vi_exitsubmode(vi, MODE_COMMAND); /* Ensure bottom line is cleared */ vi_clearbottomline(vi); } else { vi_cmdbackspace(vi); } } break; case ASCII_ESC: /* Escape exits colon mode */ { vi_exitsubmode(vi, MODE_COMMAND); } break; /* What do we do with carriage returns? line feeds? */ case '\n': /* LF terminates line */ { vi_parsecolon(vi); } break; default: { /* Ignore all but printable characters */ if (isprint(ch)) { /* Insert the filtered character into the scratch buffer */ vi_cmdch(vi, ch); } else { VI_BEL(vi); } } break; } } } /**************************************************************************** * Find Data Entry Sub-Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_findstring * * 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. * ****************************************************************************/ static bool vi_findstring(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 + len <= vi->textsize; 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 after the * 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; } /**************************************************************************** * Name: vi_parsefind * * Description: * Find the string collected in the scratch buffer. * ****************************************************************************/ static void vi_parsefind(FAR struct vi_s *vi, bool revfind) { /* Make certain that the scratch buffer contents are NUL terminated */ vi->scratch[vi->cmdlen] = '\0'; /* Is there anything in the scratch buffer? If not, then we will use the * string from the previous find operation. */ viinfo("scratch: \"%s\"\n", vi->scratch); if (vi->cmdlen > 0) { /* Copy the new search string from the scratch to the find buffer */ strlcpy(vi->findstr, vi->scratch, MAX_STRING); } /* Then attempt to find the string */ vi->revfind = revfind; if (revfind) { vi_revfindstring(vi); } else { vi_findstring(vi); } /* Exit the sub-mode and revert to command mode */ vi_exitsubmode(vi, MODE_COMMAND); } /**************************************************************************** * Name: vi_find_submode * * Description: * Find command sub-mode of the command mode processing * ****************************************************************************/ static void vi_find_submode(FAR struct vi_s *vi, bool revfind) { int ch; viinfo("Enter find sub-mode\n"); /* Loop while we are in find mode */ while (vi->mode == SUBMODE_FIND || vi->mode == SUBMODE_REVFIND) { /* Get the next character from the input */ ch = vi_getch(vi); /* Handle the newly received character */ switch (ch) { case KEY_FINDMODE_QUOTE: /* Quoted character follows */ { /* Insert the next character unconditionally */ vi_cmdch(vi, vi_getch(vi)); } break; case ASCII_BS: /* Delete the character before the cursor */ { if (vi->cmdlen == 0) { vi_exitsubmode(vi, MODE_COMMAND); /* Ensure bottom line is cleared */ vi_clearbottomline(vi); } else { vi_cmdbackspace(vi); } } break; case ASCII_ESC: /* Escape exits find mode */ { vi_exitsubmode(vi, MODE_COMMAND); } break; /* What do we do with carriage returns? line feeds? */ case '\n': /* LF terminates line */ { vi_parsefind(vi, revfind); } break; default: { /* Ignore all but printable characters */ if (isprint(ch)) { /* Insert the filtered character into the scratch buffer */ vi_cmdch(vi, ch); } else { VI_BEL(vi); } } break; } } } /**************************************************************************** * Replace Text Sub-Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_replacech * * Description: * Replace the character at the current position. If the current position * is the end of line, then insert the character. * ****************************************************************************/ static void vi_replacech(FAR struct vi_s *vi, char ch) { viinfo("curpos=%ld ch=%c[%02x]\n", vi->curpos, isprint(ch) ? ch : '.', ch); /* Is there a newline at the current cursor position? */ if (vi->text[vi->curpos] == '\n') { /* 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; } } /**************************************************************************** * Name: vi_replacech_submode * * Description: * Replace character command sub-mode of the command mode processing * ****************************************************************************/ static void vi_replacech_submode(FAR struct vi_s *vi) { off_t end; long nchars; bool found = false; int ch = 0; /* Get the number of characters to replace */ nchars = (vi->value > 0 ? vi->value : 1); viinfo("Enter replaces character(s) sub-mode: nchars=%d\n", nchars); /* Are there that many characters left on the line to be replaced? */ end = vi_lineend(vi, vi->curpos) + 1; if (vi->curpos + nchars > end) { vi_error(vi, g_fmtnotvalid); vi_setmode(vi, MODE_COMMAND, 0); } /* Loop until we get the replacement character */ while (vi->mode == SUBMODE_REPLACECH && !found) { /* Get the next character from the input */ ch = vi_getch(vi); /* Handle the newly received character */ vi->updatereqcol = true; switch (ch) { case KEY_FINDMODE_QUOTE: /* Quoted character follows */ { /* Insert the next character unconditionally */ ch = vi_getch(vi); found = true; } break; case ASCII_ESC: /* Escape exits replace mode */ { vi_setmode(vi, MODE_COMMAND, 0); if (vi->curpos > 0) { --vi->curpos; } } break; /* What do we do with carriage returns? line feeds? */ case '\n': /* LF terminates line */ { found = true; } break; default: { /* Ignore all but printable characters and tab */ if (isprint(ch) || ch == '\t') { found = true; } else { VI_BEL(vi); } } break; } } /* Now replace with the character nchar times */ 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 || vi->chgarm) { /* Yank the text and possibly delete */ vi_yanktext(vi, vi->curpos, pos, 1, vi->delarm || vi->chgarm); if (vi->delarm || vi->chgarm) { /* Redraw line if text deleted */ vi->redrawline = true; } #ifdef CONFIG_SYSTEM_VI_INCLUDE_COMMAND_REPEAT /* Setup command repeat */ if (vi->delarm || vi->chgarm) { vi_saverepeat(vi, vi->delarm ? 'd' : 'c'); vi_appendrepeat(vi, vi->tfind ? 't' : 'f'); vi_appendrepeat(vi, ch); } #endif /* If change text is armed, then enter insert mode */ if (vi->chgarm) { vi->chgarm = false; vi_setmode(vi, MODE_INSERT, 0); return; } vi->delarm = false; vi->yankarm = false; } else { /* Simply move to the specified location */ vi->curpos = pos; } } /* Revert to command mode */ vi_setmode(vi, MODE_COMMAND, 0); } /**************************************************************************** * Insert and Replace Mode Functions ****************************************************************************/ /**************************************************************************** * Name: vi_insertch * * Description: * Insert one character into the text buffer * ****************************************************************************/ static void vi_insertch(FAR struct vi_s *vi, char ch) { viinfo("curpos=%ld ch=%c[%02x]\n", vi->curpos, isprint(ch) ? ch : '.', ch); /* Make space in the buffer for the new character */ if (vi_extendtext(vi, vi->curpos, 1)) { /* Add the new character to the buffer */ vi->text[vi->curpos++] = ch; } } /**************************************************************************** * Name: vi_insert_mode * * Description: * Insert mode loop * ****************************************************************************/ 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 || vi->mode == MODE_REPLACE) { /* Make sure that the display reflects the current state */ 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 */ #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 (isprint(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 */ switch (ch) { case KEY_INSMODE_QUOTE: /* Quoted character follows */ { /* Insert the next character unconditionally */ vi_insertch(vi, vi_getch(vi)); } break; case ASCII_DEL: { if (vi->curpos < vi->textsize) { if (vi->text[vi->curpos] == '\n') { vi->drawtoeos = true; } vi_shrinktext(vi, vi->curpos, 1); } } break; case ASCII_BS: { /* Backspace changes based on mode */ if (vi->mode == MODE_INSERT) { /* 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; 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; /* What do we do with carriage returns? */ case '\n': /* LF terminates line */ { if (vi->mode == MODE_INSERT) { vi_insertch(vi, '\n'); } else { vi_replacech(vi, '\n'); } vi->drawtoeos = true; } break; 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; } } 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: { /* Don't print BEL if char is -1 from termcurses */ if (ch == -1) { continue; } else { VI_BEL(vi); } } break; } } vi_clearbottomline(vi); } /**************************************************************************** * Command line processing ****************************************************************************/ /**************************************************************************** * Name: vi_showusage * * Description: * Show command line arguments and exit. * ****************************************************************************/ static void vi_release(FAR struct vi_s *vi) { if (vi) { if (vi->text) { free(vi->text); } if (vi->yank) { free(vi->yank); } if (vi->tcurs) { termcurses_deinitterm(vi->tcurs); } free(vi); } } /**************************************************************************** * Name: vi_showusage * * Description: * Show command line arguments and exit. * ****************************************************************************/ static void vi_showusage(FAR struct vi_s *vi, FAR const char *progname, int exitcode) { fprintf(stderr, "\nUSAGE:\t%s [-c ] []\n", progname); fprintf(stderr, "\nUSAGE:\t%s -h\n\n", progname); fprintf(stderr, "Where:\n"); fprintf(stderr, "\t:\n"); fprintf(stderr, "\t\tOptional name of the file to open\n"); fprintf(stderr, "\t-c :\n"); fprintf(stderr, "\t\tOptional width of the display in columns. Default: %d\n", CONFIG_SYSTEM_VI_COLS); fprintf(stderr, "\t-r :\n"); fprintf(stderr, "\t\tOptional height of the display in rows. Default: %d\n", CONFIG_SYSTEM_VI_ROWS); fprintf(stderr, "\t-h:\n"); fprintf(stderr, "\t\tShows this message and exits.\n"); /* Release all allocated resources and exit */ vi_release(vi); exit(exitcode); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: vi_main * * Description: * The main entry point into vi. * ****************************************************************************/ int main(int argc, FAR 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 == NULL) { vi_error(vi, g_fmtallocfail); return EXIT_FAILURE; } /* Initialize non-zero elements of the vi state structure */ vi->display.row = CONFIG_SYSTEM_VI_ROWS; vi->display.column = CONFIG_SYSTEM_VI_COLS; /* Parse command line arguments */ while ((option = getopt(argc, argv, ":c:r:h")) != ERROR) { switch (option) { case 'c': /* Display width in columns */ { unsigned long value = strtoul(optarg, NULL, 10); if (value <= UINT16_MAX) { vi->display.column = (uint16_t)value; } else { fprintf(stderr, "ERROR: Column value out of range: %lu\n", value); vi_showusage(vi, argv[0], EXIT_FAILURE); } } break; case 'r': /* Display width in columns */ { unsigned long value = strtoul(optarg, NULL, 10); if (value <= UINT16_MAX) { vi->display.row = (uint16_t)value; } else { fprintf(stderr, "ERROR: Row value out of range: %lu\n", value); vi_showusage(vi, argv[0], EXIT_FAILURE); } } break; case 'h': { vi_showusage(vi, argv[0], EXIT_SUCCESS); } break; case ':': { fprintf(stderr, "ERROR: Missing parameter argument\n"); vi_showusage(vi, argv[0], EXIT_FAILURE); } break; case '?': default: { fprintf(stderr, "ERROR: Unrecognized parameter\n"); vi_showusage(vi, argv[0], EXIT_FAILURE); } break; } } /* Initialize 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 maybe one additional argument on the command line: The filename */ if (optind < argc) { /* Copy the file name into the file name buffer */ if (argv[optind][0] == '/') { strlcpy(vi->filename, argv[optind], MAX_STRING); } else { /* Make file relative to current working directory */ getcwd(vi->filename, MAX_STRING); strlcat(vi->filename, "/", MAX_STRING); strlcat(vi->filename, argv[optind], MAX_STRING); } /* Make sure the (possibly truncated) file name is NUL terminated */ vi->filename[MAX_STRING - 1] = '\0'; /* Load the file into memory */ 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"); vi_showusage(vi, argv[0], EXIT_FAILURE); } /* The editor loop */ vi->fullredraw = true; for (; ; ) { /* We loop, processing each mode change */ viinfo("mode=%d\n", vi->mode); switch (vi->mode) { default: case MODE_COMMAND: /* Command mode */ vi_cmd_mode(vi); break; case SUBMODE_COLON: /* Colon data entry in command mode */ vi_cmd_submode(vi); break; case SUBMODE_REVFIND: case SUBMODE_FIND: /* Find data entry in command mode */ vi_find_submode(vi, vi->mode == SUBMODE_REVFIND); break; case SUBMODE_REPLACECH: /* Replace characters in command mode */ vi_replacech_submode(vi); break; case MODE_INSERT: /* Insert mode */ case MODE_REPLACE: /* Replace character(s) under cursor until ESC */ vi_insert_mode(vi); break; case MODE_FINDINLINE: /* Insert mode */ vi_findinline_mode(vi); break; } } /* We won't get here */ }