blob: e4014362dfe6ffecca716f2de7ec82b6b918051b [file] [log] [blame]
/* vi: set sw=8 ts=8: */
/*
* tiny vi.c: A small 'vi' clone
* Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
char *vi_Version =
"$Id: vi.c,v 1.3 2001/04/04 19:33:32 andersen Exp $";
/*
* To compile for standalone use:
* gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
* or
* gcc -Wall -Os -s -DSTANDALONE -DCRASHME -o vi vi.c # include testing features
* strip vi
*/
/*
* Things To Do:
* EXINIT
* $HOME/.exrc
* add magic to search /foo.*bar
* add :help command
* :map macros
* how about mode lines: vi: set sw=8 ts=8:
* if mark[] values were line numbers rather than pointers
* it would be easier to change the mark when add/delete lines
*/
//---- Feature -------------- Bytes to immplement
#ifdef STANDALONE
#define vi_main main
#define BB_FEATURE_VI_COLON // 4288
#define BB_FEATURE_VI_YANKMARK // 1408
#define BB_FEATURE_VI_SEARCH // 1088
#define BB_FEATURE_VI_USE_SIGNALS // 1056
#define BB_FEATURE_VI_DOT_CMD // 576
#define BB_FEATURE_VI_READONLY // 128
#define BB_FEATURE_VI_SETOPTS // 576
#define BB_FEATURE_VI_SET // 224
#define BB_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
// To test editor using CRASHME:
// vi -C filename
// To stop testing, wait until all to text[] is deleted, or
// Ctrl-Z and kill -9 %1
// while in the editor Ctrl-T will toggle the crashme function on and off.
//#define BB_FEATURE_VI_CRASHME // randomly pick commands to execute
#endif /* STANDALONE */
#ifndef STANDALONE
#include "busybox.h"
#endif /* STANDALONE */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
#include <signal.h>
#include <setjmp.h>
#include <regex.h>
#include <ctype.h>
#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#ifndef TRUE
#define TRUE ((int)1)
#define FALSE ((int)0)
#endif /* TRUE */
#define MAX_SCR_COLS 300
// Misc. non-Ascii keys that report an escape sequence
#define VI_K_UP 128 // cursor key Up
#define VI_K_DOWN 129 // cursor key Down
#define VI_K_RIGHT 130 // Cursor Key Right
#define VI_K_LEFT 131 // cursor key Left
#define VI_K_HOME 132 // Cursor Key Home
#define VI_K_END 133 // Cursor Key End
#define VI_K_INSERT 134 // Cursor Key Insert
#define VI_K_PAGEUP 135 // Cursor Key Page Up
#define VI_K_PAGEDOWN 136 // Cursor Key Page Down
#define VI_K_FUN1 137 // Function Key F1
#define VI_K_FUN2 138 // Function Key F2
#define VI_K_FUN3 139 // Function Key F3
#define VI_K_FUN4 140 // Function Key F4
#define VI_K_FUN5 141 // Function Key F5
#define VI_K_FUN6 142 // Function Key F6
#define VI_K_FUN7 143 // Function Key F7
#define VI_K_FUN8 144 // Function Key F8
#define VI_K_FUN9 145 // Function Key F9
#define VI_K_FUN10 146 // Function Key F10
#define VI_K_FUN11 147 // Function Key F11
#define VI_K_FUN12 148 // Function Key F12
static const int YANKONLY = FALSE;
static const int YANKDEL = TRUE;
static const int FORWARD = 1; // code depends on "1" for array index
static const int BACK = -1; // code depends on "-1" for array index
static const int LIMITED = 0; // how much of text[] in char_search
static const int FULL = 1; // how much of text[] in char_search
static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
typedef unsigned char Byte;
static int editing; // >0 while we are editing a file
static int cmd_mode; // 0=command 1=insert
static int file_modified; // buffer contents changed
static int err_method; // indicate error with beep or flash
static int fn_start; // index of first cmd line file name
static int save_argc; // how many file names on cmd line
static int cmdcnt; // repetition count
static fd_set rfds; // use select() for small sleeps
static struct timeval tv; // use select() for small sleeps
static char erase_char; // the users erase character
static int rows, columns; // the terminal screen is this size
static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
static char *SOs, *SOn;
static Byte *status_buffer; // mesages to the user
static Byte last_input_char; // last char read from user
static Byte last_forward_char; // last char searched for with 'f'
static Byte *cfn; // previous, current, and next file name
static Byte *text, *end, *textend; // pointers to the user data in memory
static Byte *screen; // pointer to the virtual screen buffer
static int screensize; // and its size
static Byte *screenbegin; // index into text[], of top line on the screen
static Byte *dot; // where all the action takes place
static int tabstop;
static struct termios term_orig, term_vi; // remember what the cooked mode was
#ifdef BB_FEATURE_VI_USE_SIGNALS
static jmp_buf restart; // catch_sig()
#endif /* BB_FEATURE_VI_USE_SIGNALS */
#ifdef BB_FEATURE_VI_WIN_RESIZE
static struct winsize winsize; // remember the window size
#endif /* BB_FEATURE_VI_WIN_RESIZE */
#ifdef BB_FEATURE_VI_DOT_CMD
static int adding2q; // are we currently adding user input to q
static Byte *last_modifying_cmd; // last modifying cmd for "."
static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
#endif /* BB_FEATURE_VI_DOT_CMD */
#if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
static Byte *modifying_cmds; // cmds that modify text[]
#endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_READONLY
static int readonly;
#endif /* BB_FEATURE_VI_READONLY */
#ifdef BB_FEATURE_VI_SETOPTS
static int autoindent;
static int showmatch;
static int ignorecase;
#endif /* BB_FEATURE_VI_SETOPTS */
#ifdef BB_FEATURE_VI_YANKMARK
static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
static int YDreg, Ureg; // default delete register and orig line for "U"
static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
static Byte *context_start, *context_end;
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_SEARCH
static Byte *last_search_pattern; // last pattern from a '/' or '?' search
#endif /* BB_FEATURE_VI_SEARCH */
static void edit_file(Byte *); // edit one file
static void do_cmd(Byte); // execute a command
static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
static Byte *end_line(Byte *); // return pointer to cur line E-o-l
static Byte *dollar_line(Byte *); // return pointer to just before NL
static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
static Byte *next_line(Byte *); // return pointer to next line B-o-l
static Byte *end_screen(void); // get pointer to last char on screen
static int count_lines(Byte *, Byte *); // count line from start to stop
static Byte *find_line(int); // find begining of line #li
static Byte *move_to_col(Byte *, int); // move "p" to column l
static int isblnk(Byte); // is the char a blank or tab
static void dot_left(void); // move dot left- dont leave line
static void dot_right(void); // move dot right- dont leave line
static void dot_begin(void); // move dot to B-o-l
static void dot_end(void); // move dot to E-o-l
static void dot_next(void); // move dot to next line B-o-l
static void dot_prev(void); // move dot to prev line B-o-l
static void dot_scroll(int, int); // move the screen up or down
static void dot_skip_over_ws(void); // move dot pat WS
static void dot_delete(void); // delete the char at 'dot'
static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
static Byte *new_screen(int, int); // malloc virtual screen memory
static Byte *new_text(int); // malloc memory for text[] buffer
static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
static Byte *skip_thing(Byte *, int, int, int); // skip some object
static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
static void show_help(void); // display some help info
static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
static void rawmode(void); // set "raw" mode on tty
static void cookmode(void); // return to "cooked" mode on tty
static int mysleep(int); // sleep for 'h' 1/100 seconds
static Byte readit(void); // read (maybe cursor) key from stdin
static Byte get_one_char(void); // read 1 char from stdin
static int file_size(Byte *); // what is the byte size of "fn"
static int file_insert(Byte *, Byte *, int);
static int file_write(Byte *, Byte *, Byte *);
static void place_cursor(int, int);
static void screen_erase();
static void clear_to_eol(void);
static void clear_to_eos(void);
static void standout_start(void); // send "start reverse video" sequence
static void standout_end(void); // send "end reverse video" sequence
static void flash(int); // flash the terminal screen
static void beep(void); // beep the terminal
static void indicate_error(char); // use flash or beep to indicate error
static void show_status_line(void); // put a message on the bottom line
static void psb(char *, ...); // Print Status Buf
static void psbs(char *, ...); // Print Status Buf in standout mode
static void ni(Byte *); // display messages
static void edit_status(void); // show file status on status line
static void redraw(int); // force a full screen refresh
static void refresh(int); // update the terminal from screen[]
#ifdef BB_FEATURE_VI_SEARCH
static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
#endif /* BB_FEATURE_VI_SEARCH */
#ifdef BB_FEATURE_VI_COLON
static void Hit_Return(void);
static Byte *get_address(Byte *, int *); // get colon addr, if present
static void colon(Byte *); // execute the "colon" mode cmds
#endif /* BB_FEATURE_VI_COLON */
#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
static Byte *get_input_line(Byte *); // get input line- use "status line"
#endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
#ifdef BB_FEATURE_VI_USE_SIGNALS
static void winch_sig(int); // catch window size changes
static void suspend_sig(int); // catch ctrl-Z
static void alarm_sig(int); // catch alarm time-outs
static void catch_sig(int); // catch ctrl-C
static void core_sig(int); // catch a core dump signal
#endif /* BB_FEATURE_VI_USE_SIGNALS */
#ifdef BB_FEATURE_VI_DOT_CMD
static void start_new_cmd_q(Byte); // new queue for command
static void end_cmd_q(); // stop saving input chars
#else /* BB_FEATURE_VI_DOT_CMD */
#define end_cmd_q()
#endif /* BB_FEATURE_VI_DOT_CMD */
#ifdef BB_FEATURE_VI_WIN_RESIZE
static void window_size_get(int); // find out what size the window is
#endif /* BB_FEATURE_VI_WIN_RESIZE */
#ifdef BB_FEATURE_VI_SETOPTS
static void showmatching(Byte *); // show the matching pair () [] {}
#endif /* BB_FEATURE_VI_SETOPTS */
#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
#endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
#ifdef BB_FEATURE_VI_YANKMARK
static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
static Byte what_reg(void); // what is letter of current YDreg
static void check_context(Byte); // remember context for '' command
static Byte *swap_context(Byte *); // goto new context for '' command
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_CRASHME
static void crash_dummy();
static void crash_test();
static int crashme = 0;
#endif /* BB_FEATURE_VI_CRASHME */
extern int vi_main(int argc, char **argv)
{
int c;
#ifdef BB_FEATURE_VI_YANKMARK
int i;
#endif /* BB_FEATURE_VI_YANKMARK */
SOs = "\033[7m"; // Terminal standout mode on
SOn = "\033[0m"; // Terminal standout mode off
#ifdef BB_FEATURE_VI_CRASHME
(void) srand((long) getpid());
#endif /* BB_FEATURE_VI_CRASHME */
status_buffer = (Byte *) malloc(200); // hold messages to user
#ifdef BB_FEATURE_VI_READONLY
readonly = FALSE;
if (strncmp(argv[0], "view", 4) == 0) {
readonly = TRUE;
}
#endif /* BB_FEATURE_VI_READONLY */
#ifdef BB_FEATURE_VI_SETOPTS
autoindent = 1;
ignorecase = 1;
showmatch = 1;
#endif /* BB_FEATURE_VI_SETOPTS */
#ifdef BB_FEATURE_VI_YANKMARK
for (i = 0; i < 28; i++) {
reg[i] = 0;
} // init the yank regs
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_DOT_CMD
modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
#endif /* BB_FEATURE_VI_DOT_CMD */
// 1- process $HOME/.exrc file
// 2- process EXINIT variable from environment
// 3- process command line args
while ((c = getopt(argc, argv, "hCR")) != -1) {
switch (c) {
#ifdef BB_FEATURE_VI_CRASHME
case 'C':
crashme = 1;
break;
#endif /* BB_FEATURE_VI_CRASHME */
#ifdef BB_FEATURE_VI_READONLY
case 'R': // Read-only flag
readonly = TRUE;
break;
#endif /* BB_FEATURE_VI_READONLY */
//case 'r': // recover flag- ignore- we don't use tmp file
//case 'x': // encryption flag- ignore
//case 'c': // execute command first
//case 'h': // help -- just use default
default:
show_help();
break;
}
}
// The argv array can be used by the ":next" and ":rewind" commands
// save optind.
fn_start = optind; // remember first file name for :next and :rew
save_argc = argc;
//----- This is the main file handling loop --------------
if (optind >= argc) {
editing = 1; // 0= exit, 1= one file, 2= multiple files
edit_file(0);
} else {
for (; optind < argc; optind++) {
editing = 1; // 0=exit, 1=one file, 2+ =many files
if (cfn != 0)
free(cfn);
cfn = (Byte *) strdup(argv[optind]);
edit_file(cfn);
}
}
//-----------------------------------------------------------
return (0);
}
static void edit_file(Byte * fn)
{
char c;
int cnt, size;
#ifdef BB_FEATURE_VI_USE_SIGNALS
char *msg;
int sig;
#endif /* BB_FEATURE_VI_USE_SIGNALS */
#ifdef BB_FEATURE_VI_YANKMARK
static Byte *cur_line;
#endif /* BB_FEATURE_VI_YANKMARK */
rawmode();
rows = 24;
columns = 80;
#ifdef BB_FEATURE_VI_WIN_RESIZE
window_size_get(0);
#endif /* BB_FEATURE_VI_WIN_RESIZE */
new_screen(rows, columns); // get memory for virtual screen
cnt = file_size(fn); // file size
size = 2 * cnt; // 200% of file size
new_text(size); // get a text[] buffer
screenbegin = dot = end = text;
if (fn != 0) {
file_insert(fn, text, cnt);
} else {
(void) char_insert(text, '\n'); // start empty buf with dummy line
}
file_modified = FALSE;
#ifdef BB_FEATURE_VI_YANKMARK
YDreg = 26; // default Yank/Delete reg
Ureg = 27; // hold orig line for "U" cmd
for (cnt = 0; cnt < 28; cnt++) {
mark[cnt] = 0;
} // init the marks
mark[26] = mark[27] = text; // init "previous context"
#endif /* BB_FEATURE_VI_YANKMARK */
err_method = 1; // flash
last_forward_char = last_input_char = '\0';
crow = 0;
ccol = 0;
edit_status();
#ifdef BB_FEATURE_VI_USE_SIGNALS
signal(SIGHUP, catch_sig);
signal(SIGINT, catch_sig);
signal(SIGALRM, alarm_sig);
signal(SIGTERM, catch_sig);
signal(SIGQUIT, core_sig);
signal(SIGILL, core_sig);
signal(SIGTRAP, core_sig);
signal(SIGIOT, core_sig);
signal(SIGABRT, core_sig);
signal(SIGFPE, core_sig);
signal(SIGBUS, core_sig);
signal(SIGSEGV, core_sig);
#ifdef SIGSYS
signal(SIGSYS, core_sig);
#endif
signal(SIGWINCH, winch_sig);
signal(SIGTSTP, suspend_sig);
sig = setjmp(restart);
if (sig != 0) {
msg = "";
if (sig == SIGWINCH)
msg = "(window resize)";
if (sig == SIGHUP)
msg = "(hangup)";
if (sig == SIGINT)
msg = "(interrupt)";
if (sig == SIGTERM)
msg = "(terminate)";
if (sig == SIGBUS)
msg = "(bus error)";
if (sig == SIGSEGV)
msg = "(I tried to touch invalid memory)";
if (sig == SIGALRM)
msg = "(alarm)";
psbs("-- caught signal %d %s--", sig, msg);
}
#endif /* BB_FEATURE_VI_USE_SIGNALS */
editing = 1;
cmd_mode = 0; // 0=command 1=insert 2='R'eplace
cmdcnt = 0;
tabstop = 8;
offset = 0; // no horizontal offset
c = '\0';
#ifdef BB_FEATURE_VI_DOT_CMD
if (last_modifying_cmd != 0)
free(last_modifying_cmd);
if (ioq_start != NULL)
free(ioq_start);
ioq = ioq_start = last_modifying_cmd = 0;
adding2q = 0;
#endif /* BB_FEATURE_VI_DOT_CMD */
redraw(TRUE);
show_status_line();
//------This is the main Vi cmd handling loop -----------------------
while (editing > 0) {
#ifdef BB_FEATURE_VI_CRASHME
if (crashme > 0) {
if ((end - text) > 1) {
crash_dummy(); // generate a random command
} else {
crashme = 0;
dot =
string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
refresh(FALSE);
}
}
#endif /* BB_FEATURE_VI_CRASHME */
last_input_char = c = get_one_char(); // get a cmd from user
#ifdef BB_FEATURE_VI_YANKMARK
// save a copy of the current line- for the 'U" command
if (begin_line(dot) != cur_line) {
cur_line = begin_line(dot);
text_yank(begin_line(dot), end_line(dot), Ureg);
}
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_DOT_CMD
// These are commands that change text[].
// Remember the input for the "." command
if (!adding2q && ioq_start == 0
&& strchr((char *) modifying_cmds, c) != NULL) {
start_new_cmd_q(c);
}
#endif /* BB_FEATURE_VI_DOT_CMD */
do_cmd(c); // execute the user command
//
// poll to see if there is input already waiting. if we are
// not able to display output fast enough to keep up, skip
// the display update until we catch up with input.
if (mysleep(0) == 0) {
// no input pending- so update output
refresh(FALSE);
show_status_line();
}
#ifdef BB_FEATURE_VI_CRASHME
if (crashme > 0)
crash_test(); // test editor variables
#endif /* BB_FEATURE_VI_CRASHME */
}
//-------------------------------------------------------------------
place_cursor(rows, 0); // go to bottom of screen
clear_to_eol(); // Erase to end of line
cookmode();
}
static Byte readbuffer[BUFSIZ];
#ifdef BB_FEATURE_VI_CRASHME
static int totalcmds = 0;
static int Mp = 85; // Movement command Probability
static int Np = 90; // Non-movement command Probability
static int Dp = 96; // Delete command Probability
static int Ip = 97; // Insert command Probability
static int Yp = 98; // Yank command Probability
static int Pp = 99; // Put command Probability
static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
char chars[20] = "\t012345 abcdABCD-=.$";
char *words[20] = { "this", "is", "a", "test",
"broadcast", "the", "emergency", "of",
"system", "quick", "brown", "fox",
"jumped", "over", "lazy", "dogs",
"back", "January", "Febuary", "March"
};
char *lines[20] = {
"You should have received a copy of the GNU General Public License\n",
"char c, cm, *cmd, *cmd1;\n",
"generate a command by percentages\n",
"Numbers may be typed as a prefix to some commands.\n",
"Quit, discarding changes!\n",
"Forced write, if permission originally not valid.\n",
"In general, any ex or ed command (such as substitute or delete).\n",
"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
"Please get w/ me and I will go over it with you.\n",
"The following is a list of scheduled, committed changes.\n",
"1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
"Any question about transactions please contact Sterling Huxley.\n",
"I will try to get back to you by Friday, December 31.\n",
"This Change will be implemented on Friday.\n",
"Let me know if you have problems accessing this;\n",
"Sterling Huxley recently added you to the access list.\n",
"Would you like to go to lunch?\n",
"The last command will be automatically run.\n",
"This is too much english for a computer geek.\n",
};
char *multilines[20] = {
"You should have received a copy of the GNU General Public License\n",
"char c, cm, *cmd, *cmd1;\n",
"generate a command by percentages\n",
"Numbers may be typed as a prefix to some commands.\n",
"Quit, discarding changes!\n",
"Forced write, if permission originally not valid.\n",
"In general, any ex or ed command (such as substitute or delete).\n",
"I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
"Please get w/ me and I will go over it with you.\n",
"The following is a list of scheduled, committed changes.\n",
"1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
"Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
"Any question about transactions please contact Sterling Huxley.\n",
"I will try to get back to you by Friday, December 31.\n",
"This Change will be implemented on Friday.\n",
"Let me know if you have problems accessing this;\n",
"Sterling Huxley recently added you to the access list.\n",
"Would you like to go to lunch?\n",
"The last command will be automatically run.\n",
"This is too much english for a computer geek.\n",
};
// create a random command to execute
static void crash_dummy()
{
static int sleeptime; // how long to pause between commands
char c, cm, *cmd, *cmd1;
int i, cnt, thing, rbi, startrbi, percent;
// "dot" movement commands
cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
// is there already a command running?
if (strlen((char *) readbuffer) > 0)
goto cd1;
cd0:
startrbi = rbi = 0;
sleeptime = 0; // how long to pause between commands
memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
// generate a command by percentages
percent = (int) lrand48() % 100; // get a number from 0-99
if (percent < Mp) { // Movement commands
// available commands
cmd = cmd1;
M++;
} else if (percent < Np) { // non-movement commands
cmd = "mz<>\'\""; // available commands
N++;
} else if (percent < Dp) { // Delete commands
cmd = "dx"; // available commands
D++;
} else if (percent < Ip) { // Inset commands
cmd = "iIaAsrJ"; // available commands
I++;
} else if (percent < Yp) { // Yank commands
cmd = "yY"; // available commands
Y++;
} else if (percent < Pp) { // Put commands
cmd = "pP"; // available commands
P++;
} else {
// We do not know how to handle this command, try again
U++;
goto cd0;
}
// randomly pick one of the available cmds from "cmd[]"
i = (int) lrand48() % strlen(cmd);
cm = cmd[i];
if (strchr(":\024", cm))
goto cd0; // dont allow these commands
readbuffer[rbi++] = cm; // put cmd into input buffer
// now we have the command-
// there are 1, 2, and multi char commands
// find out which and generate the rest of command as necessary
if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
cmd1 = " \n\r0$^-+wWeEbBhjklHL";
if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
cmd1 = "abcdefghijklmnopqrstuvwxyz";
}
thing = (int) lrand48() % strlen(cmd1); // pick a movement command
c = cmd1[thing];
readbuffer[rbi++] = c; // add movement to input buffer
}
if (strchr("iIaAsc", cm)) { // multi-char commands
if (cm == 'c') {
// change some thing
thing = (int) lrand48() % strlen(cmd1); // pick a movement command
c = cmd1[thing];
readbuffer[rbi++] = c; // add movement to input buffer
}
thing = (int) lrand48() % 4; // what thing to insert
cnt = (int) lrand48() % 10; // how many to insert
for (i = 0; i < cnt; i++) {
if (thing == 0) { // insert chars
readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
} else if (thing == 1) { // insert words
strcat((char *) readbuffer, words[(int) lrand48() % 20]);
strcat((char *) readbuffer, " ");
sleeptime = 0; // how fast to type
} else if (thing == 2) { // insert lines
strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
sleeptime = 0; // how fast to type
} else { // insert multi-lines
strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
sleeptime = 0; // how fast to type
}
}
strcat((char *) readbuffer, "\033");
}
cd1:
totalcmds++;
if (sleeptime > 0)
(void) mysleep(sleeptime); // sleep 1/100 sec
}
// test to see if there are any errors
static void crash_test()
{
static time_t oldtim;
time_t tim;
char d[2], buf[100], msg[BUFSIZ];
msg[0] = '\0';
if (end < text) {
strcat((char *) msg, "end<text ");
}
if (end > textend) {
strcat((char *) msg, "end>textend ");
}
if (dot < text) {
strcat((char *) msg, "dot<text ");
}
if (dot > end) {
strcat((char *) msg, "dot>end ");
}
if (screenbegin < text) {
strcat((char *) msg, "screenbegin<text ");
}
if (screenbegin > end - 1) {
strcat((char *) msg, "screenbegin>end-1 ");
}
if (strlen(msg) > 0) {
alarm(0);
sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
write(1, buf, strlen(buf));
write(1, msg, strlen(msg));
write(1, "\n\n\n", 3);
write(1, "\033[7m[Hit return to continue]\033[0m", 32);
while (read(0, d, 1) > 0) {
if (d[0] == '\n' || d[0] == '\r')
break;
}
alarm(3);
}
tim = (time_t) time((time_t *) 0);
if (tim >= (oldtim + 3)) {
sprintf((char *) status_buffer,
"Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
totalcmds, M, N, I, D, Y, P, U, end - text + 1);
oldtim = tim;
}
return;
}
#endif /* BB_FEATURE_VI_CRASHME */
//---------------------------------------------------------------------
//----- the Ascii Chart -----------------------------------------------
//
// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
//---------------------------------------------------------------------
//----- Execute a Vi Command -----------------------------------
static void do_cmd(Byte c)
{
Byte c1, *p, *q, *msg, buf[9], *save_dot;
int cnt, i, j, dir, yf;
c1 = c; // quiet the compiler
cnt = yf = dir = 0; // quiet the compiler
p = q = save_dot = msg = buf; // quiet the compiler
memset(buf, '\0', 9); // clear buf
if (cmd_mode == 2) {
// we are 'R'eplacing the current *dot with new char
if (*dot == '\n') {
// don't Replace past E-o-l
cmd_mode = 1; // convert to insert
} else {
if (1 <= c && c <= 127) { // only ASCII chars
if (c != 27)
dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
dot = char_insert(dot, c); // insert new char
}
goto dc1;
}
}
if (cmd_mode == 1) {
// insert the char c at "dot"
if (1 <= c && c <= 127) {
dot = char_insert(dot, c); // only ASCII chars
}
goto dc1;
}
switch (c) {
//case 0x01: // soh
//case 0x09: // ht
//case 0x0b: // vt
//case 0x0e: // so
//case 0x0f: // si
//case 0x10: // dle
//case 0x11: // dc1
//case 0x12: // dc2
//case 0x13: // dc3
case 0x14: // dc4 ctrl-T
#ifdef BB_FEATURE_VI_CRASHME
crashme = (crashme == 0) ? 1 : 0;
#endif /* BB_FEATURE_VI_CRASHME */
break;
//case 0x16: // syn
//case 0x17: // etb
//case 0x18: // can
//case 0x1c: // fs
//case 0x1d: // gs
//case 0x1e: // rs
//case 0x1f: // us
//case '!': // !-
//case '#': // #-
//case '&': // &-
//case '(': // (-
//case ')': // )-
//case '*': // *-
//case ',': // ,-
//case '=': // =-
//case '@': // @-
//case 'F': // F-
//case 'G': // G-
//case 'K': // K-
//case 'M': // M-
//case 'Q': // Q-
//case 'S': // S-
//case 'T': // T-
//case 'V': // V-
//case '[': // [-
//case '\\': // \-
//case ']': // ]-
//case '_': // _-
//case '`': // `-
//case 'g': // g-
//case 'm': // m-
//case 't': // t-
//case 'v': // v-
default: // unrecognised command
buf[0] = c;
buf[1] = '\0';
if (c <= ' ') {
buf[0] = '^';
buf[1] = c + '@';
buf[2] = '\0';
}
ni((Byte *) buf);
end_cmd_q(); // stop adding to q
case 0x00: // nul- ignore
break;
case 2: // ctrl-B scroll up full screen
case VI_K_PAGEUP: // Cursor Key Page Up
dot_scroll(rows - 2, -1);
break;
#ifdef BB_FEATURE_VI_USE_SIGNALS
case 0x03: // ctrl-C interrupt
longjmp(restart, 1);
break;
case 26: // ctrl-Z suspend
suspend_sig(SIGTSTP);
break;
#endif /* BB_FEATURE_VI_USE_SIGNALS */
case 4: // ctrl-D scroll down half screen
dot_scroll((rows - 2) / 2, 1);
break;
case 5: // ctrl-E scroll down one line
dot_scroll(1, 1);
break;
case 6: // ctrl-F scroll down full screen
case VI_K_PAGEDOWN: // Cursor Key Page Down
dot_scroll(rows - 2, 1);
break;
case 7: // ctrl-G show current status
edit_status();
break;
case 'h': // h- move left
case VI_K_LEFT: // cursor key Left
case 8: // ^h- move left (This may be ERASE char)
case 127: // DEL- move left (This may be ERASE char)
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_left();
break;
case 10: // Newline ^J
case 'j': // j- goto next line, same col
case VI_K_DOWN: // cursor key Down
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_next(); // go to next B-o-l
dot = move_to_col(dot, ccol + offset); // try stay in same col
break;
case 12: // ctrl-L force redraw whole screen
place_cursor(0, 0); // put cursor in correct place
clear_to_eos(); // tel terminal to erase display
(void) mysleep(10);
screen_erase(); // erase the internal screen buffer
refresh(TRUE); // this will redraw the entire display
break;
case 13: // Carriage Return ^M
case '+': // +- goto next line
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_next();
dot_skip_over_ws();
break;
case 21: // ctrl-U scroll up half screen
dot_scroll((rows - 2) / 2, -1);
break;
case 25: // ctrl-Y scroll up one line
dot_scroll(1, -1);
break;
case 0x1b: // esc
if (cmd_mode == 0)
indicate_error(c);
cmd_mode = 0; // stop insrting
end_cmd_q();
*status_buffer = '\0'; // clear status buffer
break;
case ' ': // move right
case 'l': // move right
case VI_K_RIGHT: // Cursor Key Right
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_right();
break;
#ifdef BB_FEATURE_VI_YANKMARK
case '"': // "- name a register to use for Delete/Yank
c1 = get_one_char();
c1 = tolower(c1);
if (islower(c1)) {
YDreg = c1 - 'a';
} else {
indicate_error(c);
}
break;
case '\'': // '- goto a specific mark
c1 = get_one_char();
c1 = tolower(c1);
if (islower(c1)) {
c1 = c1 - 'a';
// get the b-o-l
q = mark[(int) c1];
if (text <= q && q < end) {
dot = q;
dot_begin(); // go to B-o-l
dot_skip_over_ws();
}
} else if (c1 == '\'') { // goto previous context
dot = swap_context(dot); // swap current and previous context
dot_begin(); // go to B-o-l
dot_skip_over_ws();
} else {
indicate_error(c);
}
break;
case 'm': // m- Mark a line
// this is really stupid. If there are any inserts or deletes
// between text[0] and dot then this mark will not point to the
// correct location! It could be off by many lines!
// Well..., at least its quick and dirty.
c1 = get_one_char();
c1 = tolower(c1);
if (islower(c1)) {
c1 = c1 - 'a';
// remember the line
mark[(int) c1] = dot;
} else {
indicate_error(c);
}
break;
case 'P': // P- Put register before
case 'p': // p- put register after
p = reg[YDreg];
if (p == 0) {
psbs("Nothing in register %c", what_reg());
break;
}
// are we putting whole lines or strings
if (strchr((char *) p, '\n') != NULL) {
if (c == 'P') {
dot_begin(); // putting lines- Put above
}
if (c == 'p') {
// are we putting after very last line?
if (end_line(dot) == (end - 1)) {
dot = end; // force dot to end of text[]
} else {
dot_next(); // next line, then put before
}
}
} else {
if (c == 'p')
dot_right(); // move to right, can move to NL
}
dot = string_insert(dot, p); // insert the string
end_cmd_q(); // stop adding to q
break;
case 'u': // u-
case 'U': // U- Undo; replace current line with original version
if (reg[Ureg] != 0) {
p = begin_line(dot);
q = end_line(dot);
p = text_hole_delete(p, q); // delete cur line
p = string_insert(p, reg[Ureg]); // insert orig line
dot = p;
dot_skip_over_ws();
}
break;
#endif /* BB_FEATURE_VI_YANKMARK */
case '$': // $- goto end of line
case VI_K_END: // Cursor Key End
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot = end_line(dot + 1);
break;
case '%': // %- find matching char of pair () [] {}
for (q = dot; q < end && *q != '\n'; q++) {
if (strchr("()[]{}", *q) != NULL) {
// we found half of a pair
p = find_pair(q, *q);
if (p == NULL) {
indicate_error(c);
} else {
dot = p;
}
break;
}
}
if (*q == '\n')
indicate_error(c);
break;
case 'f': // f- forward to a user specified char
last_forward_char = get_one_char(); // get the search char
//
// dont seperate these two commands. 'f' depends on ';'
//
//**** fall thru to ... 'i'
case ';': // ;- look at rest of line for last forward char
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
q = dot + 1;
while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
q++;
}
if (*q == last_forward_char)
dot = q;
break;
case '-': // -- goto prev line
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_prev();
dot_skip_over_ws();
break;
#ifdef BB_FEATURE_VI_DOT_CMD
case '.': // .- repeat the last modifying command
// Stuff the last_modifying_cmd back into stdin
// and let it be re-executed.
if (last_modifying_cmd != 0) {
ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
}
break;
#endif /* BB_FEATURE_VI_DOT_CMD */
#ifdef BB_FEATURE_VI_SEARCH
case '?': // /- search for a pattern
case '/': // /- search for a pattern
buf[0] = c;
buf[1] = '\0';
q = get_input_line(buf); // get input line- use "status line"
if (strlen((char *) q) == 1)
goto dc3; // if no pat re-use old pat
if (strlen((char *) q) > 1) { // new pat- save it and find
// there is a new pat
if (last_search_pattern != 0) {
free(last_search_pattern);
}
last_search_pattern = (Byte *) strdup((char *) q);
goto dc3; // now find the pattern
}
// user changed mind and erased the "/"- do nothing
break;
case 'N': // N- backward search for last pattern
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dir = BACK; // assume BACKWARD search
p = dot - 1;
if (last_search_pattern[0] == '?') {
dir = FORWARD;
p = dot + 1;
}
goto dc4; // now search for pattern
break;
case 'n': // n- repeat search for last pattern
// search rest of text[] starting at next char
// if search fails return orignal "p" not the "p+1" address
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dc3:
if (last_search_pattern == 0) {
msg = (Byte *) "No previous regular expression";
goto dc2;
}
if (last_search_pattern[0] == '/') {
dir = FORWARD; // assume FORWARD search
p = dot + 1;
}
if (last_search_pattern[0] == '?') {
dir = BACK;
p = dot - 1;
}
dc4:
q = char_search(p, last_search_pattern + 1, dir, FULL);
if (q != NULL) {
dot = q; // good search, update "dot"
msg = (Byte *) "";
goto dc2;
}
// no pattern found between "dot" and "end"- continue at top
p = text;
if (dir == BACK) {
p = end - 1;
}
q = char_search(p, last_search_pattern + 1, dir, FULL);
if (q != NULL) { // found something
dot = q; // found new pattern- goto it
msg = (Byte *) "search hit BOTTOM, continuing at TOP";
if (dir == BACK) {
msg = (Byte *) "search hit TOP, continuing at BOTTOM";
}
} else {
msg = (Byte *) "Pattern not found";
}
dc2:
psbs("%s", msg);
break;
case '{': // {- move backward paragraph
q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
if (q != NULL) { // found blank line
dot = next_line(q); // move to next blank line
}
break;
case '}': // }- move forward paragraph
q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
if (q != NULL) { // found blank line
dot = next_line(q); // move to next blank line
}
break;
#endif /* BB_FEATURE_VI_SEARCH */
case '0': // 0- goto begining of line
case '1': // 1-
case '2': // 2-
case '3': // 3-
case '4': // 4-
case '5': // 5-
case '6': // 6-
case '7': // 7-
case '8': // 8-
case '9': // 9-
if (c == '0' && cmdcnt < 1) {
dot_begin(); // this was a standalone zero
} else {
cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
}
break;
case ':': // :- the colon mode commands
#ifdef BB_FEATURE_VI_COLON
p = get_input_line((Byte *) ":"); // get input line- use "status line"
colon(p); // execute the command
#else /* BB_FEATURE_VI_COLON */
*status_buffer = '\0'; // clear the status buffer
place_cursor(rows - 1, 0); // go to Status line, bottom of screen
clear_to_eol(); // clear the line
write(1, ":", 1); // write out the : prompt
for (cnt = 0; cnt < 8; cnt++) {
c1 = get_one_char();
if (c1 == '\n' || c1 == '\r') {
break;
}
buf[cnt] = c1;
buf[cnt + 1] = '\0';
write(1, buf + cnt, 1); // echo the char
}
cnt = strlen((char *) buf);
if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
if (file_modified == TRUE && buf[1] != '!') {
psbs("No write since last change (:quit! overrides)");
} else {
editing = 0;
}
} else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
strncasecmp((char *) buf, "wq", cnt) == 0) {
cnt = file_write(cfn, text, end - 1);
file_modified = FALSE;
psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
if (buf[1] == 'q') {
editing = 0;
}
} else { // unrecognised cmd
ni((Byte *) buf);
}
#endif /* BB_FEATURE_VI_COLON */
break;
case '<': // <- Left shift something
case '>': // >- Right shift something
cnt = count_lines(text, dot); // remember what line we are on
c1 = get_one_char(); // get the type of thing to delete
find_range(&p, &q, c1);
(void) yank_delete(p, q, 1, YANKONLY); // save copy before change
p = begin_line(p);
q = end_line(q);
i = count_lines(p, q); // # of lines we are shifting
for ( ; i > 0; i--, p = next_line(p)) {
if (c == '<') {
// shift left- remove tab or 8 spaces
if (*p == '\t') {
// shrink buffer 1 char
(void) text_hole_delete(p, p);
} else if (*p == ' ') {
// we should be calculating columns, not just SPACE
for (j = 0; *p == ' ' && j < tabstop; j++) {
(void) text_hole_delete(p, p);
}
}
} else if (c == '>') {
// shift right -- add tab or 8 spaces
(void) char_insert(p, '\t');
}
}
dot = find_line(cnt); // what line were we on
dot_skip_over_ws();
end_cmd_q(); // stop adding to q
break;
case 'A': // A- append at e-o-l
dot_end(); // go to e-o-l
//**** fall thru to ... 'a'
case 'a': // a- append after current char
if (*dot != '\n')
dot++;
goto dc_i;
break;
case 'B': // B- back a blank-delimited Word
case 'E': // E- end of a blank-delimited word
case 'W': // W- forward a blank-delimited word
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dir = FORWARD;
if (c == 'B')
dir = BACK;
if (c == 'W' || isspace(dot[dir])) {
dot = skip_thing(dot, 1, dir, S_TO_WS);
dot = skip_thing(dot, 2, dir, S_OVER_WS);
}
if (c != 'W')
dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
break;
case 'C': // C- Change to e-o-l
case 'D': // D- delete to e-o-l
save_dot = dot;
dot = dollar_line(dot); // move to before NL
// copy text into a register and delete
dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
if (c == 'C')
goto dc_i; // start inserting
#ifdef BB_FEATURE_VI_DOT_CMD
if (c == 'D')
end_cmd_q(); // stop adding to q
#endif /* BB_FEATURE_VI_DOT_CMD */
break;
case 'H': // H- goto top line on screen
dot = screenbegin;
if (cmdcnt > (rows - 1)) {
cmdcnt = (rows - 1);
}
if (cmdcnt-- > 1) {
do_cmd('+');
} // repeat cnt
dot_skip_over_ws();
break;
case 'I': // I- insert before first non-blank
dot_begin(); // 0
dot_skip_over_ws();
//**** fall thru to ... 'i'
case 'i': // i- insert before current char
case VI_K_INSERT: // Cursor Key Insert
dc_i:
cmd_mode = 1; // start insrting
psb("-- Insert --");
break;
case 'J': // J- join current and next lines together
if (cmdcnt-- > 2) {
do_cmd(c);
} // repeat cnt
dot_end(); // move to NL
if (dot < end - 1) { // make sure not last char in text[]
*dot++ = ' '; // replace NL with space
while (isblnk(*dot)) { // delete leading WS
dot_delete();
}
}
end_cmd_q(); // stop adding to q
break;
case 'L': // L- goto bottom line on screen
dot = end_screen();
if (cmdcnt > (rows - 1)) {
cmdcnt = (rows - 1);
}
if (cmdcnt-- > 1) {
do_cmd('-');
} // repeat cnt
dot_begin();
dot_skip_over_ws();
break;
case 'O': // O- open a empty line above
// 0i\n\033-i
p = begin_line(dot);
if (p[-1] == '\n') {
dot_prev();
case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
dot_end();
dot = char_insert(dot, '\n');
} else {
dot_begin(); // 0
dot = char_insert(dot, '\n'); // i\n\033
dot_prev(); // -
}
goto dc_i;
break;
case 'R': // R- continuous Replace char
cmd_mode = 2;
psb("-- Replace --");
break;
case 'X': // X- delete char before dot
case 'x': // x- delete the current char
case 's': // s- substitute the current char
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dir = 0;
if (c == 'X')
dir = -1;
if (dot[dir] != '\n') {
if (c == 'X')
dot--; // delete prev char
dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
}
if (c == 's')
goto dc_i; // start insrting
end_cmd_q(); // stop adding to q
break;
case 'Z': // Z- if modified, {write}; exit
// ZZ means to save file (if necessary), then exit
c1 = get_one_char();
if (c1 != 'Z') {
indicate_error(c);
break;
}
if (file_modified == TRUE
#ifdef BB_FEATURE_VI_READONLY
&& readonly == FALSE
#endif /* BB_FEATURE_VI_READONLY */
) {
cnt = file_write(cfn, text, end - 1);
if (cnt == (end - 1 - text + 1)) {
editing = 0;
}
} else {
editing = 0;
}
break;
case '^': // ^- move to first non-blank on line
dot_begin();
dot_skip_over_ws();
break;
case 'b': // b- back a word
case 'e': // e- end of word
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dir = FORWARD;
if (c == 'b')
dir = BACK;
if ((dot + dir) < text || (dot + dir) > end - 1)
break;
dot += dir;
if (isspace(*dot)) {
dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
}
if (isalnum(*dot) || *dot == '_') {
dot = skip_thing(dot, 1, dir, S_END_ALNUM);
} else if (ispunct(*dot)) {
dot = skip_thing(dot, 1, dir, S_END_PUNCT);
}
break;
case 'c': // c- change something
case 'd': // d- delete something
#ifdef BB_FEATURE_VI_YANKMARK
case 'y': // y- yank something
case 'Y': // Y- Yank a line
#endif /* BB_FEATURE_VI_YANKMARK */
yf = YANKDEL; // assume either "c" or "d"
#ifdef BB_FEATURE_VI_YANKMARK
if (c == 'y' || c == 'Y')
yf = YANKONLY;
#endif /* BB_FEATURE_VI_YANKMARK */
c1 = 'y';
if (c != 'Y')
c1 = get_one_char(); // get the type of thing to delete
find_range(&p, &q, c1);
if (c1 == 27) { // ESC- user changed mind and wants out
c = c1 = 27; // Escape- do nothing
} else if (strchr("wW", c1)) {
if (c == 'c') {
// don't include trailing WS as part of word
while (isblnk(*q)) {
if (q <= text || q[-1] == '\n')
break;
q--;
}
}
dot = yank_delete(p, q, 0, yf); // delete word
} else if (strchr("0bBeE$", c1)) {
// single line copy text into a register and delete
dot = yank_delete(p, q, 0, yf); // delete word
} else if (strchr("cdykjHL+-{}\r\n", c1)) {
// multiple line copy text into a register and delete
dot = yank_delete(p, q, 1, yf); // delete lines
if (c == 'd') {
dot_begin();
dot_skip_over_ws();
}
} else {
// could not recognize object
c = c1 = 27; // error-
indicate_error(c);
}
if (c1 != 27) {
// if CHANGING, not deleting, start inserting after the delete
if (c == 'c') {
strcpy((char *) buf, "Change");
goto dc_i; // start inserting
}
if (c == 'd') {
strcpy((char *) buf, "Delete");
}
#ifdef BB_FEATURE_VI_YANKMARK
if (c == 'y' || c == 'Y') {
strcpy((char *) buf, "Yank");
}
p = reg[YDreg];
q = p + strlen((char *) p);
for (cnt = 0; p <= q; p++) {
if (*p == '\n')
cnt++;
}
psb("%s %d lines (%d chars) using [%c]",
buf, cnt, strlen((char *) reg[YDreg]), what_reg());
#endif /* BB_FEATURE_VI_YANKMARK */
end_cmd_q(); // stop adding to q
}
break;
case 'k': // k- goto prev line, same col
case VI_K_UP: // cursor key Up
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
dot_prev();
dot = move_to_col(dot, ccol + offset); // try stay in same col
break;
case 'r': // r- replace the current char with user input
c1 = get_one_char(); // get the replacement char
if (*dot != '\n') {
*dot = c1;
file_modified = TRUE; // has the file been modified
}
end_cmd_q(); // stop adding to q
break;
case 'w': // w- forward a word
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
} else if (ispunct(*dot)) { // we are on PUNCT
dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
}
if (dot < end - 1)
dot++; // move over word
if (isspace(*dot)) {
dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
}
break;
case 'z': // z-
c1 = get_one_char(); // get the replacement char
cnt = 0;
if (c1 == '.')
cnt = (rows - 2) / 2; // put dot at center
if (c1 == '-')
cnt = rows - 2; // put dot at bottom
screenbegin = begin_line(dot); // start dot at top
dot_scroll(cnt, -1);
break;
case '|': // |- move to column "cmdcnt"
dot = move_to_col(dot, cmdcnt - 1); // try to move to column
break;
case '~': // ~- flip the case of letters a-z -> A-Z
if (cmdcnt-- > 1) {
do_cmd(c);
} // repeat cnt
if (islower(*dot)) {
*dot = toupper(*dot);
file_modified = TRUE; // has the file been modified
} else if (isupper(*dot)) {
*dot = tolower(*dot);
file_modified = TRUE; // has the file been modified
}
dot_right();
end_cmd_q(); // stop adding to q
break;
//----- The Cursor and Function Keys -----------------------------
case VI_K_HOME: // Cursor Key Home
dot_begin();
break;
// The Fn keys could point to do_macro which could translate them
case VI_K_FUN1: // Function Key F1
case VI_K_FUN2: // Function Key F2
case VI_K_FUN3: // Function Key F3
case VI_K_FUN4: // Function Key F4
case VI_K_FUN5: // Function Key F5
case VI_K_FUN6: // Function Key F6
case VI_K_FUN7: // Function Key F7
case VI_K_FUN8: // Function Key F8
case VI_K_FUN9: // Function Key F9
case VI_K_FUN10: // Function Key F10
case VI_K_FUN11: // Function Key F11
case VI_K_FUN12: // Function Key F12
break;
}
dc1:
// if text[] just became empty, add back an empty line
if (end == text) {
(void) char_insert(text, '\n'); // start empty buf with dummy line
dot = text;
}
// it is OK for dot to exactly equal to end, otherwise check dot validity
if (dot != end) {
dot = bound_dot(dot); // make sure "dot" is valid
}
#ifdef BB_FEATURE_VI_YANKMARK
check_context(c); // update the current context
#endif /* BB_FEATURE_VI_YANKMARK */
if (!isdigit(c))
cmdcnt = 0; // cmd was not a number, reset cmdcnt
cnt = dot - begin_line(dot);
// Try to stay off of the Newline
if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
dot--;
}
//----- The Colon commands -------------------------------------
#ifdef BB_FEATURE_VI_COLON
static Byte *get_address(Byte * p, int *addr) // get colon addr, if present
{
int st;
Byte *q;
#ifdef BB_FEATURE_VI_YANKMARK
Byte c;
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_SEARCH
Byte *pat, buf[1024];
#endif /* BB_FEATURE_VI_SEARCH */
*addr = -1; // assume no addr
if (*p == '.') { // the current line
p++;
q = begin_line(dot);
*addr = count_lines(text, q);
#ifdef BB_FEATURE_VI_YANKMARK
} else if (*p == '\'') { // is this a mark addr
p++;
c = tolower(*p);
p++;
if (c >= 'a' && c <= 'z') {
// we have a mark
c = c - 'a';
q = mark[(int) c];
if (q != NULL) { // is mark valid
*addr = count_lines(text, q); // count lines
}
}
#endif /* BB_FEATURE_VI_YANKMARK */
#ifdef BB_FEATURE_VI_SEARCH
} else if (*p == '/') { // a search pattern
q = buf;
for (p++; *p; p++) {
if (*p == '/')
break;
*q++ = *p;
*q = '\0';
}
pat = (Byte *) strdup((char *) buf); // save copy of pattern
if (*p == '/')
p++;
q = char_search(dot, pat, FORWARD, FULL);
if (q != NULL) {
*addr = count_lines(text, q);
}
free(pat);
#endif /* BB_FEATURE_VI_SEARCH */
} else if (*p == '$') { // the last line in file
p++;
q = begin_line(end - 1);
*addr = count_lines(text, q);
} else if (isdigit(*p)) { // specific line number
sscanf((char *) p, "%d%n", addr, &st);
p += st;
} else { // I don't reconise this
// unrecognised address- assume -1
*addr = -1;
}
return (p);
}
static void colon(Byte * buf)
{
Byte c, *orig_buf, *buf1, *q, *r;
Byte *fn, cmd[100], args[100];
int i, l, li, ch, st, b, e;
int useforce, forced;
// :3154 // if (-e line 3154) goto it else stay put
// :4,33w! foo // write a portion of buffer to file "foo"
// :w // write all of buffer to current file
// :q // quit
// :q! // quit- dont care about modified file
// :'a,'z!sort -u // filter block through sort
// :'f // goto mark "f"
// :'fl // list literal the mark "f" line
// :.r bar // read file "bar" into buffer before dot
// :/123/,/abc/d // delete lines from "123" line to "abc" line
// :/xyz/ // goto the "xyz" line
// :s/find/replace/ // substitute pattern "find" with "replace"
//
if (strlen((char *) buf) <= 0)
goto vc1;
if (*buf == ':')
buf++; // move past the ':'
forced = useforce = FALSE;
li = st = ch = i = 0;
b = e = -1;
q = text; // assume 1,$ for the range
r = end - 1;
li = count_lines(text, end - 1);
fn = cfn; // default to current file
// look for optional FIRST address(es) :. :1 :1,9 :'q,'a
while (isblnk(*buf))
buf++;
// get FIRST addr, if present
buf = get_address(buf, &b);
while (isblnk(*buf))
buf++;
if (*buf == ',') {
buf++;
while (isblnk(*buf))
buf++;
// look for SECOND address
buf = get_address(buf, &e);
}
while (isblnk(*buf))
buf++;
// remember orig command line
orig_buf = buf;
// get the COMMAND into cmd[]
buf1 = cmd;
*buf1 = '\0';
while (*buf != '\0') {
if (isspace(*buf))
break;
*buf1++ = *buf++;
*buf1 = '\0';
}
// get any ARGuments
while (isblnk(*buf))
buf++;
strcpy((char *) args, (char *) buf);
if (cmd[strlen((char *) cmd) - 1] == '!') {
useforce = TRUE;
cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
}
if (b >= 0) {
// if there is only one addr, then the addr
// is the line number of the single line the
// user wants. So, reset the end
// pointer to point at end of the "b" line
q = find_line(b); // what line is #b
r = end_line(q);
li = 1;
}
if (e >= 0) {
// we were given two addrs. change the
// end pointer to the addr given by user.
r = find_line(e); // what line is #e
r = end_line(r);
li = e - b + 1;
}
// ------------ now look for the command ------------
i = strlen((char *) cmd);
if (i == 0) { // :123CR goto line #123
if (b >= 0) {
dot = find_line(b); // what line is #b
dot_skip_over_ws();
}
} else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
if (b < 0) { // no addr given- use defaults
b = e = count_lines(text, dot);
}
psb("%d", b);
} else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
if (b < 0) { // no addr given- use defaults
q = begin_line(dot); // assume .,. for the range
r = end_line(dot);
}
dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
dot_skip_over_ws();
} else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
// don't exit if the file been modified
if (file_modified == TRUE && useforce != TRUE) {
psbs("No write since last change (:edit! overrides)");
goto vc1;
}
fn = args;
if (strlen((char *) fn) <= 0) {
// no file name given, re-edit current file
fn = cfn;
}
if (cfn != 0)
free(cfn);
cfn = (Byte *) strdup((char *) fn); // make this the current file
// delete all the contents of text[]
new_text(2 * file_size(fn));
screenbegin = dot = end = text;
// insert new file
if (fn != 0) {
ch = file_insert(fn, text, file_size(fn));
}
file_modified = FALSE;
#ifdef BB_FEATURE_VI_YANKMARK
if (reg[Ureg] != 0)
free(reg[Ureg]); // free orig line reg- for 'U'
if (reg[YDreg] != 0)
free(reg[YDreg]); // free default yank/delete register
for (li = 0; li < 28; li++) {
mark[li] = 0;
} // init the marks
#endif /* BB_FEATURE_VI_YANKMARK */
// how many lines in text[]?
li = count_lines(text, end - 1);
psb("\"%s\" %dL, %dC", cfn, li, ch);
} else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
if (b != -1 || e != -1) {
ni((Byte *) "No address allowed on this command");
goto vc1;
}
if (strlen((char *) args) > 0) {
// user wants a new filename
if (cfn != NULL)
free(cfn);
cfn = (Byte *) strdup((char *) args);
} else {
// user wants file status info
edit_status();
}
} else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
// print out values of all features
place_cursor(rows - 1, 0); // go to Status line, bottom of screen
clear_to_eol(); // clear the line
cookmode();
show_help();
rawmode();
Hit_Return();
} else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
if (b < 0) { // no addr given- use defaults
q = begin_line(dot); // assume .,. for the range
r = end_line(dot);
}
place_cursor(rows - 1, 0); // go to Status line, bottom of screen
clear_to_eol(); // clear the line
write(1, "\r\n", 2);
for (; q <= r; q++) {
c = *q;
if (c > '~')
standout_start();
if (c == '\n') {
write(1, "$\r", 2);
} else if (*q < ' ') {
write(1, "^", 1);
c += '@';
}
write(1, &c, 1);
if (c > '~')
standout_end();
}
#ifdef BB_FEATURE_VI_SET
vc2:
#endif /* BB_FEATURE_VI_SET */
Hit_Return();
} else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
(strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
if (useforce == TRUE) {
// force end of argv list
if (*cmd == 'q') {
optind = save_argc;
}
editing = 0;
goto vc1;
}
// don't exit if the file been modified
if (file_modified == TRUE) {
psbs("No write since last change (:%s! overrides)",
(*cmd == 'q' ? "quit" : "next"));
goto vc1;
}
// are there other file to edit
if (*cmd == 'q' && optind < save_argc - 1) {
psbs("%d more file to edit", (save_argc - optind - 1));
goto vc1;
}
if (*cmd == 'n' && optind >= save_argc - 1) {
psbs("No more files to edit");
goto vc1;
}
editing = 0;
} else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
fn = args;
if (strlen((char *) fn) <= 0) {
psbs("No filename given");
goto vc1;
}
if (b < 0) { // no addr given- use defaults
q = begin_line(dot); // assume "dot"
}
// read after current line- unless user said ":0r foo"
if (b != 0)
q = next_line(q);
ch = file_insert(fn, q, file_size(fn));
if (ch < 0)
goto vc1; // nothing was inserted
// how many lines in text[]?
li = count_lines(q, q + ch - 1);
psb("\"%s\" %dL, %dC", fn, li, ch);
if (ch > 0) {
// if the insert is before "dot" then we need to update
if (q <= dot)
dot += ch;
file_modified = TRUE;
}
} else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
if (file_modified == TRUE && useforce != TRUE) {
psbs("No write since last change (:rewind! overrides)");
} else {
// reset the filenames to edit
optind = fn_start - 1;
editing = 0;
}
#ifdef BB_FEATURE_VI_SET
} else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
i = 0; // offset into args
if (strlen((char *) args) == 0) {
// print out values of all options
place_cursor(rows - 1, 0); // go to Status line, bottom of screen
clear_to_eol(); // clear the line
printf("----------------------------------------\r\n");
#ifdef BB_FEATURE_VI_SETOPTS
if (!autoindent)
printf("no");
printf("autoindent ");
if (!ignorecase)
printf("no");
printf("ignorecase ");
if (!showmatch)
printf("no");
printf("showmatch ");
printf("tabstop=%d ", tabstop);
#endif /* BB_FEATURE_VI_SETOPTS */
printf("\r\n");
goto vc2;
}
if (strncasecmp((char *) args, "no", 2) == 0)
i = 2; // ":set noautoindent"
#ifdef BB_FEATURE_VI_SETOPTS
if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
strncasecmp((char *) args + i, "ai", 2) == 0) {
autoindent = (i == 2) ? 0 : 1;
}
if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
strncasecmp((char *) args + i, "ic", 2) == 0) {
ignorecase = (i == 2) ? 0 : 1;
}
if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
strncasecmp((char *) args + i, "sm", 2) == 0) {
showmatch = (i == 2) ? 0 : 1;
}
if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
sscanf(strchr((char *) args + i, '='), "=%d", &ch);
if (ch > 0 && ch < columns - 1)
tabstop = ch;
}
#endif /* BB_FEATURE_VI_SETOPTS */
#endif /* BB_FEATURE_VI_SET */
#ifdef BB_FEATURE_VI_SEARCH
} else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
Byte *ls, *F, *R;
int gflag;
// F points to the "find" pattern
// R points to the "replace" pattern
// replace the cmd line delimiters "/" with NULLs
gflag = 0; // global replace flag
c = orig_buf[1]; // what is the delimiter
F = orig_buf + 2; // start of "find"
R = (Byte *) strchr((char *) F, c); // middle delimiter
*R++ = '\0'; // terminate "find"
buf1 = (Byte *) strchr((char *) R, c);
*buf1++ = '\0'; // terminate "replace"
if (*buf1 == 'g') { // :s/foo/bar/g
buf1++;
gflag++; // turn on gflag
}
q = begin_line(q);
if (b < 0) { // maybe :s/foo/bar/
q = begin_line(dot); // start with cur line
b = count_lines(text, q); // cur line number
}
if (e < 0)
e = b; // maybe :.s/foo/bar/
for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
ls = q; // orig line start
vc4:
buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
if (buf1 != NULL) {
// we found the "find" pattern- delete it
(void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
// inset the "replace" patern
(void) string_insert(buf1, R); // insert the string
// check for "global" :s/foo/bar/g
if (gflag == 1) {
if ((buf1 + strlen((char *) R)) < end_line(ls)) {
q = buf1 + strlen((char *) R);
goto vc4; // don't let q move past cur line
}
}
}
q = next_line(ls);
}
#endif /* BB_FEATURE_VI_SEARCH */
} else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
psb("%s", vi_Version);
} else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
(strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
// is there a file name to write to?
if (strlen((char *) args) > 0) {
fn = args;
}
#ifdef BB_FEATURE_VI_READONLY
if (readonly == TRUE) {
psbs("\"%s\" File is read only", fn);
goto vc3;
}
#endif /* BB_FEATURE_VI_READONLY */
// how many lines in text[]?
li = count_lines(q, r);
ch = r - q + 1;
if (useforce == TRUE) {
// if "fn" is not write-able, chmod u+w
// sprintf(syscmd, "chmod u+w %s", fn);
// system(syscmd);
forced = TRUE;
}
l = file_write(fn, q, r);
if (useforce == TRUE && forced == TRUE) {
// chmod u-w
// sprintf(syscmd, "chmod u-w %s", fn);
// system(syscmd);
forced = FALSE;
}
psb("\"%s\" %dL, %dC", fn, li, l);
if (q == text && r == end - 1 && l == ch)
file_modified = FALSE;
if (cmd[1] == 'q' && l == ch) {
editing = 0;
}
#ifdef BB_FEATURE_VI_READONLY
vc3:;
#endif /* BB_FEATURE_VI_READONLY */
#ifdef BB_FEATURE_VI_YANKMARK
} else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
if (b < 0) { // no addr given- use defaults
q = begin_line(dot); // assume .,. for the range
r = end_line(dot);
}
text_yank(q, r, YDreg);
li = count_lines(q, r);
psb("Yank %d lines (%d chars) into [%c]",
li, strlen((char *) reg[YDreg]), what_reg());
#endif /* BB_FEATURE_VI_YANKMARK */
} else {
// cmd unknown
ni((Byte *) cmd);
}
vc1:
dot = bound_dot(dot); // make sure "dot" is valid
return;
}
static void Hit_Return(void)
{
char c;
standout_start(); // start reverse video
write(1, "[Hit return to continue]", 24);
standout_end(); // end reverse video
while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
;
redraw(TRUE); // force redraw all
}
#endif /* BB_FEATURE_VI_COLON */
//----- Synchronize the cursor to Dot --------------------------
static void sync_cursor(Byte * d, int *row, int *col)
{
Byte *beg_cur, *end_cur; // begin and end of "d" line
Byte *beg_scr, *end_scr; // begin and end of screen
Byte *tp;
int cnt, ro, co;
beg_cur = begin_line(d); // first char of cur line
end_cur = end_line(d); // last char of cur line
beg_scr = end_scr = screenbegin; // first char of screen
end_scr = end_screen(); // last char of screen
if (beg_cur < screenbegin) {
// "d" is before top line on screen
// how many lines do we have to move
cnt = count_lines(beg_cur, screenbegin);
sc1:
screenbegin = beg_cur;
if (cnt > (rows - 1) / 2) {
// we moved too many lines. put "dot" in middle of screen
for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
screenbegin = prev_line(screenbegin);
}
}
} else if (beg_cur > end_scr) {
// "d" is after bottom line on screen
// how many lines do we have to move
cnt = count_lines(end_scr, beg_cur);
if (cnt > (rows - 1) / 2)
goto sc1; // too many lines
for (ro = 0; ro < cnt - 1; ro++) {
// move screen begin the same amount
screenbegin = next_line(screenbegin);
// now, move the end of screen
end_scr = next_line(end_scr);
end_scr = end_line(end_scr);
}
}
// "d" is on screen- find out which row
tp = screenbegin;
for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
if (tp == beg_cur)
break;
tp = next_line(tp);
}
// find out what col "d" is on
co = 0;
do { // drive "co" to correct column
if (*tp == '\n' || *tp == '\0')
break;
if (*tp == '\t') {
// 7 - (co % 8 )
co += ((tabstop - 1) - (co % tabstop));
} else if (*tp < ' ') {
co++; // display as ^X, use 2 columns
}
} while (tp++ < d && ++co);
// "co" is the column where "dot" is.
// The screen has "columns" columns.
// The currently displayed columns are 0+offset -- columns+ofset
// |-------------------------------------------------------------|
// ^ ^ ^
// offset | |------- columns ----------------|
//
// If "co" is already in this range then we do not have to adjust offset
// but, we do have to subtract the "offset" bias from "co".
// If "co" is outside this range then we have to change "offset".
// If the first char of a line is a tab the cursor will try to stay
// in column 7, but we have to set offset to 0.
if (co < 0 + offset) {
offset = co;
}
if (co >= columns + offset) {
offset = co - columns + 1;
}
// if the first char of the line is a tab, and "dot" is sitting on it
// force offset to 0.
if (d == beg_cur && *d == '\t') {
offset = 0;
}
co -= offset;
*row = ro;
*col = co;
}
//----- Text Movement Routines ---------------------------------
static Byte *begin_line(Byte * p) // return pointer to first char cur line
{
while (p > text && p[-1] != '\n')
p--; // go to cur line B-o-l
return (p);
}
static Byte *end_line(Byte * p) // return pointer to NL of cur line line
{
while (p < end - 1 && *p != '\n')
p++; // go to cur line E-o-l
return (p);
}
static Byte *dollar_line(Byte * p) // return pointer to just before NL line
{
while (p < end - 1 && *p != '\n')
p++; // go to cur line E-o-l
// Try to stay off of the Newline
if (*p == '\n' && (p - begin_line(p)) > 0)
p--;
return (p);
}
static Byte *prev_line(Byte * p) // return pointer first char prev line
{
p = begin_line(p); // goto begining of cur line
if (p[-1] == '\n' && p > text)
p--; // step to prev line
p = begin_line(p); // goto begining of prev line
return (p);
}
static Byte *next_line(Byte * p) // return pointer first char next line
{
p = end_line(p);
if (*p == '\n' && p < end - 1)
p++; // step to next line
return (p);
}
//----- Text Information Routines ------------------------------
static Byte *end_screen(void)
{
Byte *q;
int cnt;
// find new bottom line
q = screenbegin;
for (cnt = 0; cnt < rows - 2; cnt++)
q = next_line(q);
q = end_line(q);
return (q);
}
static int count_lines(Byte * start, Byte * stop) // count line from start to stop
{
Byte *q;
int cnt;
if (stop < start) { // start and stop are backwards- reverse them
q = start;
start = stop;
stop = q;
}
cnt = 0;
stop = end_line(stop); // get to end of this line
for (q = start; q <= stop && q <= end - 1; q++) {
if (*q == '\n')
cnt++;
}
return (cnt);
}
static Byte *find_line(int li) // find begining of line #li
{
Byte *q;
for (q = text; li > 1; li--) {
q = next_line(q);
}
return (q);
}
//----- Dot Movement Routines ----------------------------------
static void dot_left(void)
{
if (dot > text && dot[-1] != '\n')
dot--;
}
static void dot_right(void)
{
if (dot < end - 1 && *dot != '\n')
dot++;
}
static void dot_begin(void)
{
dot = begin_line(dot); // return pointer to first char cur line
}
static void dot_end(void)
{
dot = end_line(dot); // return pointer to last char cur line
}
static Byte *move_to_col(Byte * p, int l)
{
int co;
p = begin_line(p);
co = 0;
do {
if (*p == '\n' || *p == '\0')
break;
if (*p == '\t') {
// 7 - (co % 8 )
co += ((tabstop - 1) - (co % tabstop));
} else if (*p < ' ') {
co++; // display as ^X, use 2 columns
}
} while (++co <= l && p++ < end);
return (p);
}
static void dot_next(void)
{
dot = next_line(dot);
}
static void dot_prev(void)
{
dot = prev_line(dot);
}
static void dot_scroll(int cnt, int dir)
{
Byte *q;
for (; cnt > 0; cnt--) {
if (dir < 0) {
// scroll Backwards
// ctrl-Y scroll up one line
screenbegin = prev_line(screenbegin);
} else {
// scroll Forwards
// ctrl-E scroll down one line
screenbegin = next_line(screenbegin);
}
}
// make sure "dot" stays on the screen so we dont scroll off
if (dot < screenbegin)
dot = screenbegin;
q = end_screen(); // find new bottom line
if (dot > q)
dot = begin_line(q); // is dot is below bottom line?
dot_skip_over_ws();
}
static void dot_skip_over_ws(void)
{
// skip WS
while (isspace(*dot) && *dot != '\n' && dot < end - 1)
dot++;
}
static void dot_delete(void) // delete the char at 'dot'
{
(void) text_hole_delete(dot, dot);
}
static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
{
if (p >= end && end > text) {
p = end - 1;
indicate_error('1');
}
if (p < text) {
p = text;
indicate_error('2');
}
return (p);
}
//----- Helper Utility Routines --------------------------------
//----------------------------------------------------------------
//----- Char Routines --------------------------------------------
/* Chars that are part of a word-
* 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
* Chars that are Not part of a word (stoppers)
* !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
* Chars that are WhiteSpace
* TAB NEWLINE VT FF RETURN SPACE
* DO NOT COUNT NEWLINE AS WHITESPACE
*/
static Byte *new_screen(int ro, int co)
{
if (screen != 0)
free(screen);
screensize = ro * co + 8;
screen = (Byte *) malloc(screensize);
return (screen);
}
static Byte *new_text(int size)
{
if (size < 10240)
size = 10240; // have a minimum size for new files
if (text != 0) {
//text -= 4;
free(text);
}
text = (Byte *) malloc(size + 8);
memset(text, '\0', size); // clear new text[]
//text += 4; // leave some room for "oops"
textend = text + size - 1;
//textend -= 4; // leave some root for "oops"
return (text);
}
#ifdef BB_FEATURE_VI_SEARCH
static int mycmp(Byte * s1, Byte * s2, int len)
{
int i;
i = strncmp((char *) s1, (char *) s2, len);
#ifdef BB_FEATURE_VI_SETOPTS
if (ignorecase) {
i = strncasecmp((char *) s1, (char *) s2, len);
}
#endif /* BB_FEATURE_VI_SETOPTS */
return (i);
}
static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
{
#ifndef REGEX_SEARCH
Byte *start, *stop;
int len;
len = strlen((char *) pat);
if (dir == FORWARD) {
stop = end - 1; // assume range is p - end-1
if (range == LIMITED)
stop = next_line(p); // range is to next line
for (start = p; start < stop; start++) {
if (mycmp(start, pat, len) == 0) {
return (start);
}
}
} else if (dir == BACK) {
stop = text; // assume range is text - p
if (range == LIMITED)
stop = prev_line(p); // range is to prev line
for (start = p - len; start >= stop; start--) {
if (mycmp(start, pat, len) == 0) {
return (start);
}
}
}
// pattern not found
return (NULL);
#else /*REGEX_SEARCH */
char *q;
struct re_pattern_buffer preg;
int i;
int size, range;
re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
preg.translate = 0;
preg.fastmap = 0;
preg.buffer = 0;
preg.allocated = 0;
// assume a LIMITED forward search
q = next_line(p);
q = end_line(q);
q = end - 1;
if (dir == BACK) {
q = prev_line(p);
q = text;
}
// count the number of chars to search over, forward or backward
size = q - p;
if (size < 0)
size = p - q;
// RANGE could be negative if we are searching backwards
range = q - p;
q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
if (q != 0) {
// The pattern was not compiled
psbs("bad search pattern: \"%s\": %s", pat, q);
i = 0; // return p if pattern not compiled
goto cs1;
}
q = p;
if (range < 0) {
q = p - size;
if (q < text)
q = text;
}
// search for the compiled pattern, preg, in p[]
// range < 0- search backward
// range > 0- search forward
// 0 < start < size
// re_search() < 0 not found or error
// re_search() > 0 index of found pattern
// struct pattern char int int int struct reg
// re_search (*pattern_buffer, *string, size, start, range, *regs)
i = re_search(&preg, q, size, 0, range, 0);
if (i == -1) {
p = 0;
i = 0; // return NULL if pattern not found
}
cs1:
if (dir == FORWARD) {
p = p + i;
} else {
p = p - i;
}
return (p);
#endif /*REGEX_SEARCH */
}
#endif /* BB_FEATURE_VI_SEARCH */
static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
{
if (c == 22) { // Is this an ctrl-V?
p = stupid_insert(p, '^'); // use ^ to indicate literal next
p--; // backup onto ^
refresh(FALSE); // show the ^
c = get_one_char();
*p = c;
p++;
file_modified = TRUE; // has the file been modified
} else if (c == 27) { // Is this an ESC?
cmd_mode = 0;
cmdcnt = 0;
end_cmd_q(); // stop adding to q
*status_buffer = '\0'; // clear the status buffer
if (p[-1] != '\n') {
p--;
}
} else if (c == erase_char) { // Is this a BS
// 123456789
if (p[-1] != '\n') {
p--;
p = text_hole_delete(p, p); // shrink buffer 1 char
#ifdef BB_FEATURE_VI_DOT_CMD
// also rmove char from last_modifying_cmd
if (strlen((char *) last_modifying_cmd) > 0) {
Byte *q;
q = last_modifying_cmd;
q[strlen((char *) q) - 1] = '\0'; // erase BS
q[strlen((char *) q) - 1] = '\0'; // erase prev char
}
#endif /* BB_FEATURE_VI_DOT_CMD */
}
} else {
// insert a char into text[]
Byte *sp; // "save p"
if (c == 13)
c = '\n'; // translate \r to \n
sp = p; // remember addr of insert
p = stupid_insert(p, c); // insert the char
#ifdef BB_FEATURE_VI_SETOPTS
if (showmatch && strchr(")]}", *sp) != NULL) {
showmatching(sp);
}
if (autoindent && c == '\n') { // auto indent the new line
Byte *q;
q = prev_line(p); // use prev line as templet
for (; isblnk(*q); q++) {
p = stupid_insert(p, *q); // insert the char
}
}
#endif /* BB_FEATURE_VI_SETOPTS */
}
return (p);
}
static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
{
p = text_hole_make(p, 1);
if (p != 0) {
*p = c;
file_modified = TRUE; // has the file been modified
p++;
}
return (p);
}
static Byte find_range(Byte ** start, Byte ** stop, Byte c)
{
Byte *save_dot, *p, *q;
int cnt;
save_dot = dot;
p = q = dot;
if (strchr("cdy><", c)) {
p = q = begin_line(p);
for (cnt = 1; cnt < cmdcnt; cnt++) {
q = next_line(q);
}
q = end_line(q);
} else if (strchr("$0bBeE", c)) {
do_cmd(c); // execute movement cmd
q = dot;
} else if (strchr("wW", c)) {
do_cmd(c); // execute movement cmd
if (dot > text)
dot--; // move back off of next word
if (dot > text && *dot == '\n')
dot--; // stay off NL
q = dot;
} else if (strchr("H-k{", c)) {
q = end_line(dot); // find NL
do_cmd(c); // execute movement cmd
dot_begin();
p = dot;
} else if (strchr("L+j}\r\n", c)) {
p = begin_line(dot);
do_cmd(c); // execute movement cmd
dot_end(); // find NL
q = dot;
} else {
c = 27; // error- return an ESC char
//break;
}
*start = p;
*stop = q;
if (q < p) {
*start = q;
*stop = p;
}
dot = save_dot;
return (c);
}
static int st_test(Byte * p, int type, int dir, Byte * tested)
{
Byte c, c0, ci;
int test, inc;
inc = dir;
c = c0 = p[0];
ci = p[inc];
test = 0;
if (type == S_BEFORE_WS) {
c = ci;
test = ((!isspace(c)) || c == '\n');
}
if (type == S_TO_WS) {
c = c0;
test = ((!isspace(c)) || c == '\n');
}
if (type == S_OVER_WS) {
c = c0;
test = ((isspace(c)));
}
if (type == S_END_PUNCT) {
c = ci;
test = ((ispunct(c)));
}
if (type == S_END_ALNUM) {
c = ci;
test = ((isalnum(c)) || c == '_');
}
*tested = c;
return (test);
}
static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
{
Byte c;
while (st_test(p, type, dir, &c)) {
// make sure we limit search to correct number of lines
if (c == '\n' && --linecnt < 1)
break;
if (dir >= 0 && p >= end - 1)
break;
if (dir < 0 && p <= text)
break;
p += dir; // move to next char
}
return (p);
}
// find matching char of pair () [] {}
static Byte *find_pair(Byte * p, Byte c)
{
Byte match, *q;
int dir, level;
match = ')';
level = 1;
dir = 1; // assume forward
switch (c) {
case '(':
match = ')';
break;
case '[':
match = ']';
break;
case '{':
match = '}';
break;
case ')':
match = '(';
dir = -1;
break;
case ']':
match = '[';
dir = -1;
break;
case '}':
match = '{';
dir = -1;
break;
}
for (q = p + dir; text <= q && q < end; q += dir) {
// look for match, count levels of pairs (( ))
if (*q == c)
level++; // increase pair levels
if (*q == match)
level--; // reduce pair level
if (level == 0)
break; // found matching pair
}
if (level != 0)
q = NULL; // indicate no match
return (q);
}
#ifdef BB_FEATURE_VI_SETOPTS
// show the matching char of a pair, () [] {}
static void showmatching(Byte * p)
{
Byte *q, *save_dot;
// we found half of a pair
q = find_pair(p, *p); // get loc of matching char
if (q == NULL) {
indicate_error('3'); // no matching char
} else {
// "q" now points to matching pair
save_dot = dot; // remember where we are
dot = q; // go to new loc
refresh(FALSE); // let the user see it
(void) mysleep(40); // give user some time
dot = save_dot; // go back to old loc
refresh(FALSE);
}
}
#endif /* BB_FEATURE_VI_SETOPTS */
// open a hole in text[]
static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
{
Byte *src, *dest;
int cnt;
if (size <= 0)
goto thm0;
src = p;
dest = p + size;
cnt = end - src; // the rest of buffer
if (memmove(dest, src, cnt) != dest) {
psbs("can't create room for new characters");
}
memset(p, ' ', size); // clear new hole
end = end + size; // adjust the new END
file_modified = TRUE; // has the file been modified
thm0:
return (p);
}
// close a hole in text[]
static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
{
Byte *src, *dest;
int cnt, hole_size;
// move forwards, from beginning
// assume p <= q
src = q + 1;
dest = p;
if (q < p) { // they are backward- swap them
src = p + 1;
dest = q;
}
hole_size = q - p + 1;
cnt = end - src;
if (src < text || src > end)
goto thd0;
if (dest < text || dest >= end)
goto thd0;
if (src >= end)
goto thd_atend; // just delete the end of the buffer
if (memmove(dest, src, cnt) != dest) {
psbs("can't delete the character");
}
thd_atend:
end = end - hole_size; // adjust the new END
if (dest >= end)
dest = end - 1; // make sure dest in below end-1
if (end <= text)
dest = end = text; // keep pointers valid
file_modified = TRUE; // has the file been modified
thd0:
return (dest);
}
// copy text into register, then delete text.
// if dist <= 0, do not include, or go past, a NewLine
//
static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
{
Byte *p;
// make sure start <= stop
if (start > stop) {
// they are backwards, reverse them
p = start;
start = stop;
stop = p;
}
if (dist <= 0) {
// we can not cross NL boundaries
p = start;
if (*p == '\n')
return (p);
// dont go past a NewLine
for (; p + 1 <= stop; p++) {
if (p[1] == '\n') {
stop = p; // "stop" just before NewLine
break;
}
}
}
p = start;
#ifdef BB_FEATURE_VI_YANKMARK
text_yank(start, stop, YDreg);
#endif /* BB_FEATURE_VI_YANKMARK */