blob: e4014362dfe6ffecca716f2de7ec82b6b918051b [file] [log] [blame]
Eric Andersen3f980402001-04-04 17:31:15 +00001/* vi: set sw=8 ts=8: */
2/*
3 * tiny vi.c: A small 'vi' clone
4 * Copyright (C) 2000, 2001 Sterling Huxley <sterling@europa.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21char *vi_Version =
Eric Andersen4fd382e2001-04-04 19:33:32 +000022 "$Id: vi.c,v 1.3 2001/04/04 19:33:32 andersen Exp $";
Eric Andersen3f980402001-04-04 17:31:15 +000023
24/*
25 * To compile for standalone use:
Eric Andersend402edf2001-04-04 19:29:48 +000026 * gcc -Wall -Os -s -DSTANDALONE -o vi vi.c
Eric Andersen3f980402001-04-04 17:31:15 +000027 * or
Eric Andersend402edf2001-04-04 19:29:48 +000028 * gcc -Wall -Os -s -DSTANDALONE -DCRASHME -o vi vi.c # include testing features
Eric Andersen3f980402001-04-04 17:31:15 +000029 * strip vi
30 */
31
32/*
33 * Things To Do:
34 * EXINIT
35 * $HOME/.exrc
36 * add magic to search /foo.*bar
37 * add :help command
38 * :map macros
39 * how about mode lines: vi: set sw=8 ts=8:
40 * if mark[] values were line numbers rather than pointers
41 * it would be easier to change the mark when add/delete lines
42 */
43
44//---- Feature -------------- Bytes to immplement
45#ifdef STANDALONE
Eric Andersend402edf2001-04-04 19:29:48 +000046#define vi_main main
Eric Andersen3f980402001-04-04 17:31:15 +000047#define BB_FEATURE_VI_COLON // 4288
48#define BB_FEATURE_VI_YANKMARK // 1408
49#define BB_FEATURE_VI_SEARCH // 1088
50#define BB_FEATURE_VI_USE_SIGNALS // 1056
51#define BB_FEATURE_VI_DOT_CMD // 576
52#define BB_FEATURE_VI_READONLY // 128
53#define BB_FEATURE_VI_SETOPTS // 576
54#define BB_FEATURE_VI_SET // 224
55#define BB_FEATURE_VI_WIN_RESIZE // 256 WIN_RESIZE
56// To test editor using CRASHME:
57// vi -C filename
58// To stop testing, wait until all to text[] is deleted, or
59// Ctrl-Z and kill -9 %1
60// while in the editor Ctrl-T will toggle the crashme function on and off.
61//#define BB_FEATURE_VI_CRASHME // randomly pick commands to execute
62#endif /* STANDALONE */
63
64#ifndef STANDALONE
65#include "busybox.h"
66#endif /* STANDALONE */
67#include <stdio.h>
68#include <stdlib.h>
69#include <string.h>
70#include <termios.h>
71#include <unistd.h>
72#include <sys/ioctl.h>
73#include <sys/time.h>
74#include <sys/types.h>
75#include <sys/stat.h>
76#include <time.h>
77#include <fcntl.h>
78#include <signal.h>
79#include <setjmp.h>
80#include <regex.h>
81#include <ctype.h>
82#include <assert.h>
83#include <errno.h>
84#include <stdarg.h>
Eric Andersen3f980402001-04-04 17:31:15 +000085
86#ifndef TRUE
87#define TRUE ((int)1)
88#define FALSE ((int)0)
89#endif /* TRUE */
90#define MAX_SCR_COLS 300
91
92// Misc. non-Ascii keys that report an escape sequence
93#define VI_K_UP 128 // cursor key Up
94#define VI_K_DOWN 129 // cursor key Down
95#define VI_K_RIGHT 130 // Cursor Key Right
96#define VI_K_LEFT 131 // cursor key Left
97#define VI_K_HOME 132 // Cursor Key Home
98#define VI_K_END 133 // Cursor Key End
99#define VI_K_INSERT 134 // Cursor Key Insert
100#define VI_K_PAGEUP 135 // Cursor Key Page Up
101#define VI_K_PAGEDOWN 136 // Cursor Key Page Down
102#define VI_K_FUN1 137 // Function Key F1
103#define VI_K_FUN2 138 // Function Key F2
104#define VI_K_FUN3 139 // Function Key F3
105#define VI_K_FUN4 140 // Function Key F4
106#define VI_K_FUN5 141 // Function Key F5
107#define VI_K_FUN6 142 // Function Key F6
108#define VI_K_FUN7 143 // Function Key F7
109#define VI_K_FUN8 144 // Function Key F8
110#define VI_K_FUN9 145 // Function Key F9
111#define VI_K_FUN10 146 // Function Key F10
112#define VI_K_FUN11 147 // Function Key F11
113#define VI_K_FUN12 148 // Function Key F12
114
115static const int YANKONLY = FALSE;
116static const int YANKDEL = TRUE;
117static const int FORWARD = 1; // code depends on "1" for array index
118static const int BACK = -1; // code depends on "-1" for array index
119static const int LIMITED = 0; // how much of text[] in char_search
120static const int FULL = 1; // how much of text[] in char_search
121
122static const int S_BEFORE_WS = 1; // used in skip_thing() for moving "dot"
123static const int S_TO_WS = 2; // used in skip_thing() for moving "dot"
124static const int S_OVER_WS = 3; // used in skip_thing() for moving "dot"
125static const int S_END_PUNCT = 4; // used in skip_thing() for moving "dot"
126static const int S_END_ALNUM = 5; // used in skip_thing() for moving "dot"
127
128typedef unsigned char Byte;
129
130
131static int editing; // >0 while we are editing a file
132static int cmd_mode; // 0=command 1=insert
133static int file_modified; // buffer contents changed
134static int err_method; // indicate error with beep or flash
135static int fn_start; // index of first cmd line file name
136static int save_argc; // how many file names on cmd line
137static int cmdcnt; // repetition count
138static fd_set rfds; // use select() for small sleeps
139static struct timeval tv; // use select() for small sleeps
140static char erase_char; // the users erase character
141static int rows, columns; // the terminal screen is this size
142static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset
143static char *SOs, *SOn;
144static Byte *status_buffer; // mesages to the user
145static Byte last_input_char; // last char read from user
146static Byte last_forward_char; // last char searched for with 'f'
147static Byte *cfn; // previous, current, and next file name
148static Byte *text, *end, *textend; // pointers to the user data in memory
149static Byte *screen; // pointer to the virtual screen buffer
150static int screensize; // and its size
151static Byte *screenbegin; // index into text[], of top line on the screen
152static Byte *dot; // where all the action takes place
153static int tabstop;
154static struct termios term_orig, term_vi; // remember what the cooked mode was
155
156#ifdef BB_FEATURE_VI_USE_SIGNALS
157static jmp_buf restart; // catch_sig()
158#endif /* BB_FEATURE_VI_USE_SIGNALS */
159#ifdef BB_FEATURE_VI_WIN_RESIZE
160static struct winsize winsize; // remember the window size
161#endif /* BB_FEATURE_VI_WIN_RESIZE */
162#ifdef BB_FEATURE_VI_DOT_CMD
163static int adding2q; // are we currently adding user input to q
164static Byte *last_modifying_cmd; // last modifying cmd for "."
165static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read"
166#endif /* BB_FEATURE_VI_DOT_CMD */
167#if defined(BB_FEATURE_VI_DOT_CMD) || defined(BB_FEATURE_VI_YANKMARK)
168static Byte *modifying_cmds; // cmds that modify text[]
169#endif /* BB_FEATURE_VI_DOT_CMD || BB_FEATURE_VI_YANKMARK */
170#ifdef BB_FEATURE_VI_READONLY
171static int readonly;
172#endif /* BB_FEATURE_VI_READONLY */
173#ifdef BB_FEATURE_VI_SETOPTS
174static int autoindent;
175static int showmatch;
176static int ignorecase;
177#endif /* BB_FEATURE_VI_SETOPTS */
178#ifdef BB_FEATURE_VI_YANKMARK
179static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27
180static int YDreg, Ureg; // default delete register and orig line for "U"
181static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context ''
182static Byte *context_start, *context_end;
183#endif /* BB_FEATURE_VI_YANKMARK */
184#ifdef BB_FEATURE_VI_SEARCH
185static Byte *last_search_pattern; // last pattern from a '/' or '?' search
186#endif /* BB_FEATURE_VI_SEARCH */
187
188
189static void edit_file(Byte *); // edit one file
190static void do_cmd(Byte); // execute a command
191static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot
192static Byte *begin_line(Byte *); // return pointer to cur line B-o-l
193static Byte *end_line(Byte *); // return pointer to cur line E-o-l
194static Byte *dollar_line(Byte *); // return pointer to just before NL
195static Byte *prev_line(Byte *); // return pointer to prev line B-o-l
196static Byte *next_line(Byte *); // return pointer to next line B-o-l
197static Byte *end_screen(void); // get pointer to last char on screen
198static int count_lines(Byte *, Byte *); // count line from start to stop
199static Byte *find_line(int); // find begining of line #li
200static Byte *move_to_col(Byte *, int); // move "p" to column l
201static int isblnk(Byte); // is the char a blank or tab
202static void dot_left(void); // move dot left- dont leave line
203static void dot_right(void); // move dot right- dont leave line
204static void dot_begin(void); // move dot to B-o-l
205static void dot_end(void); // move dot to E-o-l
206static void dot_next(void); // move dot to next line B-o-l
207static void dot_prev(void); // move dot to prev line B-o-l
208static void dot_scroll(int, int); // move the screen up or down
209static void dot_skip_over_ws(void); // move dot pat WS
210static void dot_delete(void); // delete the char at 'dot'
211static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end"
212static Byte *new_screen(int, int); // malloc virtual screen memory
213static Byte *new_text(int); // malloc memory for text[] buffer
214static Byte *char_insert(Byte *, Byte); // insert the char c at 'p'
215static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p'
216static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object
217static int st_test(Byte *, int, int, Byte *); // helper for skip_thing()
218static Byte *skip_thing(Byte *, int, int, int); // skip some object
219static Byte *find_pair(Byte *, Byte); // find matching pair () [] {}
220static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole
221static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole
222static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete
223static void show_help(void); // display some help info
224static void print_literal(Byte *, Byte *); // copy s to buf, convert unprintable
225static void rawmode(void); // set "raw" mode on tty
226static void cookmode(void); // return to "cooked" mode on tty
227static int mysleep(int); // sleep for 'h' 1/100 seconds
228static Byte readit(void); // read (maybe cursor) key from stdin
229static Byte get_one_char(void); // read 1 char from stdin
230static int file_size(Byte *); // what is the byte size of "fn"
231static int file_insert(Byte *, Byte *, int);
232static int file_write(Byte *, Byte *, Byte *);
233static void place_cursor(int, int);
234static void screen_erase();
235static void clear_to_eol(void);
236static void clear_to_eos(void);
237static void standout_start(void); // send "start reverse video" sequence
238static void standout_end(void); // send "end reverse video" sequence
239static void flash(int); // flash the terminal screen
240static void beep(void); // beep the terminal
241static void indicate_error(char); // use flash or beep to indicate error
242static void show_status_line(void); // put a message on the bottom line
243static void psb(char *, ...); // Print Status Buf
244static void psbs(char *, ...); // Print Status Buf in standout mode
245static void ni(Byte *); // display messages
246static void edit_status(void); // show file status on status line
247static void redraw(int); // force a full screen refresh
248static void refresh(int); // update the terminal from screen[]
249
250#ifdef BB_FEATURE_VI_SEARCH
251static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p
252static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase"
253#endif /* BB_FEATURE_VI_SEARCH */
254#ifdef BB_FEATURE_VI_COLON
255static void Hit_Return(void);
256static Byte *get_address(Byte *, int *); // get colon addr, if present
257static void colon(Byte *); // execute the "colon" mode cmds
258#endif /* BB_FEATURE_VI_COLON */
259#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
260static Byte *get_input_line(Byte *); // get input line- use "status line"
261#endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
262#ifdef BB_FEATURE_VI_USE_SIGNALS
263static void winch_sig(int); // catch window size changes
264static void suspend_sig(int); // catch ctrl-Z
265static void alarm_sig(int); // catch alarm time-outs
266static void catch_sig(int); // catch ctrl-C
267static void core_sig(int); // catch a core dump signal
268#endif /* BB_FEATURE_VI_USE_SIGNALS */
269#ifdef BB_FEATURE_VI_DOT_CMD
270static void start_new_cmd_q(Byte); // new queue for command
271static void end_cmd_q(); // stop saving input chars
272#else /* BB_FEATURE_VI_DOT_CMD */
273#define end_cmd_q()
274#endif /* BB_FEATURE_VI_DOT_CMD */
275#ifdef BB_FEATURE_VI_WIN_RESIZE
276static void window_size_get(int); // find out what size the window is
277#endif /* BB_FEATURE_VI_WIN_RESIZE */
278#ifdef BB_FEATURE_VI_SETOPTS
279static void showmatching(Byte *); // show the matching pair () [] {}
280#endif /* BB_FEATURE_VI_SETOPTS */
281#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
282static Byte *string_insert(Byte *, Byte *); // insert the string at 'p'
283#endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
284#ifdef BB_FEATURE_VI_YANKMARK
285static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register
286static Byte what_reg(void); // what is letter of current YDreg
287static void check_context(Byte); // remember context for '' command
288static Byte *swap_context(Byte *); // goto new context for '' command
289#endif /* BB_FEATURE_VI_YANKMARK */
290#ifdef BB_FEATURE_VI_CRASHME
291static void crash_dummy();
292static void crash_test();
293static int crashme = 0;
294#endif /* BB_FEATURE_VI_CRASHME */
295
296
Eric Andersen3f980402001-04-04 17:31:15 +0000297extern int vi_main(int argc, char **argv)
Eric Andersen3f980402001-04-04 17:31:15 +0000298{
Eric Andersend402edf2001-04-04 19:29:48 +0000299 int c;
Eric Andersen3f980402001-04-04 17:31:15 +0000300
301#ifdef BB_FEATURE_VI_YANKMARK
302 int i;
303#endif /* BB_FEATURE_VI_YANKMARK */
304
305 SOs = "\033[7m"; // Terminal standout mode on
306 SOn = "\033[0m"; // Terminal standout mode off
307#ifdef BB_FEATURE_VI_CRASHME
308 (void) srand((long) getpid());
309#endif /* BB_FEATURE_VI_CRASHME */
310 status_buffer = (Byte *) malloc(200); // hold messages to user
311#ifdef BB_FEATURE_VI_READONLY
312 readonly = FALSE;
313 if (strncmp(argv[0], "view", 4) == 0) {
314 readonly = TRUE;
315 }
316#endif /* BB_FEATURE_VI_READONLY */
317#ifdef BB_FEATURE_VI_SETOPTS
318 autoindent = 1;
319 ignorecase = 1;
320 showmatch = 1;
321#endif /* BB_FEATURE_VI_SETOPTS */
322#ifdef BB_FEATURE_VI_YANKMARK
323 for (i = 0; i < 28; i++) {
324 reg[i] = 0;
325 } // init the yank regs
326#endif /* BB_FEATURE_VI_YANKMARK */
327#ifdef BB_FEATURE_VI_DOT_CMD
328 modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[]
329#endif /* BB_FEATURE_VI_DOT_CMD */
330
331 // 1- process $HOME/.exrc file
332 // 2- process EXINIT variable from environment
333 // 3- process command line args
334 while ((c = getopt(argc, argv, "hCR")) != -1) {
335 switch (c) {
336#ifdef BB_FEATURE_VI_CRASHME
337 case 'C':
338 crashme = 1;
339 break;
340#endif /* BB_FEATURE_VI_CRASHME */
341#ifdef BB_FEATURE_VI_READONLY
342 case 'R': // Read-only flag
343 readonly = TRUE;
344 break;
345#endif /* BB_FEATURE_VI_READONLY */
346 //case 'r': // recover flag- ignore- we don't use tmp file
347 //case 'x': // encryption flag- ignore
348 //case 'c': // execute command first
349 //case 'h': // help -- just use default
350 default:
351 show_help();
352 break;
353 }
354 }
355
356 // The argv array can be used by the ":next" and ":rewind" commands
357 // save optind.
358 fn_start = optind; // remember first file name for :next and :rew
359 save_argc = argc;
360
361 //----- This is the main file handling loop --------------
362 if (optind >= argc) {
363 editing = 1; // 0= exit, 1= one file, 2= multiple files
364 edit_file(0);
365 } else {
366 for (; optind < argc; optind++) {
367 editing = 1; // 0=exit, 1=one file, 2+ =many files
368 if (cfn != 0)
369 free(cfn);
370 cfn = (Byte *) strdup(argv[optind]);
371 edit_file(cfn);
372 }
373 }
374 //-----------------------------------------------------------
375
376 return (0);
377}
378
379static void edit_file(Byte * fn)
380{
381 char c;
382 int cnt, size;
383
384#ifdef BB_FEATURE_VI_USE_SIGNALS
385 char *msg;
386 int sig;
387#endif /* BB_FEATURE_VI_USE_SIGNALS */
388#ifdef BB_FEATURE_VI_YANKMARK
389 static Byte *cur_line;
390#endif /* BB_FEATURE_VI_YANKMARK */
391
392 rawmode();
393 rows = 24;
394 columns = 80;
395#ifdef BB_FEATURE_VI_WIN_RESIZE
396 window_size_get(0);
397#endif /* BB_FEATURE_VI_WIN_RESIZE */
398 new_screen(rows, columns); // get memory for virtual screen
399
400 cnt = file_size(fn); // file size
401 size = 2 * cnt; // 200% of file size
402 new_text(size); // get a text[] buffer
403 screenbegin = dot = end = text;
404 if (fn != 0) {
405 file_insert(fn, text, cnt);
406 } else {
407 (void) char_insert(text, '\n'); // start empty buf with dummy line
408 }
409 file_modified = FALSE;
410#ifdef BB_FEATURE_VI_YANKMARK
411 YDreg = 26; // default Yank/Delete reg
412 Ureg = 27; // hold orig line for "U" cmd
413 for (cnt = 0; cnt < 28; cnt++) {
414 mark[cnt] = 0;
415 } // init the marks
416 mark[26] = mark[27] = text; // init "previous context"
417#endif /* BB_FEATURE_VI_YANKMARK */
418
419 err_method = 1; // flash
420 last_forward_char = last_input_char = '\0';
421 crow = 0;
422 ccol = 0;
423 edit_status();
424
425#ifdef BB_FEATURE_VI_USE_SIGNALS
426 signal(SIGHUP, catch_sig);
427 signal(SIGINT, catch_sig);
428 signal(SIGALRM, alarm_sig);
429 signal(SIGTERM, catch_sig);
430 signal(SIGQUIT, core_sig);
431 signal(SIGILL, core_sig);
432 signal(SIGTRAP, core_sig);
433 signal(SIGIOT, core_sig);
434 signal(SIGABRT, core_sig);
435 signal(SIGFPE, core_sig);
436 signal(SIGBUS, core_sig);
437 signal(SIGSEGV, core_sig);
Eric Andersend402edf2001-04-04 19:29:48 +0000438#ifdef SIGSYS
Eric Andersen3f980402001-04-04 17:31:15 +0000439 signal(SIGSYS, core_sig);
Eric Andersend402edf2001-04-04 19:29:48 +0000440#endif
Eric Andersen3f980402001-04-04 17:31:15 +0000441 signal(SIGWINCH, winch_sig);
442 signal(SIGTSTP, suspend_sig);
443 sig = setjmp(restart);
444 if (sig != 0) {
445 msg = "";
446 if (sig == SIGWINCH)
447 msg = "(window resize)";
448 if (sig == SIGHUP)
449 msg = "(hangup)";
450 if (sig == SIGINT)
451 msg = "(interrupt)";
452 if (sig == SIGTERM)
453 msg = "(terminate)";
454 if (sig == SIGBUS)
455 msg = "(bus error)";
456 if (sig == SIGSEGV)
457 msg = "(I tried to touch invalid memory)";
458 if (sig == SIGALRM)
459 msg = "(alarm)";
460
461 psbs("-- caught signal %d %s--", sig, msg);
462 }
463#endif /* BB_FEATURE_VI_USE_SIGNALS */
464
465 editing = 1;
466 cmd_mode = 0; // 0=command 1=insert 2='R'eplace
467 cmdcnt = 0;
468 tabstop = 8;
469 offset = 0; // no horizontal offset
470 c = '\0';
471#ifdef BB_FEATURE_VI_DOT_CMD
472 if (last_modifying_cmd != 0)
473 free(last_modifying_cmd);
474 if (ioq_start != NULL)
475 free(ioq_start);
476 ioq = ioq_start = last_modifying_cmd = 0;
477 adding2q = 0;
478#endif /* BB_FEATURE_VI_DOT_CMD */
479 redraw(TRUE);
480 show_status_line();
481
482 //------This is the main Vi cmd handling loop -----------------------
483 while (editing > 0) {
484#ifdef BB_FEATURE_VI_CRASHME
485 if (crashme > 0) {
486 if ((end - text) > 1) {
487 crash_dummy(); // generate a random command
488 } else {
489 crashme = 0;
490 dot =
491 string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string
492 refresh(FALSE);
493 }
494 }
495#endif /* BB_FEATURE_VI_CRASHME */
496 last_input_char = c = get_one_char(); // get a cmd from user
497#ifdef BB_FEATURE_VI_YANKMARK
498 // save a copy of the current line- for the 'U" command
499 if (begin_line(dot) != cur_line) {
500 cur_line = begin_line(dot);
501 text_yank(begin_line(dot), end_line(dot), Ureg);
502 }
503#endif /* BB_FEATURE_VI_YANKMARK */
504#ifdef BB_FEATURE_VI_DOT_CMD
505 // These are commands that change text[].
506 // Remember the input for the "." command
507 if (!adding2q && ioq_start == 0
508 && strchr((char *) modifying_cmds, c) != NULL) {
509 start_new_cmd_q(c);
510 }
511#endif /* BB_FEATURE_VI_DOT_CMD */
512 do_cmd(c); // execute the user command
513 //
514 // poll to see if there is input already waiting. if we are
515 // not able to display output fast enough to keep up, skip
516 // the display update until we catch up with input.
517 if (mysleep(0) == 0) {
518 // no input pending- so update output
519 refresh(FALSE);
520 show_status_line();
521 }
522#ifdef BB_FEATURE_VI_CRASHME
523 if (crashme > 0)
524 crash_test(); // test editor variables
525#endif /* BB_FEATURE_VI_CRASHME */
526 }
527 //-------------------------------------------------------------------
528
529 place_cursor(rows, 0); // go to bottom of screen
530 clear_to_eol(); // Erase to end of line
531 cookmode();
532}
533
534static Byte readbuffer[BUFSIZ];
535
536#ifdef BB_FEATURE_VI_CRASHME
537static int totalcmds = 0;
538static int Mp = 85; // Movement command Probability
539static int Np = 90; // Non-movement command Probability
540static int Dp = 96; // Delete command Probability
541static int Ip = 97; // Insert command Probability
542static int Yp = 98; // Yank command Probability
543static int Pp = 99; // Put command Probability
544static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0;
545char chars[20] = "\t012345 abcdABCD-=.$";
546char *words[20] = { "this", "is", "a", "test",
547 "broadcast", "the", "emergency", "of",
548 "system", "quick", "brown", "fox",
549 "jumped", "over", "lazy", "dogs",
550 "back", "January", "Febuary", "March"
551};
552char *lines[20] = {
553 "You should have received a copy of the GNU General Public License\n",
554 "char c, cm, *cmd, *cmd1;\n",
555 "generate a command by percentages\n",
556 "Numbers may be typed as a prefix to some commands.\n",
557 "Quit, discarding changes!\n",
558 "Forced write, if permission originally not valid.\n",
559 "In general, any ex or ed command (such as substitute or delete).\n",
560 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
561 "Please get w/ me and I will go over it with you.\n",
562 "The following is a list of scheduled, committed changes.\n",
563 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
564 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
565 "Any question about transactions please contact Sterling Huxley.\n",
566 "I will try to get back to you by Friday, December 31.\n",
567 "This Change will be implemented on Friday.\n",
568 "Let me know if you have problems accessing this;\n",
569 "Sterling Huxley recently added you to the access list.\n",
570 "Would you like to go to lunch?\n",
571 "The last command will be automatically run.\n",
572 "This is too much english for a computer geek.\n",
573};
574char *multilines[20] = {
575 "You should have received a copy of the GNU General Public License\n",
576 "char c, cm, *cmd, *cmd1;\n",
577 "generate a command by percentages\n",
578 "Numbers may be typed as a prefix to some commands.\n",
579 "Quit, discarding changes!\n",
580 "Forced write, if permission originally not valid.\n",
581 "In general, any ex or ed command (such as substitute or delete).\n",
582 "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n",
583 "Please get w/ me and I will go over it with you.\n",
584 "The following is a list of scheduled, committed changes.\n",
585 "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n",
586 "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n",
587 "Any question about transactions please contact Sterling Huxley.\n",
588 "I will try to get back to you by Friday, December 31.\n",
589 "This Change will be implemented on Friday.\n",
590 "Let me know if you have problems accessing this;\n",
591 "Sterling Huxley recently added you to the access list.\n",
592 "Would you like to go to lunch?\n",
593 "The last command will be automatically run.\n",
594 "This is too much english for a computer geek.\n",
595};
596
597// create a random command to execute
598static void crash_dummy()
599{
600 static int sleeptime; // how long to pause between commands
601 char c, cm, *cmd, *cmd1;
602 int i, cnt, thing, rbi, startrbi, percent;
603
604 // "dot" movement commands
605 cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL";
606
607 // is there already a command running?
608 if (strlen((char *) readbuffer) > 0)
609 goto cd1;
610 cd0:
611 startrbi = rbi = 0;
612 sleeptime = 0; // how long to pause between commands
613 memset(readbuffer, '\0', BUFSIZ - 1); // clear the read buffer
614 // generate a command by percentages
615 percent = (int) lrand48() % 100; // get a number from 0-99
616 if (percent < Mp) { // Movement commands
617 // available commands
618 cmd = cmd1;
619 M++;
620 } else if (percent < Np) { // non-movement commands
621 cmd = "mz<>\'\""; // available commands
622 N++;
623 } else if (percent < Dp) { // Delete commands
624 cmd = "dx"; // available commands
625 D++;
626 } else if (percent < Ip) { // Inset commands
627 cmd = "iIaAsrJ"; // available commands
628 I++;
629 } else if (percent < Yp) { // Yank commands
630 cmd = "yY"; // available commands
631 Y++;
632 } else if (percent < Pp) { // Put commands
633 cmd = "pP"; // available commands
634 P++;
635 } else {
636 // We do not know how to handle this command, try again
637 U++;
638 goto cd0;
639 }
640 // randomly pick one of the available cmds from "cmd[]"
641 i = (int) lrand48() % strlen(cmd);
642 cm = cmd[i];
643 if (strchr(":\024", cm))
644 goto cd0; // dont allow these commands
645 readbuffer[rbi++] = cm; // put cmd into input buffer
646
647 // now we have the command-
648 // there are 1, 2, and multi char commands
649 // find out which and generate the rest of command as necessary
650 if (strchr("dmryz<>\'\"", cm)) { // 2-char commands
651 cmd1 = " \n\r0$^-+wWeEbBhjklHL";
652 if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[]
653 cmd1 = "abcdefghijklmnopqrstuvwxyz";
654 }
655 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
656 c = cmd1[thing];
657 readbuffer[rbi++] = c; // add movement to input buffer
658 }
659 if (strchr("iIaAsc", cm)) { // multi-char commands
660 if (cm == 'c') {
661 // change some thing
662 thing = (int) lrand48() % strlen(cmd1); // pick a movement command
663 c = cmd1[thing];
664 readbuffer[rbi++] = c; // add movement to input buffer
665 }
666 thing = (int) lrand48() % 4; // what thing to insert
667 cnt = (int) lrand48() % 10; // how many to insert
668 for (i = 0; i < cnt; i++) {
669 if (thing == 0) { // insert chars
670 readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))];
671 } else if (thing == 1) { // insert words
672 strcat((char *) readbuffer, words[(int) lrand48() % 20]);
673 strcat((char *) readbuffer, " ");
674 sleeptime = 0; // how fast to type
675 } else if (thing == 2) { // insert lines
676 strcat((char *) readbuffer, lines[(int) lrand48() % 20]);
677 sleeptime = 0; // how fast to type
678 } else { // insert multi-lines
679 strcat((char *) readbuffer, multilines[(int) lrand48() % 20]);
680 sleeptime = 0; // how fast to type
681 }
682 }
683 strcat((char *) readbuffer, "\033");
684 }
685 cd1:
686 totalcmds++;
687 if (sleeptime > 0)
688 (void) mysleep(sleeptime); // sleep 1/100 sec
689}
690
691// test to see if there are any errors
692static void crash_test()
693{
694 static time_t oldtim;
695 time_t tim;
696 char d[2], buf[100], msg[BUFSIZ];
697
698 msg[0] = '\0';
699 if (end < text) {
700 strcat((char *) msg, "end<text ");
701 }
702 if (end > textend) {
703 strcat((char *) msg, "end>textend ");
704 }
705 if (dot < text) {
706 strcat((char *) msg, "dot<text ");
707 }
708 if (dot > end) {
709 strcat((char *) msg, "dot>end ");
710 }
711 if (screenbegin < text) {
712 strcat((char *) msg, "screenbegin<text ");
713 }
714 if (screenbegin > end - 1) {
715 strcat((char *) msg, "screenbegin>end-1 ");
716 }
717
718 if (strlen(msg) > 0) {
719 alarm(0);
720 sprintf(buf, "\n\n%d: \'%c\' ", totalcmds, last_input_char);
721 write(1, buf, strlen(buf));
722 write(1, msg, strlen(msg));
723 write(1, "\n\n\n", 3);
724 write(1, "\033[7m[Hit return to continue]\033[0m", 32);
725 while (read(0, d, 1) > 0) {
726 if (d[0] == '\n' || d[0] == '\r')
727 break;
728 }
729 alarm(3);
730 }
731 tim = (time_t) time((time_t *) 0);
732 if (tim >= (oldtim + 3)) {
733 sprintf((char *) status_buffer,
734 "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d",
735 totalcmds, M, N, I, D, Y, P, U, end - text + 1);
736 oldtim = tim;
737 }
738 return;
739}
740#endif /* BB_FEATURE_VI_CRASHME */
741
742//---------------------------------------------------------------------
743//----- the Ascii Chart -----------------------------------------------
744//
745// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
746// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
747// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
748// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
749// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
750// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f /
751// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
752// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
753// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
754// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
755// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
756// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
757// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
758// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
759// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
760// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
761//---------------------------------------------------------------------
762
763//----- Execute a Vi Command -----------------------------------
764static void do_cmd(Byte c)
765{
766 Byte c1, *p, *q, *msg, buf[9], *save_dot;
767 int cnt, i, j, dir, yf;
768
769 c1 = c; // quiet the compiler
770 cnt = yf = dir = 0; // quiet the compiler
771 p = q = save_dot = msg = buf; // quiet the compiler
772 memset(buf, '\0', 9); // clear buf
773 if (cmd_mode == 2) {
774 // we are 'R'eplacing the current *dot with new char
775 if (*dot == '\n') {
776 // don't Replace past E-o-l
777 cmd_mode = 1; // convert to insert
778 } else {
779 if (1 <= c && c <= 127) { // only ASCII chars
780 if (c != 27)
781 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
782 dot = char_insert(dot, c); // insert new char
783 }
784 goto dc1;
785 }
786 }
787 if (cmd_mode == 1) {
788 // insert the char c at "dot"
789 if (1 <= c && c <= 127) {
790 dot = char_insert(dot, c); // only ASCII chars
791 }
792 goto dc1;
793 }
794
795 switch (c) {
796 //case 0x01: // soh
797 //case 0x09: // ht
798 //case 0x0b: // vt
799 //case 0x0e: // so
800 //case 0x0f: // si
801 //case 0x10: // dle
802 //case 0x11: // dc1
803 //case 0x12: // dc2
804 //case 0x13: // dc3
805 case 0x14: // dc4 ctrl-T
806#ifdef BB_FEATURE_VI_CRASHME
807 crashme = (crashme == 0) ? 1 : 0;
808#endif /* BB_FEATURE_VI_CRASHME */
809 break;
810 //case 0x16: // syn
811 //case 0x17: // etb
812 //case 0x18: // can
813 //case 0x1c: // fs
814 //case 0x1d: // gs
815 //case 0x1e: // rs
816 //case 0x1f: // us
817 //case '!': // !-
818 //case '#': // #-
819 //case '&': // &-
820 //case '(': // (-
821 //case ')': // )-
822 //case '*': // *-
823 //case ',': // ,-
824 //case '=': // =-
825 //case '@': // @-
826 //case 'F': // F-
827 //case 'G': // G-
828 //case 'K': // K-
829 //case 'M': // M-
830 //case 'Q': // Q-
831 //case 'S': // S-
832 //case 'T': // T-
833 //case 'V': // V-
834 //case '[': // [-
835 //case '\\': // \-
836 //case ']': // ]-
837 //case '_': // _-
838 //case '`': // `-
839 //case 'g': // g-
840 //case 'm': // m-
841 //case 't': // t-
842 //case 'v': // v-
843 default: // unrecognised command
844 buf[0] = c;
845 buf[1] = '\0';
846 if (c <= ' ') {
847 buf[0] = '^';
848 buf[1] = c + '@';
849 buf[2] = '\0';
850 }
851 ni((Byte *) buf);
852 end_cmd_q(); // stop adding to q
853 case 0x00: // nul- ignore
854 break;
855 case 2: // ctrl-B scroll up full screen
856 case VI_K_PAGEUP: // Cursor Key Page Up
857 dot_scroll(rows - 2, -1);
858 break;
859#ifdef BB_FEATURE_VI_USE_SIGNALS
860 case 0x03: // ctrl-C interrupt
861 longjmp(restart, 1);
862 break;
863 case 26: // ctrl-Z suspend
864 suspend_sig(SIGTSTP);
865 break;
866#endif /* BB_FEATURE_VI_USE_SIGNALS */
867 case 4: // ctrl-D scroll down half screen
868 dot_scroll((rows - 2) / 2, 1);
869 break;
870 case 5: // ctrl-E scroll down one line
871 dot_scroll(1, 1);
872 break;
873 case 6: // ctrl-F scroll down full screen
874 case VI_K_PAGEDOWN: // Cursor Key Page Down
875 dot_scroll(rows - 2, 1);
876 break;
877 case 7: // ctrl-G show current status
878 edit_status();
879 break;
880 case 'h': // h- move left
881 case VI_K_LEFT: // cursor key Left
882 case 8: // ^h- move left (This may be ERASE char)
883 case 127: // DEL- move left (This may be ERASE char)
884 if (cmdcnt-- > 1) {
885 do_cmd(c);
886 } // repeat cnt
887 dot_left();
888 break;
889 case 10: // Newline ^J
890 case 'j': // j- goto next line, same col
891 case VI_K_DOWN: // cursor key Down
892 if (cmdcnt-- > 1) {
893 do_cmd(c);
894 } // repeat cnt
895 dot_next(); // go to next B-o-l
896 dot = move_to_col(dot, ccol + offset); // try stay in same col
897 break;
898 case 12: // ctrl-L force redraw whole screen
899 place_cursor(0, 0); // put cursor in correct place
900 clear_to_eos(); // tel terminal to erase display
901 (void) mysleep(10);
902 screen_erase(); // erase the internal screen buffer
903 refresh(TRUE); // this will redraw the entire display
904 break;
905 case 13: // Carriage Return ^M
906 case '+': // +- goto next line
907 if (cmdcnt-- > 1) {
908 do_cmd(c);
909 } // repeat cnt
910 dot_next();
911 dot_skip_over_ws();
912 break;
913 case 21: // ctrl-U scroll up half screen
914 dot_scroll((rows - 2) / 2, -1);
915 break;
916 case 25: // ctrl-Y scroll up one line
917 dot_scroll(1, -1);
918 break;
919 case 0x1b: // esc
920 if (cmd_mode == 0)
921 indicate_error(c);
922 cmd_mode = 0; // stop insrting
923 end_cmd_q();
924 *status_buffer = '\0'; // clear status buffer
925 break;
926 case ' ': // move right
927 case 'l': // move right
928 case VI_K_RIGHT: // Cursor Key Right
929 if (cmdcnt-- > 1) {
930 do_cmd(c);
931 } // repeat cnt
932 dot_right();
933 break;
934#ifdef BB_FEATURE_VI_YANKMARK
935 case '"': // "- name a register to use for Delete/Yank
936 c1 = get_one_char();
937 c1 = tolower(c1);
938 if (islower(c1)) {
939 YDreg = c1 - 'a';
940 } else {
941 indicate_error(c);
942 }
943 break;
944 case '\'': // '- goto a specific mark
945 c1 = get_one_char();
946 c1 = tolower(c1);
947 if (islower(c1)) {
948 c1 = c1 - 'a';
949 // get the b-o-l
950 q = mark[(int) c1];
951 if (text <= q && q < end) {
952 dot = q;
953 dot_begin(); // go to B-o-l
954 dot_skip_over_ws();
955 }
956 } else if (c1 == '\'') { // goto previous context
957 dot = swap_context(dot); // swap current and previous context
958 dot_begin(); // go to B-o-l
959 dot_skip_over_ws();
960 } else {
961 indicate_error(c);
962 }
963 break;
964 case 'm': // m- Mark a line
965 // this is really stupid. If there are any inserts or deletes
966 // between text[0] and dot then this mark will not point to the
967 // correct location! It could be off by many lines!
968 // Well..., at least its quick and dirty.
969 c1 = get_one_char();
970 c1 = tolower(c1);
971 if (islower(c1)) {
972 c1 = c1 - 'a';
973 // remember the line
974 mark[(int) c1] = dot;
975 } else {
976 indicate_error(c);
977 }
978 break;
979 case 'P': // P- Put register before
980 case 'p': // p- put register after
981 p = reg[YDreg];
982 if (p == 0) {
983 psbs("Nothing in register %c", what_reg());
984 break;
985 }
986 // are we putting whole lines or strings
987 if (strchr((char *) p, '\n') != NULL) {
988 if (c == 'P') {
989 dot_begin(); // putting lines- Put above
990 }
991 if (c == 'p') {
992 // are we putting after very last line?
993 if (end_line(dot) == (end - 1)) {
994 dot = end; // force dot to end of text[]
995 } else {
996 dot_next(); // next line, then put before
997 }
998 }
999 } else {
1000 if (c == 'p')
1001 dot_right(); // move to right, can move to NL
1002 }
1003 dot = string_insert(dot, p); // insert the string
1004 end_cmd_q(); // stop adding to q
1005 break;
1006 case 'u': // u-
1007 case 'U': // U- Undo; replace current line with original version
1008 if (reg[Ureg] != 0) {
1009 p = begin_line(dot);
1010 q = end_line(dot);
1011 p = text_hole_delete(p, q); // delete cur line
1012 p = string_insert(p, reg[Ureg]); // insert orig line
1013 dot = p;
1014 dot_skip_over_ws();
1015 }
1016 break;
1017#endif /* BB_FEATURE_VI_YANKMARK */
1018 case '$': // $- goto end of line
1019 case VI_K_END: // Cursor Key End
1020 if (cmdcnt-- > 1) {
1021 do_cmd(c);
1022 } // repeat cnt
1023 dot = end_line(dot + 1);
1024 break;
1025 case '%': // %- find matching char of pair () [] {}
1026 for (q = dot; q < end && *q != '\n'; q++) {
1027 if (strchr("()[]{}", *q) != NULL) {
1028 // we found half of a pair
1029 p = find_pair(q, *q);
1030 if (p == NULL) {
1031 indicate_error(c);
1032 } else {
1033 dot = p;
1034 }
1035 break;
1036 }
1037 }
1038 if (*q == '\n')
1039 indicate_error(c);
1040 break;
1041 case 'f': // f- forward to a user specified char
1042 last_forward_char = get_one_char(); // get the search char
1043 //
1044 // dont seperate these two commands. 'f' depends on ';'
1045 //
1046 //**** fall thru to ... 'i'
1047 case ';': // ;- look at rest of line for last forward char
1048 if (cmdcnt-- > 1) {
1049 do_cmd(c);
1050 } // repeat cnt
1051 q = dot + 1;
1052 while (q < end - 1 && *q != '\n' && *q != last_forward_char) {
1053 q++;
1054 }
1055 if (*q == last_forward_char)
1056 dot = q;
1057 break;
1058 case '-': // -- goto prev line
1059 if (cmdcnt-- > 1) {
1060 do_cmd(c);
1061 } // repeat cnt
1062 dot_prev();
1063 dot_skip_over_ws();
1064 break;
1065#ifdef BB_FEATURE_VI_DOT_CMD
1066 case '.': // .- repeat the last modifying command
1067 // Stuff the last_modifying_cmd back into stdin
1068 // and let it be re-executed.
1069 if (last_modifying_cmd != 0) {
1070 ioq = ioq_start = (Byte *) strdup((char *) last_modifying_cmd);
1071 }
1072 break;
1073#endif /* BB_FEATURE_VI_DOT_CMD */
1074#ifdef BB_FEATURE_VI_SEARCH
1075 case '?': // /- search for a pattern
1076 case '/': // /- search for a pattern
1077 buf[0] = c;
1078 buf[1] = '\0';
1079 q = get_input_line(buf); // get input line- use "status line"
1080 if (strlen((char *) q) == 1)
1081 goto dc3; // if no pat re-use old pat
1082 if (strlen((char *) q) > 1) { // new pat- save it and find
1083 // there is a new pat
1084 if (last_search_pattern != 0) {
1085 free(last_search_pattern);
1086 }
1087 last_search_pattern = (Byte *) strdup((char *) q);
1088 goto dc3; // now find the pattern
1089 }
1090 // user changed mind and erased the "/"- do nothing
1091 break;
1092 case 'N': // N- backward search for last pattern
1093 if (cmdcnt-- > 1) {
1094 do_cmd(c);
1095 } // repeat cnt
1096 dir = BACK; // assume BACKWARD search
1097 p = dot - 1;
1098 if (last_search_pattern[0] == '?') {
1099 dir = FORWARD;
1100 p = dot + 1;
1101 }
1102 goto dc4; // now search for pattern
1103 break;
1104 case 'n': // n- repeat search for last pattern
1105 // search rest of text[] starting at next char
1106 // if search fails return orignal "p" not the "p+1" address
1107 if (cmdcnt-- > 1) {
1108 do_cmd(c);
1109 } // repeat cnt
1110 dc3:
1111 if (last_search_pattern == 0) {
1112 msg = (Byte *) "No previous regular expression";
1113 goto dc2;
1114 }
1115 if (last_search_pattern[0] == '/') {
1116 dir = FORWARD; // assume FORWARD search
1117 p = dot + 1;
1118 }
1119 if (last_search_pattern[0] == '?') {
1120 dir = BACK;
1121 p = dot - 1;
1122 }
1123 dc4:
1124 q = char_search(p, last_search_pattern + 1, dir, FULL);
1125 if (q != NULL) {
1126 dot = q; // good search, update "dot"
1127 msg = (Byte *) "";
1128 goto dc2;
1129 }
1130 // no pattern found between "dot" and "end"- continue at top
1131 p = text;
1132 if (dir == BACK) {
1133 p = end - 1;
1134 }
1135 q = char_search(p, last_search_pattern + 1, dir, FULL);
1136 if (q != NULL) { // found something
1137 dot = q; // found new pattern- goto it
1138 msg = (Byte *) "search hit BOTTOM, continuing at TOP";
1139 if (dir == BACK) {
1140 msg = (Byte *) "search hit TOP, continuing at BOTTOM";
1141 }
1142 } else {
1143 msg = (Byte *) "Pattern not found";
1144 }
1145 dc2:
1146 psbs("%s", msg);
1147 break;
1148 case '{': // {- move backward paragraph
1149 q = char_search(dot, (Byte *) "\n\n", BACK, FULL);
1150 if (q != NULL) { // found blank line
1151 dot = next_line(q); // move to next blank line
1152 }
1153 break;
1154 case '}': // }- move forward paragraph
1155 q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL);
1156 if (q != NULL) { // found blank line
1157 dot = next_line(q); // move to next blank line
1158 }
1159 break;
1160#endif /* BB_FEATURE_VI_SEARCH */
1161 case '0': // 0- goto begining of line
1162 case '1': // 1-
1163 case '2': // 2-
1164 case '3': // 3-
1165 case '4': // 4-
1166 case '5': // 5-
1167 case '6': // 6-
1168 case '7': // 7-
1169 case '8': // 8-
1170 case '9': // 9-
1171 if (c == '0' && cmdcnt < 1) {
1172 dot_begin(); // this was a standalone zero
1173 } else {
1174 cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number
1175 }
1176 break;
1177 case ':': // :- the colon mode commands
1178#ifdef BB_FEATURE_VI_COLON
1179 p = get_input_line((Byte *) ":"); // get input line- use "status line"
1180 colon(p); // execute the command
1181#else /* BB_FEATURE_VI_COLON */
1182 *status_buffer = '\0'; // clear the status buffer
1183 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1184 clear_to_eol(); // clear the line
1185 write(1, ":", 1); // write out the : prompt
1186 for (cnt = 0; cnt < 8; cnt++) {
1187 c1 = get_one_char();
1188 if (c1 == '\n' || c1 == '\r') {
1189 break;
1190 }
1191 buf[cnt] = c1;
1192 buf[cnt + 1] = '\0';
1193 write(1, buf + cnt, 1); // echo the char
1194 }
1195 cnt = strlen((char *) buf);
1196 if (strncasecmp((char *) buf, "quit", cnt) == 0 ||
1197 strncasecmp((char *) buf, "q!", cnt) == 0) { // delete lines
1198 if (file_modified == TRUE && buf[1] != '!') {
1199 psbs("No write since last change (:quit! overrides)");
1200 } else {
1201 editing = 0;
1202 }
1203 } else if (strncasecmp((char *) buf, "write", cnt) == 0 ||
1204 strncasecmp((char *) buf, "wq", cnt) == 0) {
1205 cnt = file_write(cfn, text, end - 1);
1206 file_modified = FALSE;
1207 psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt);
1208 if (buf[1] == 'q') {
1209 editing = 0;
1210 }
1211 } else { // unrecognised cmd
1212 ni((Byte *) buf);
1213 }
1214#endif /* BB_FEATURE_VI_COLON */
1215 break;
1216 case '<': // <- Left shift something
1217 case '>': // >- Right shift something
1218 cnt = count_lines(text, dot); // remember what line we are on
1219 c1 = get_one_char(); // get the type of thing to delete
1220 find_range(&p, &q, c1);
1221 (void) yank_delete(p, q, 1, YANKONLY); // save copy before change
1222 p = begin_line(p);
1223 q = end_line(q);
1224 i = count_lines(p, q); // # of lines we are shifting
1225 for ( ; i > 0; i--, p = next_line(p)) {
1226 if (c == '<') {
1227 // shift left- remove tab or 8 spaces
1228 if (*p == '\t') {
1229 // shrink buffer 1 char
1230 (void) text_hole_delete(p, p);
1231 } else if (*p == ' ') {
1232 // we should be calculating columns, not just SPACE
1233 for (j = 0; *p == ' ' && j < tabstop; j++) {
1234 (void) text_hole_delete(p, p);
1235 }
1236 }
1237 } else if (c == '>') {
1238 // shift right -- add tab or 8 spaces
1239 (void) char_insert(p, '\t');
1240 }
1241 }
1242 dot = find_line(cnt); // what line were we on
1243 dot_skip_over_ws();
1244 end_cmd_q(); // stop adding to q
1245 break;
1246 case 'A': // A- append at e-o-l
1247 dot_end(); // go to e-o-l
1248 //**** fall thru to ... 'a'
1249 case 'a': // a- append after current char
1250 if (*dot != '\n')
1251 dot++;
1252 goto dc_i;
1253 break;
1254 case 'B': // B- back a blank-delimited Word
1255 case 'E': // E- end of a blank-delimited word
1256 case 'W': // W- forward a blank-delimited word
1257 if (cmdcnt-- > 1) {
1258 do_cmd(c);
1259 } // repeat cnt
1260 dir = FORWARD;
1261 if (c == 'B')
1262 dir = BACK;
1263 if (c == 'W' || isspace(dot[dir])) {
1264 dot = skip_thing(dot, 1, dir, S_TO_WS);
1265 dot = skip_thing(dot, 2, dir, S_OVER_WS);
1266 }
1267 if (c != 'W')
1268 dot = skip_thing(dot, 1, dir, S_BEFORE_WS);
1269 break;
1270 case 'C': // C- Change to e-o-l
1271 case 'D': // D- delete to e-o-l
1272 save_dot = dot;
1273 dot = dollar_line(dot); // move to before NL
1274 // copy text into a register and delete
1275 dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l
1276 if (c == 'C')
1277 goto dc_i; // start inserting
1278#ifdef BB_FEATURE_VI_DOT_CMD
1279 if (c == 'D')
1280 end_cmd_q(); // stop adding to q
1281#endif /* BB_FEATURE_VI_DOT_CMD */
1282 break;
1283 case 'H': // H- goto top line on screen
1284 dot = screenbegin;
1285 if (cmdcnt > (rows - 1)) {
1286 cmdcnt = (rows - 1);
1287 }
1288 if (cmdcnt-- > 1) {
1289 do_cmd('+');
1290 } // repeat cnt
1291 dot_skip_over_ws();
1292 break;
1293 case 'I': // I- insert before first non-blank
1294 dot_begin(); // 0
1295 dot_skip_over_ws();
1296 //**** fall thru to ... 'i'
1297 case 'i': // i- insert before current char
1298 case VI_K_INSERT: // Cursor Key Insert
1299 dc_i:
1300 cmd_mode = 1; // start insrting
1301 psb("-- Insert --");
1302 break;
1303 case 'J': // J- join current and next lines together
1304 if (cmdcnt-- > 2) {
1305 do_cmd(c);
1306 } // repeat cnt
1307 dot_end(); // move to NL
1308 if (dot < end - 1) { // make sure not last char in text[]
1309 *dot++ = ' '; // replace NL with space
1310 while (isblnk(*dot)) { // delete leading WS
1311 dot_delete();
1312 }
1313 }
1314 end_cmd_q(); // stop adding to q
1315 break;
1316 case 'L': // L- goto bottom line on screen
1317 dot = end_screen();
1318 if (cmdcnt > (rows - 1)) {
1319 cmdcnt = (rows - 1);
1320 }
1321 if (cmdcnt-- > 1) {
1322 do_cmd('-');
1323 } // repeat cnt
1324 dot_begin();
1325 dot_skip_over_ws();
1326 break;
1327 case 'O': // O- open a empty line above
1328 // 0i\n\033-i
1329 p = begin_line(dot);
1330 if (p[-1] == '\n') {
1331 dot_prev();
1332 case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..."
1333 dot_end();
1334 dot = char_insert(dot, '\n');
1335 } else {
1336 dot_begin(); // 0
1337 dot = char_insert(dot, '\n'); // i\n\033
1338 dot_prev(); // -
1339 }
1340 goto dc_i;
1341 break;
1342 case 'R': // R- continuous Replace char
1343 cmd_mode = 2;
1344 psb("-- Replace --");
1345 break;
1346 case 'X': // X- delete char before dot
1347 case 'x': // x- delete the current char
1348 case 's': // s- substitute the current char
1349 if (cmdcnt-- > 1) {
1350 do_cmd(c);
1351 } // repeat cnt
1352 dir = 0;
1353 if (c == 'X')
1354 dir = -1;
1355 if (dot[dir] != '\n') {
1356 if (c == 'X')
1357 dot--; // delete prev char
1358 dot = yank_delete(dot, dot, 0, YANKDEL); // delete char
1359 }
1360 if (c == 's')
1361 goto dc_i; // start insrting
1362 end_cmd_q(); // stop adding to q
1363 break;
1364 case 'Z': // Z- if modified, {write}; exit
1365 // ZZ means to save file (if necessary), then exit
1366 c1 = get_one_char();
1367 if (c1 != 'Z') {
1368 indicate_error(c);
1369 break;
1370 }
1371 if (file_modified == TRUE
1372#ifdef BB_FEATURE_VI_READONLY
1373 && readonly == FALSE
1374#endif /* BB_FEATURE_VI_READONLY */
1375 ) {
1376 cnt = file_write(cfn, text, end - 1);
1377 if (cnt == (end - 1 - text + 1)) {
1378 editing = 0;
1379 }
1380 } else {
1381 editing = 0;
1382 }
1383 break;
1384 case '^': // ^- move to first non-blank on line
1385 dot_begin();
1386 dot_skip_over_ws();
1387 break;
1388 case 'b': // b- back a word
1389 case 'e': // e- end of word
1390 if (cmdcnt-- > 1) {
1391 do_cmd(c);
1392 } // repeat cnt
1393 dir = FORWARD;
1394 if (c == 'b')
1395 dir = BACK;
1396 if ((dot + dir) < text || (dot + dir) > end - 1)
1397 break;
1398 dot += dir;
1399 if (isspace(*dot)) {
1400 dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS);
1401 }
1402 if (isalnum(*dot) || *dot == '_') {
1403 dot = skip_thing(dot, 1, dir, S_END_ALNUM);
1404 } else if (ispunct(*dot)) {
1405 dot = skip_thing(dot, 1, dir, S_END_PUNCT);
1406 }
1407 break;
1408 case 'c': // c- change something
1409 case 'd': // d- delete something
1410#ifdef BB_FEATURE_VI_YANKMARK
1411 case 'y': // y- yank something
1412 case 'Y': // Y- Yank a line
1413#endif /* BB_FEATURE_VI_YANKMARK */
1414 yf = YANKDEL; // assume either "c" or "d"
1415#ifdef BB_FEATURE_VI_YANKMARK
1416 if (c == 'y' || c == 'Y')
1417 yf = YANKONLY;
1418#endif /* BB_FEATURE_VI_YANKMARK */
1419 c1 = 'y';
1420 if (c != 'Y')
1421 c1 = get_one_char(); // get the type of thing to delete
1422 find_range(&p, &q, c1);
1423 if (c1 == 27) { // ESC- user changed mind and wants out
1424 c = c1 = 27; // Escape- do nothing
1425 } else if (strchr("wW", c1)) {
1426 if (c == 'c') {
1427 // don't include trailing WS as part of word
1428 while (isblnk(*q)) {
1429 if (q <= text || q[-1] == '\n')
1430 break;
1431 q--;
1432 }
1433 }
1434 dot = yank_delete(p, q, 0, yf); // delete word
1435 } else if (strchr("0bBeE$", c1)) {
1436 // single line copy text into a register and delete
1437 dot = yank_delete(p, q, 0, yf); // delete word
1438 } else if (strchr("cdykjHL+-{}\r\n", c1)) {
1439 // multiple line copy text into a register and delete
1440 dot = yank_delete(p, q, 1, yf); // delete lines
1441 if (c == 'd') {
1442 dot_begin();
1443 dot_skip_over_ws();
1444 }
1445 } else {
1446 // could not recognize object
1447 c = c1 = 27; // error-
1448 indicate_error(c);
1449 }
1450 if (c1 != 27) {
1451 // if CHANGING, not deleting, start inserting after the delete
1452 if (c == 'c') {
1453 strcpy((char *) buf, "Change");
1454 goto dc_i; // start inserting
1455 }
1456 if (c == 'd') {
1457 strcpy((char *) buf, "Delete");
1458 }
1459#ifdef BB_FEATURE_VI_YANKMARK
1460 if (c == 'y' || c == 'Y') {
1461 strcpy((char *) buf, "Yank");
1462 }
1463 p = reg[YDreg];
1464 q = p + strlen((char *) p);
1465 for (cnt = 0; p <= q; p++) {
1466 if (*p == '\n')
1467 cnt++;
1468 }
1469 psb("%s %d lines (%d chars) using [%c]",
1470 buf, cnt, strlen((char *) reg[YDreg]), what_reg());
1471#endif /* BB_FEATURE_VI_YANKMARK */
1472 end_cmd_q(); // stop adding to q
1473 }
1474 break;
1475 case 'k': // k- goto prev line, same col
1476 case VI_K_UP: // cursor key Up
1477 if (cmdcnt-- > 1) {
1478 do_cmd(c);
1479 } // repeat cnt
1480 dot_prev();
1481 dot = move_to_col(dot, ccol + offset); // try stay in same col
1482 break;
1483 case 'r': // r- replace the current char with user input
1484 c1 = get_one_char(); // get the replacement char
1485 if (*dot != '\n') {
1486 *dot = c1;
1487 file_modified = TRUE; // has the file been modified
1488 }
1489 end_cmd_q(); // stop adding to q
1490 break;
1491 case 'w': // w- forward a word
1492 if (cmdcnt-- > 1) {
1493 do_cmd(c);
1494 } // repeat cnt
1495 if (isalnum(*dot) || *dot == '_') { // we are on ALNUM
1496 dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM);
1497 } else if (ispunct(*dot)) { // we are on PUNCT
1498 dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT);
1499 }
1500 if (dot < end - 1)
1501 dot++; // move over word
1502 if (isspace(*dot)) {
1503 dot = skip_thing(dot, 2, FORWARD, S_OVER_WS);
1504 }
1505 break;
1506 case 'z': // z-
1507 c1 = get_one_char(); // get the replacement char
1508 cnt = 0;
1509 if (c1 == '.')
1510 cnt = (rows - 2) / 2; // put dot at center
1511 if (c1 == '-')
1512 cnt = rows - 2; // put dot at bottom
1513 screenbegin = begin_line(dot); // start dot at top
1514 dot_scroll(cnt, -1);
1515 break;
1516 case '|': // |- move to column "cmdcnt"
1517 dot = move_to_col(dot, cmdcnt - 1); // try to move to column
1518 break;
1519 case '~': // ~- flip the case of letters a-z -> A-Z
1520 if (cmdcnt-- > 1) {
1521 do_cmd(c);
1522 } // repeat cnt
1523 if (islower(*dot)) {
1524 *dot = toupper(*dot);
1525 file_modified = TRUE; // has the file been modified
1526 } else if (isupper(*dot)) {
1527 *dot = tolower(*dot);
1528 file_modified = TRUE; // has the file been modified
1529 }
1530 dot_right();
1531 end_cmd_q(); // stop adding to q
1532 break;
1533 //----- The Cursor and Function Keys -----------------------------
1534 case VI_K_HOME: // Cursor Key Home
1535 dot_begin();
1536 break;
1537 // The Fn keys could point to do_macro which could translate them
1538 case VI_K_FUN1: // Function Key F1
1539 case VI_K_FUN2: // Function Key F2
1540 case VI_K_FUN3: // Function Key F3
1541 case VI_K_FUN4: // Function Key F4
1542 case VI_K_FUN5: // Function Key F5
1543 case VI_K_FUN6: // Function Key F6
1544 case VI_K_FUN7: // Function Key F7
1545 case VI_K_FUN8: // Function Key F8
1546 case VI_K_FUN9: // Function Key F9
1547 case VI_K_FUN10: // Function Key F10
1548 case VI_K_FUN11: // Function Key F11
1549 case VI_K_FUN12: // Function Key F12
1550 break;
1551 }
1552
1553 dc1:
1554 // if text[] just became empty, add back an empty line
1555 if (end == text) {
1556 (void) char_insert(text, '\n'); // start empty buf with dummy line
1557 dot = text;
1558 }
1559 // it is OK for dot to exactly equal to end, otherwise check dot validity
1560 if (dot != end) {
1561 dot = bound_dot(dot); // make sure "dot" is valid
1562 }
1563#ifdef BB_FEATURE_VI_YANKMARK
1564 check_context(c); // update the current context
1565#endif /* BB_FEATURE_VI_YANKMARK */
1566
1567 if (!isdigit(c))
1568 cmdcnt = 0; // cmd was not a number, reset cmdcnt
1569 cnt = dot - begin_line(dot);
1570 // Try to stay off of the Newline
1571 if (*dot == '\n' && cnt > 0 && cmd_mode == 0)
1572 dot--;
1573}
1574
1575//----- The Colon commands -------------------------------------
1576#ifdef BB_FEATURE_VI_COLON
1577static Byte *get_address(Byte * p, int *addr) // get colon addr, if present
1578{
1579 int st;
1580 Byte *q;
1581
1582#ifdef BB_FEATURE_VI_YANKMARK
1583 Byte c;
1584#endif /* BB_FEATURE_VI_YANKMARK */
1585#ifdef BB_FEATURE_VI_SEARCH
1586 Byte *pat, buf[1024];
1587#endif /* BB_FEATURE_VI_SEARCH */
1588
1589 *addr = -1; // assume no addr
1590 if (*p == '.') { // the current line
1591 p++;
1592 q = begin_line(dot);
1593 *addr = count_lines(text, q);
1594#ifdef BB_FEATURE_VI_YANKMARK
1595 } else if (*p == '\'') { // is this a mark addr
1596 p++;
1597 c = tolower(*p);
1598 p++;
1599 if (c >= 'a' && c <= 'z') {
1600 // we have a mark
1601 c = c - 'a';
1602 q = mark[(int) c];
1603 if (q != NULL) { // is mark valid
1604 *addr = count_lines(text, q); // count lines
1605 }
1606 }
1607#endif /* BB_FEATURE_VI_YANKMARK */
1608#ifdef BB_FEATURE_VI_SEARCH
1609 } else if (*p == '/') { // a search pattern
1610 q = buf;
1611 for (p++; *p; p++) {
1612 if (*p == '/')
1613 break;
1614 *q++ = *p;
1615 *q = '\0';
1616 }
1617 pat = (Byte *) strdup((char *) buf); // save copy of pattern
1618 if (*p == '/')
1619 p++;
1620 q = char_search(dot, pat, FORWARD, FULL);
1621 if (q != NULL) {
1622 *addr = count_lines(text, q);
1623 }
1624 free(pat);
1625#endif /* BB_FEATURE_VI_SEARCH */
1626 } else if (*p == '$') { // the last line in file
1627 p++;
1628 q = begin_line(end - 1);
1629 *addr = count_lines(text, q);
1630 } else if (isdigit(*p)) { // specific line number
1631 sscanf((char *) p, "%d%n", addr, &st);
1632 p += st;
1633 } else { // I don't reconise this
1634 // unrecognised address- assume -1
1635 *addr = -1;
1636 }
1637 return (p);
1638}
1639
1640static void colon(Byte * buf)
1641{
1642 Byte c, *orig_buf, *buf1, *q, *r;
1643 Byte *fn, cmd[100], args[100];
1644 int i, l, li, ch, st, b, e;
1645 int useforce, forced;
1646
1647 // :3154 // if (-e line 3154) goto it else stay put
1648 // :4,33w! foo // write a portion of buffer to file "foo"
1649 // :w // write all of buffer to current file
1650 // :q // quit
1651 // :q! // quit- dont care about modified file
1652 // :'a,'z!sort -u // filter block through sort
1653 // :'f // goto mark "f"
1654 // :'fl // list literal the mark "f" line
1655 // :.r bar // read file "bar" into buffer before dot
1656 // :/123/,/abc/d // delete lines from "123" line to "abc" line
1657 // :/xyz/ // goto the "xyz" line
1658 // :s/find/replace/ // substitute pattern "find" with "replace"
1659 //
1660 if (strlen((char *) buf) <= 0)
1661 goto vc1;
1662 if (*buf == ':')
1663 buf++; // move past the ':'
1664
1665 forced = useforce = FALSE;
1666 li = st = ch = i = 0;
1667 b = e = -1;
1668 q = text; // assume 1,$ for the range
1669 r = end - 1;
1670 li = count_lines(text, end - 1);
1671 fn = cfn; // default to current file
1672
1673 // look for optional FIRST address(es) :. :1 :1,9 :'q,'a
1674 while (isblnk(*buf))
1675 buf++;
1676
1677 // get FIRST addr, if present
1678 buf = get_address(buf, &b);
1679 while (isblnk(*buf))
1680 buf++;
1681 if (*buf == ',') {
1682 buf++;
1683 while (isblnk(*buf))
1684 buf++;
1685 // look for SECOND address
1686 buf = get_address(buf, &e);
1687 }
1688 while (isblnk(*buf))
1689 buf++;
1690
1691 // remember orig command line
1692 orig_buf = buf;
1693
1694 // get the COMMAND into cmd[]
1695 buf1 = cmd;
1696 *buf1 = '\0';
1697 while (*buf != '\0') {
1698 if (isspace(*buf))
1699 break;
1700 *buf1++ = *buf++;
1701 *buf1 = '\0';
1702 }
1703 // get any ARGuments
1704 while (isblnk(*buf))
1705 buf++;
1706 strcpy((char *) args, (char *) buf);
1707 if (cmd[strlen((char *) cmd) - 1] == '!') {
1708 useforce = TRUE;
1709 cmd[strlen((char *) cmd) - 1] = '\0'; // get rid of !
1710 }
1711 if (b >= 0) {
1712 // if there is only one addr, then the addr
1713 // is the line number of the single line the
1714 // user wants. So, reset the end
1715 // pointer to point at end of the "b" line
1716 q = find_line(b); // what line is #b
1717 r = end_line(q);
1718 li = 1;
1719 }
1720 if (e >= 0) {
1721 // we were given two addrs. change the
1722 // end pointer to the addr given by user.
1723 r = find_line(e); // what line is #e
1724 r = end_line(r);
1725 li = e - b + 1;
1726 }
1727 // ------------ now look for the command ------------
1728 i = strlen((char *) cmd);
1729 if (i == 0) { // :123CR goto line #123
1730 if (b >= 0) {
1731 dot = find_line(b); // what line is #b
1732 dot_skip_over_ws();
1733 }
1734 } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address
1735 if (b < 0) { // no addr given- use defaults
1736 b = e = count_lines(text, dot);
1737 }
1738 psb("%d", b);
1739 } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines
1740 if (b < 0) { // no addr given- use defaults
1741 q = begin_line(dot); // assume .,. for the range
1742 r = end_line(dot);
1743 }
1744 dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines
1745 dot_skip_over_ws();
1746 } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file
1747 // don't exit if the file been modified
1748 if (file_modified == TRUE && useforce != TRUE) {
1749 psbs("No write since last change (:edit! overrides)");
1750 goto vc1;
1751 }
1752 fn = args;
1753 if (strlen((char *) fn) <= 0) {
1754 // no file name given, re-edit current file
1755 fn = cfn;
1756 }
1757 if (cfn != 0)
1758 free(cfn);
1759 cfn = (Byte *) strdup((char *) fn); // make this the current file
1760 // delete all the contents of text[]
1761 new_text(2 * file_size(fn));
1762 screenbegin = dot = end = text;
1763 // insert new file
1764 if (fn != 0) {
1765 ch = file_insert(fn, text, file_size(fn));
1766 }
1767 file_modified = FALSE;
1768#ifdef BB_FEATURE_VI_YANKMARK
1769 if (reg[Ureg] != 0)
1770 free(reg[Ureg]); // free orig line reg- for 'U'
1771 if (reg[YDreg] != 0)
1772 free(reg[YDreg]); // free default yank/delete register
1773 for (li = 0; li < 28; li++) {
1774 mark[li] = 0;
1775 } // init the marks
1776#endif /* BB_FEATURE_VI_YANKMARK */
1777 // how many lines in text[]?
1778 li = count_lines(text, end - 1);
1779 psb("\"%s\" %dL, %dC", cfn, li, ch);
1780 } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this
1781 if (b != -1 || e != -1) {
1782 ni((Byte *) "No address allowed on this command");
1783 goto vc1;
1784 }
1785 if (strlen((char *) args) > 0) {
1786 // user wants a new filename
1787 if (cfn != NULL)
1788 free(cfn);
1789 cfn = (Byte *) strdup((char *) args);
1790 } else {
1791 // user wants file status info
1792 edit_status();
1793 }
1794 } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available
1795 // print out values of all features
1796 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1797 clear_to_eol(); // clear the line
1798 cookmode();
1799 show_help();
1800 rawmode();
1801 Hit_Return();
1802 } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line
1803 if (b < 0) { // no addr given- use defaults
1804 q = begin_line(dot); // assume .,. for the range
1805 r = end_line(dot);
1806 }
1807 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1808 clear_to_eol(); // clear the line
1809 write(1, "\r\n", 2);
1810 for (; q <= r; q++) {
1811 c = *q;
1812 if (c > '~')
1813 standout_start();
1814 if (c == '\n') {
1815 write(1, "$\r", 2);
1816 } else if (*q < ' ') {
1817 write(1, "^", 1);
1818 c += '@';
1819 }
1820 write(1, &c, 1);
1821 if (c > '~')
1822 standout_end();
1823 }
1824#ifdef BB_FEATURE_VI_SET
1825 vc2:
1826#endif /* BB_FEATURE_VI_SET */
1827 Hit_Return();
1828 } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit
1829 (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file
1830 if (useforce == TRUE) {
1831 // force end of argv list
1832 if (*cmd == 'q') {
1833 optind = save_argc;
1834 }
1835 editing = 0;
1836 goto vc1;
1837 }
1838 // don't exit if the file been modified
1839 if (file_modified == TRUE) {
1840 psbs("No write since last change (:%s! overrides)",
1841 (*cmd == 'q' ? "quit" : "next"));
1842 goto vc1;
1843 }
1844 // are there other file to edit
1845 if (*cmd == 'q' && optind < save_argc - 1) {
1846 psbs("%d more file to edit", (save_argc - optind - 1));
1847 goto vc1;
1848 }
1849 if (*cmd == 'n' && optind >= save_argc - 1) {
1850 psbs("No more files to edit");
1851 goto vc1;
1852 }
1853 editing = 0;
1854 } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[]
1855 fn = args;
1856 if (strlen((char *) fn) <= 0) {
1857 psbs("No filename given");
1858 goto vc1;
1859 }
1860 if (b < 0) { // no addr given- use defaults
1861 q = begin_line(dot); // assume "dot"
1862 }
1863 // read after current line- unless user said ":0r foo"
1864 if (b != 0)
1865 q = next_line(q);
1866 ch = file_insert(fn, q, file_size(fn));
1867 if (ch < 0)
1868 goto vc1; // nothing was inserted
1869 // how many lines in text[]?
1870 li = count_lines(q, q + ch - 1);
1871 psb("\"%s\" %dL, %dC", fn, li, ch);
1872 if (ch > 0) {
1873 // if the insert is before "dot" then we need to update
1874 if (q <= dot)
1875 dot += ch;
1876 file_modified = TRUE;
1877 }
1878 } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args
1879 if (file_modified == TRUE && useforce != TRUE) {
1880 psbs("No write since last change (:rewind! overrides)");
1881 } else {
1882 // reset the filenames to edit
1883 optind = fn_start - 1;
1884 editing = 0;
1885 }
1886#ifdef BB_FEATURE_VI_SET
1887 } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features
1888 i = 0; // offset into args
1889 if (strlen((char *) args) == 0) {
1890 // print out values of all options
1891 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
1892 clear_to_eol(); // clear the line
1893 printf("----------------------------------------\r\n");
1894#ifdef BB_FEATURE_VI_SETOPTS
1895 if (!autoindent)
1896 printf("no");
1897 printf("autoindent ");
1898 if (!ignorecase)
1899 printf("no");
1900 printf("ignorecase ");
1901 if (!showmatch)
1902 printf("no");
1903 printf("showmatch ");
1904 printf("tabstop=%d ", tabstop);
1905#endif /* BB_FEATURE_VI_SETOPTS */
1906 printf("\r\n");
1907 goto vc2;
1908 }
1909 if (strncasecmp((char *) args, "no", 2) == 0)
1910 i = 2; // ":set noautoindent"
1911#ifdef BB_FEATURE_VI_SETOPTS
1912 if (strncasecmp((char *) args + i, "autoindent", 10) == 0 ||
1913 strncasecmp((char *) args + i, "ai", 2) == 0) {
1914 autoindent = (i == 2) ? 0 : 1;
1915 }
1916 if (strncasecmp((char *) args + i, "ignorecase", 10) == 0 ||
1917 strncasecmp((char *) args + i, "ic", 2) == 0) {
1918 ignorecase = (i == 2) ? 0 : 1;
1919 }
1920 if (strncasecmp((char *) args + i, "showmatch", 9) == 0 ||
1921 strncasecmp((char *) args + i, "sm", 2) == 0) {
1922 showmatch = (i == 2) ? 0 : 1;
1923 }
1924 if (strncasecmp((char *) args + i, "tabstop", 7) == 0) {
1925 sscanf(strchr((char *) args + i, '='), "=%d", &ch);
1926 if (ch > 0 && ch < columns - 1)
1927 tabstop = ch;
1928 }
1929#endif /* BB_FEATURE_VI_SETOPTS */
1930#endif /* BB_FEATURE_VI_SET */
1931#ifdef BB_FEATURE_VI_SEARCH
1932 } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern
1933 Byte *ls, *F, *R;
1934 int gflag;
1935
1936 // F points to the "find" pattern
1937 // R points to the "replace" pattern
1938 // replace the cmd line delimiters "/" with NULLs
1939 gflag = 0; // global replace flag
1940 c = orig_buf[1]; // what is the delimiter
1941 F = orig_buf + 2; // start of "find"
1942 R = (Byte *) strchr((char *) F, c); // middle delimiter
1943 *R++ = '\0'; // terminate "find"
1944 buf1 = (Byte *) strchr((char *) R, c);
1945 *buf1++ = '\0'; // terminate "replace"
1946 if (*buf1 == 'g') { // :s/foo/bar/g
1947 buf1++;
1948 gflag++; // turn on gflag
1949 }
1950 q = begin_line(q);
1951 if (b < 0) { // maybe :s/foo/bar/
1952 q = begin_line(dot); // start with cur line
1953 b = count_lines(text, q); // cur line number
1954 }
1955 if (e < 0)
1956 e = b; // maybe :.s/foo/bar/
1957 for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0
1958 ls = q; // orig line start
1959 vc4:
1960 buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find"
1961 if (buf1 != NULL) {
1962 // we found the "find" pattern- delete it
1963 (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1);
1964 // inset the "replace" patern
1965 (void) string_insert(buf1, R); // insert the string
1966 // check for "global" :s/foo/bar/g
1967 if (gflag == 1) {
1968 if ((buf1 + strlen((char *) R)) < end_line(ls)) {
1969 q = buf1 + strlen((char *) R);
1970 goto vc4; // don't let q move past cur line
1971 }
1972 }
1973 }
1974 q = next_line(ls);
1975 }
1976#endif /* BB_FEATURE_VI_SEARCH */
1977 } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version
1978 psb("%s", vi_Version);
1979 } else if ((strncasecmp((char *) cmd, "write", i) == 0) || // write text to file
1980 (strncasecmp((char *) cmd, "wq", i) == 0)) { // write text to file
1981 // is there a file name to write to?
1982 if (strlen((char *) args) > 0) {
1983 fn = args;
1984 }
1985#ifdef BB_FEATURE_VI_READONLY
1986 if (readonly == TRUE) {
1987 psbs("\"%s\" File is read only", fn);
1988 goto vc3;
1989 }
1990#endif /* BB_FEATURE_VI_READONLY */
1991 // how many lines in text[]?
1992 li = count_lines(q, r);
1993 ch = r - q + 1;
1994 if (useforce == TRUE) {
1995 // if "fn" is not write-able, chmod u+w
1996 // sprintf(syscmd, "chmod u+w %s", fn);
1997 // system(syscmd);
1998 forced = TRUE;
1999 }
2000 l = file_write(fn, q, r);
2001 if (useforce == TRUE && forced == TRUE) {
2002 // chmod u-w
2003 // sprintf(syscmd, "chmod u-w %s", fn);
2004 // system(syscmd);
2005 forced = FALSE;
2006 }
2007 psb("\"%s\" %dL, %dC", fn, li, l);
2008 if (q == text && r == end - 1 && l == ch)
2009 file_modified = FALSE;
2010 if (cmd[1] == 'q' && l == ch) {
2011 editing = 0;
2012 }
2013#ifdef BB_FEATURE_VI_READONLY
2014 vc3:;
2015#endif /* BB_FEATURE_VI_READONLY */
2016#ifdef BB_FEATURE_VI_YANKMARK
2017 } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines
2018 if (b < 0) { // no addr given- use defaults
2019 q = begin_line(dot); // assume .,. for the range
2020 r = end_line(dot);
2021 }
2022 text_yank(q, r, YDreg);
2023 li = count_lines(q, r);
2024 psb("Yank %d lines (%d chars) into [%c]",
2025 li, strlen((char *) reg[YDreg]), what_reg());
2026#endif /* BB_FEATURE_VI_YANKMARK */
2027 } else {
2028 // cmd unknown
2029 ni((Byte *) cmd);
2030 }
2031 vc1:
2032 dot = bound_dot(dot); // make sure "dot" is valid
2033 return;
2034}
2035
2036static void Hit_Return(void)
2037{
2038 char c;
2039
2040 standout_start(); // start reverse video
2041 write(1, "[Hit return to continue]", 24);
2042 standout_end(); // end reverse video
2043 while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */
2044 ;
2045 redraw(TRUE); // force redraw all
2046}
2047#endif /* BB_FEATURE_VI_COLON */
2048
2049//----- Synchronize the cursor to Dot --------------------------
2050static void sync_cursor(Byte * d, int *row, int *col)
2051{
2052 Byte *beg_cur, *end_cur; // begin and end of "d" line
2053 Byte *beg_scr, *end_scr; // begin and end of screen
2054 Byte *tp;
2055 int cnt, ro, co;
2056
2057 beg_cur = begin_line(d); // first char of cur line
2058 end_cur = end_line(d); // last char of cur line
2059
2060 beg_scr = end_scr = screenbegin; // first char of screen
2061 end_scr = end_screen(); // last char of screen
2062
2063 if (beg_cur < screenbegin) {
2064 // "d" is before top line on screen
2065 // how many lines do we have to move
2066 cnt = count_lines(beg_cur, screenbegin);
2067 sc1:
2068 screenbegin = beg_cur;
2069 if (cnt > (rows - 1) / 2) {
2070 // we moved too many lines. put "dot" in middle of screen
2071 for (cnt = 0; cnt < (rows - 1) / 2; cnt++) {
2072 screenbegin = prev_line(screenbegin);
2073 }
2074 }
2075 } else if (beg_cur > end_scr) {
2076 // "d" is after bottom line on screen
2077 // how many lines do we have to move
2078 cnt = count_lines(end_scr, beg_cur);
2079 if (cnt > (rows - 1) / 2)
2080 goto sc1; // too many lines
2081 for (ro = 0; ro < cnt - 1; ro++) {
2082 // move screen begin the same amount
2083 screenbegin = next_line(screenbegin);
2084 // now, move the end of screen
2085 end_scr = next_line(end_scr);
2086 end_scr = end_line(end_scr);
2087 }
2088 }
2089 // "d" is on screen- find out which row
2090 tp = screenbegin;
2091 for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row
2092 if (tp == beg_cur)
2093 break;
2094 tp = next_line(tp);
2095 }
2096
2097 // find out what col "d" is on
2098 co = 0;
2099 do { // drive "co" to correct column
2100 if (*tp == '\n' || *tp == '\0')
2101 break;
2102 if (*tp == '\t') {
2103 // 7 - (co % 8 )
2104 co += ((tabstop - 1) - (co % tabstop));
2105 } else if (*tp < ' ') {
2106 co++; // display as ^X, use 2 columns
2107 }
2108 } while (tp++ < d && ++co);
2109
2110 // "co" is the column where "dot" is.
2111 // The screen has "columns" columns.
2112 // The currently displayed columns are 0+offset -- columns+ofset
2113 // |-------------------------------------------------------------|
2114 // ^ ^ ^
2115 // offset | |------- columns ----------------|
2116 //
2117 // If "co" is already in this range then we do not have to adjust offset
2118 // but, we do have to subtract the "offset" bias from "co".
2119 // If "co" is outside this range then we have to change "offset".
2120 // If the first char of a line is a tab the cursor will try to stay
2121 // in column 7, but we have to set offset to 0.
2122
2123 if (co < 0 + offset) {
2124 offset = co;
2125 }
2126 if (co >= columns + offset) {
2127 offset = co - columns + 1;
2128 }
2129 // if the first char of the line is a tab, and "dot" is sitting on it
2130 // force offset to 0.
2131 if (d == beg_cur && *d == '\t') {
2132 offset = 0;
2133 }
2134 co -= offset;
2135
2136 *row = ro;
2137 *col = co;
2138}
2139
2140//----- Text Movement Routines ---------------------------------
2141static Byte *begin_line(Byte * p) // return pointer to first char cur line
2142{
2143 while (p > text && p[-1] != '\n')
2144 p--; // go to cur line B-o-l
2145 return (p);
2146}
2147
2148static Byte *end_line(Byte * p) // return pointer to NL of cur line line
2149{
2150 while (p < end - 1 && *p != '\n')
2151 p++; // go to cur line E-o-l
2152 return (p);
2153}
2154
2155static Byte *dollar_line(Byte * p) // return pointer to just before NL line
2156{
2157 while (p < end - 1 && *p != '\n')
2158 p++; // go to cur line E-o-l
2159 // Try to stay off of the Newline
2160 if (*p == '\n' && (p - begin_line(p)) > 0)
2161 p--;
2162 return (p);
2163}
2164
2165static Byte *prev_line(Byte * p) // return pointer first char prev line
2166{
2167 p = begin_line(p); // goto begining of cur line
2168 if (p[-1] == '\n' && p > text)
2169 p--; // step to prev line
2170 p = begin_line(p); // goto begining of prev line
2171 return (p);
2172}
2173
2174static Byte *next_line(Byte * p) // return pointer first char next line
2175{
2176 p = end_line(p);
2177 if (*p == '\n' && p < end - 1)
2178 p++; // step to next line
2179 return (p);
2180}
2181
2182//----- Text Information Routines ------------------------------
2183static Byte *end_screen(void)
2184{
2185 Byte *q;
2186 int cnt;
2187
2188 // find new bottom line
2189 q = screenbegin;
2190 for (cnt = 0; cnt < rows - 2; cnt++)
2191 q = next_line(q);
2192 q = end_line(q);
2193 return (q);
2194}
2195
2196static int count_lines(Byte * start, Byte * stop) // count line from start to stop
2197{
2198 Byte *q;
2199 int cnt;
2200
2201 if (stop < start) { // start and stop are backwards- reverse them
2202 q = start;
2203 start = stop;
2204 stop = q;
2205 }
2206 cnt = 0;
2207 stop = end_line(stop); // get to end of this line
2208 for (q = start; q <= stop && q <= end - 1; q++) {
2209 if (*q == '\n')
2210 cnt++;
2211 }
2212 return (cnt);
2213}
2214
2215static Byte *find_line(int li) // find begining of line #li
2216{
2217 Byte *q;
2218
2219 for (q = text; li > 1; li--) {
2220 q = next_line(q);
2221 }
2222 return (q);
2223}
2224
2225//----- Dot Movement Routines ----------------------------------
2226static void dot_left(void)
2227{
2228 if (dot > text && dot[-1] != '\n')
2229 dot--;
2230}
2231
2232static void dot_right(void)
2233{
2234 if (dot < end - 1 && *dot != '\n')
2235 dot++;
2236}
2237
2238static void dot_begin(void)
2239{
2240 dot = begin_line(dot); // return pointer to first char cur line
2241}
2242
2243static void dot_end(void)
2244{
2245 dot = end_line(dot); // return pointer to last char cur line
2246}
2247
2248static Byte *move_to_col(Byte * p, int l)
2249{
2250 int co;
2251
2252 p = begin_line(p);
2253 co = 0;
2254 do {
2255 if (*p == '\n' || *p == '\0')
2256 break;
2257 if (*p == '\t') {
2258 // 7 - (co % 8 )
2259 co += ((tabstop - 1) - (co % tabstop));
2260 } else if (*p < ' ') {
2261 co++; // display as ^X, use 2 columns
2262 }
2263 } while (++co <= l && p++ < end);
2264 return (p);
2265}
2266
2267static void dot_next(void)
2268{
2269 dot = next_line(dot);
2270}
2271
2272static void dot_prev(void)
2273{
2274 dot = prev_line(dot);
2275}
2276
2277static void dot_scroll(int cnt, int dir)
2278{
2279 Byte *q;
2280
2281 for (; cnt > 0; cnt--) {
2282 if (dir < 0) {
2283 // scroll Backwards
2284 // ctrl-Y scroll up one line
2285 screenbegin = prev_line(screenbegin);
2286 } else {
2287 // scroll Forwards
2288 // ctrl-E scroll down one line
2289 screenbegin = next_line(screenbegin);
2290 }
2291 }
2292 // make sure "dot" stays on the screen so we dont scroll off
2293 if (dot < screenbegin)
2294 dot = screenbegin;
2295 q = end_screen(); // find new bottom line
2296 if (dot > q)
2297 dot = begin_line(q); // is dot is below bottom line?
2298 dot_skip_over_ws();
2299}
2300
2301static void dot_skip_over_ws(void)
2302{
2303 // skip WS
2304 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
2305 dot++;
2306}
2307
2308static void dot_delete(void) // delete the char at 'dot'
2309{
2310 (void) text_hole_delete(dot, dot);
2311}
2312
2313static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end"
2314{
2315 if (p >= end && end > text) {
2316 p = end - 1;
2317 indicate_error('1');
2318 }
2319 if (p < text) {
2320 p = text;
2321 indicate_error('2');
2322 }
2323 return (p);
2324}
2325
2326//----- Helper Utility Routines --------------------------------
2327
2328//----------------------------------------------------------------
2329//----- Char Routines --------------------------------------------
2330/* Chars that are part of a word-
2331 * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
2332 * Chars that are Not part of a word (stoppers)
2333 * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~
2334 * Chars that are WhiteSpace
2335 * TAB NEWLINE VT FF RETURN SPACE
2336 * DO NOT COUNT NEWLINE AS WHITESPACE
2337 */
2338
2339static Byte *new_screen(int ro, int co)
2340{
2341 if (screen != 0)
2342 free(screen);
2343 screensize = ro * co + 8;
2344 screen = (Byte *) malloc(screensize);
2345 return (screen);
2346}
2347
2348static Byte *new_text(int size)
2349{
2350 if (size < 10240)
2351 size = 10240; // have a minimum size for new files
2352 if (text != 0) {
2353 //text -= 4;
2354 free(text);
2355 }
2356 text = (Byte *) malloc(size + 8);
2357 memset(text, '\0', size); // clear new text[]
2358 //text += 4; // leave some room for "oops"
2359 textend = text + size - 1;
2360 //textend -= 4; // leave some root for "oops"
2361 return (text);
2362}
2363
2364#ifdef BB_FEATURE_VI_SEARCH
2365static int mycmp(Byte * s1, Byte * s2, int len)
2366{
2367 int i;
2368
2369 i = strncmp((char *) s1, (char *) s2, len);
2370#ifdef BB_FEATURE_VI_SETOPTS
2371 if (ignorecase) {
2372 i = strncasecmp((char *) s1, (char *) s2, len);
2373 }
2374#endif /* BB_FEATURE_VI_SETOPTS */
2375 return (i);
2376}
2377
2378static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p
2379{
2380#ifndef REGEX_SEARCH
2381 Byte *start, *stop;
2382 int len;
2383
2384 len = strlen((char *) pat);
2385 if (dir == FORWARD) {
2386 stop = end - 1; // assume range is p - end-1
2387 if (range == LIMITED)
2388 stop = next_line(p); // range is to next line
2389 for (start = p; start < stop; start++) {
2390 if (mycmp(start, pat, len) == 0) {
2391 return (start);
2392 }
2393 }
2394 } else if (dir == BACK) {
2395 stop = text; // assume range is text - p
2396 if (range == LIMITED)
2397 stop = prev_line(p); // range is to prev line
2398 for (start = p - len; start >= stop; start--) {
2399 if (mycmp(start, pat, len) == 0) {
2400 return (start);
2401 }
2402 }
2403 }
2404 // pattern not found
2405 return (NULL);
2406#else /*REGEX_SEARCH */
2407 char *q;
2408 struct re_pattern_buffer preg;
2409 int i;
2410 int size, range;
2411
2412 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2413 preg.translate = 0;
2414 preg.fastmap = 0;
2415 preg.buffer = 0;
2416 preg.allocated = 0;
2417
2418 // assume a LIMITED forward search
2419 q = next_line(p);
2420 q = end_line(q);
2421 q = end - 1;
2422 if (dir == BACK) {
2423 q = prev_line(p);
2424 q = text;
2425 }
2426 // count the number of chars to search over, forward or backward
2427 size = q - p;
2428 if (size < 0)
2429 size = p - q;
2430 // RANGE could be negative if we are searching backwards
2431 range = q - p;
2432
2433 q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg);
2434 if (q != 0) {
2435 // The pattern was not compiled
2436 psbs("bad search pattern: \"%s\": %s", pat, q);
2437 i = 0; // return p if pattern not compiled
2438 goto cs1;
2439 }
2440
2441 q = p;
2442 if (range < 0) {
2443 q = p - size;
2444 if (q < text)
2445 q = text;
2446 }
2447 // search for the compiled pattern, preg, in p[]
2448 // range < 0- search backward
2449 // range > 0- search forward
2450 // 0 < start < size
2451 // re_search() < 0 not found or error
2452 // re_search() > 0 index of found pattern
2453 // struct pattern char int int int struct reg
2454 // re_search (*pattern_buffer, *string, size, start, range, *regs)
2455 i = re_search(&preg, q, size, 0, range, 0);
2456 if (i == -1) {
2457 p = 0;
2458 i = 0; // return NULL if pattern not found
2459 }
2460 cs1:
2461 if (dir == FORWARD) {
2462 p = p + i;
2463 } else {
2464 p = p - i;
2465 }
2466 return (p);
2467#endif /*REGEX_SEARCH */
2468}
2469#endif /* BB_FEATURE_VI_SEARCH */
2470
2471static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p'
2472{
2473 if (c == 22) { // Is this an ctrl-V?
2474 p = stupid_insert(p, '^'); // use ^ to indicate literal next
2475 p--; // backup onto ^
2476 refresh(FALSE); // show the ^
2477 c = get_one_char();
2478 *p = c;
2479 p++;
2480 file_modified = TRUE; // has the file been modified
2481 } else if (c == 27) { // Is this an ESC?
2482 cmd_mode = 0;
2483 cmdcnt = 0;
2484 end_cmd_q(); // stop adding to q
2485 *status_buffer = '\0'; // clear the status buffer
2486 if (p[-1] != '\n') {
2487 p--;
2488 }
2489 } else if (c == erase_char) { // Is this a BS
2490 // 123456789
2491 if (p[-1] != '\n') {
2492 p--;
2493 p = text_hole_delete(p, p); // shrink buffer 1 char
2494#ifdef BB_FEATURE_VI_DOT_CMD
2495 // also rmove char from last_modifying_cmd
2496 if (strlen((char *) last_modifying_cmd) > 0) {
2497 Byte *q;
2498
2499 q = last_modifying_cmd;
2500 q[strlen((char *) q) - 1] = '\0'; // erase BS
2501 q[strlen((char *) q) - 1] = '\0'; // erase prev char
2502 }
2503#endif /* BB_FEATURE_VI_DOT_CMD */
2504 }
2505 } else {
2506 // insert a char into text[]
2507 Byte *sp; // "save p"
2508
2509 if (c == 13)
2510 c = '\n'; // translate \r to \n
2511 sp = p; // remember addr of insert
2512 p = stupid_insert(p, c); // insert the char
2513#ifdef BB_FEATURE_VI_SETOPTS
2514 if (showmatch && strchr(")]}", *sp) != NULL) {
2515 showmatching(sp);
2516 }
2517 if (autoindent && c == '\n') { // auto indent the new line
2518 Byte *q;
2519
2520 q = prev_line(p); // use prev line as templet
2521 for (; isblnk(*q); q++) {
2522 p = stupid_insert(p, *q); // insert the char
2523 }
2524 }
2525#endif /* BB_FEATURE_VI_SETOPTS */
2526 }
2527 return (p);
2528}
2529
2530static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p'
2531{
2532 p = text_hole_make(p, 1);
2533 if (p != 0) {
2534 *p = c;
2535 file_modified = TRUE; // has the file been modified
2536 p++;
2537 }
2538 return (p);
2539}
2540
2541static Byte find_range(Byte ** start, Byte ** stop, Byte c)
2542{
2543 Byte *save_dot, *p, *q;
2544 int cnt;
2545
2546 save_dot = dot;
2547 p = q = dot;
2548
2549 if (strchr("cdy><", c)) {
2550 p = q = begin_line(p);
2551 for (cnt = 1; cnt < cmdcnt; cnt++) {
2552 q = next_line(q);
2553 }
2554 q = end_line(q);
2555 } else if (strchr("$0bBeE", c)) {
2556 do_cmd(c); // execute movement cmd
2557 q = dot;
2558 } else if (strchr("wW", c)) {
2559 do_cmd(c); // execute movement cmd
2560 if (dot > text)
2561 dot--; // move back off of next word
2562 if (dot > text && *dot == '\n')
2563 dot--; // stay off NL
2564 q = dot;
2565 } else if (strchr("H-k{", c)) {
2566 q = end_line(dot); // find NL
2567 do_cmd(c); // execute movement cmd
2568 dot_begin();
2569 p = dot;
2570 } else if (strchr("L+j}\r\n", c)) {
2571 p = begin_line(dot);
2572 do_cmd(c); // execute movement cmd
2573 dot_end(); // find NL
2574 q = dot;
2575 } else {
2576 c = 27; // error- return an ESC char
2577 //break;
2578 }
2579 *start = p;
2580 *stop = q;
2581 if (q < p) {
2582 *start = q;
2583 *stop = p;
2584 }
2585 dot = save_dot;
2586 return (c);
2587}
2588
2589static int st_test(Byte * p, int type, int dir, Byte * tested)
2590{
2591 Byte c, c0, ci;
2592 int test, inc;
2593
2594 inc = dir;
2595 c = c0 = p[0];
2596 ci = p[inc];
2597 test = 0;
2598
2599 if (type == S_BEFORE_WS) {
2600 c = ci;
2601 test = ((!isspace(c)) || c == '\n');
2602 }
2603 if (type == S_TO_WS) {
2604 c = c0;
2605 test = ((!isspace(c)) || c == '\n');
2606 }
2607 if (type == S_OVER_WS) {
2608 c = c0;
2609 test = ((isspace(c)));
2610 }
2611 if (type == S_END_PUNCT) {
2612 c = ci;
2613 test = ((ispunct(c)));
2614 }
2615 if (type == S_END_ALNUM) {
2616 c = ci;
2617 test = ((isalnum(c)) || c == '_');
2618 }
2619 *tested = c;
2620 return (test);
2621}
2622
2623static Byte *skip_thing(Byte * p, int linecnt, int dir, int type)
2624{
2625 Byte c;
2626
2627 while (st_test(p, type, dir, &c)) {
2628 // make sure we limit search to correct number of lines
2629 if (c == '\n' && --linecnt < 1)
2630 break;
2631 if (dir >= 0 && p >= end - 1)
2632 break;
2633 if (dir < 0 && p <= text)
2634 break;
2635 p += dir; // move to next char
2636 }
2637 return (p);
2638}
2639
2640// find matching char of pair () [] {}
2641static Byte *find_pair(Byte * p, Byte c)
2642{
2643 Byte match, *q;
2644 int dir, level;
2645
2646 match = ')';
2647 level = 1;
2648 dir = 1; // assume forward
2649 switch (c) {
2650 case '(':
2651 match = ')';
2652 break;
2653 case '[':
2654 match = ']';
2655 break;
2656 case '{':
2657 match = '}';
2658 break;
2659 case ')':
2660 match = '(';
2661 dir = -1;
2662 break;
2663 case ']':
2664 match = '[';
2665 dir = -1;
2666 break;
2667 case '}':
2668 match = '{';
2669 dir = -1;
2670 break;
2671 }
2672 for (q = p + dir; text <= q && q < end; q += dir) {
2673 // look for match, count levels of pairs (( ))
2674 if (*q == c)
2675 level++; // increase pair levels
2676 if (*q == match)
2677 level--; // reduce pair level
2678 if (level == 0)
2679 break; // found matching pair
2680 }
2681 if (level != 0)
2682 q = NULL; // indicate no match
2683 return (q);
2684}
2685
2686#ifdef BB_FEATURE_VI_SETOPTS
2687// show the matching char of a pair, () [] {}
2688static void showmatching(Byte * p)
2689{
2690 Byte *q, *save_dot;
2691
2692 // we found half of a pair
2693 q = find_pair(p, *p); // get loc of matching char
2694 if (q == NULL) {
2695 indicate_error('3'); // no matching char
2696 } else {
2697 // "q" now points to matching pair
2698 save_dot = dot; // remember where we are
2699 dot = q; // go to new loc
2700 refresh(FALSE); // let the user see it
2701 (void) mysleep(40); // give user some time
2702 dot = save_dot; // go back to old loc
2703 refresh(FALSE);
2704 }
2705}
2706#endif /* BB_FEATURE_VI_SETOPTS */
2707
2708// open a hole in text[]
2709static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole
2710{
2711 Byte *src, *dest;
2712 int cnt;
2713
2714 if (size <= 0)
2715 goto thm0;
2716 src = p;
2717 dest = p + size;
2718 cnt = end - src; // the rest of buffer
2719 if (memmove(dest, src, cnt) != dest) {
2720 psbs("can't create room for new characters");
2721 }
2722 memset(p, ' ', size); // clear new hole
2723 end = end + size; // adjust the new END
2724 file_modified = TRUE; // has the file been modified
2725 thm0:
2726 return (p);
2727}
2728
2729// close a hole in text[]
2730static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive
2731{
2732 Byte *src, *dest;
2733 int cnt, hole_size;
2734
2735 // move forwards, from beginning
2736 // assume p <= q
2737 src = q + 1;
2738 dest = p;
2739 if (q < p) { // they are backward- swap them
2740 src = p + 1;
2741 dest = q;
2742 }
2743 hole_size = q - p + 1;
2744 cnt = end - src;
2745 if (src < text || src > end)
2746 goto thd0;
2747 if (dest < text || dest >= end)
2748 goto thd0;
2749 if (src >= end)
2750 goto thd_atend; // just delete the end of the buffer
2751 if (memmove(dest, src, cnt) != dest) {
2752 psbs("can't delete the character");
2753 }
2754 thd_atend:
2755 end = end - hole_size; // adjust the new END
2756 if (dest >= end)
2757 dest = end - 1; // make sure dest in below end-1
2758 if (end <= text)
2759 dest = end = text; // keep pointers valid
2760 file_modified = TRUE; // has the file been modified
2761 thd0:
2762 return (dest);
2763}
2764
2765// copy text into register, then delete text.
2766// if dist <= 0, do not include, or go past, a NewLine
2767//
2768static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf)
2769{
2770 Byte *p;
2771
2772 // make sure start <= stop
2773 if (start > stop) {
2774 // they are backwards, reverse them
2775 p = start;
2776 start = stop;
2777 stop = p;
2778 }
2779 if (dist <= 0) {
2780 // we can not cross NL boundaries
2781 p = start;
2782 if (*p == '\n')
2783 return (p);
2784 // dont go past a NewLine
2785 for (; p + 1 <= stop; p++) {
2786 if (p[1] == '\n') {
2787 stop = p; // "stop" just before NewLine
2788 break;
2789 }
2790 }
2791 }
2792 p = start;
2793#ifdef BB_FEATURE_VI_YANKMARK
2794 text_yank(start, stop, YDreg);
2795#endif /* BB_FEATURE_VI_YANKMARK */
2796 if (yf == YANKDEL) {
2797 p = text_hole_delete(start, stop);
2798 } // delete lines
2799 return (p);
2800}
2801
2802static void show_help(void)
2803{
2804 printf("These features are available:\n");
2805#ifdef BB_FEATURE_VI_SEARCH
2806 printf("\tPattern searches with / and ?\n");
2807#endif /* BB_FEATURE_VI_SEARCH */
2808#ifdef BB_FEATURE_VI_DOT_CMD
2809 printf("\tLast command repeat with \'.\'\n");
2810#endif /* BB_FEATURE_VI_DOT_CMD */
2811#ifdef BB_FEATURE_VI_YANKMARK
2812 printf("\tLine marking with 'x\n");
2813 printf("\tNamed buffers with \"x\n");
2814#endif /* BB_FEATURE_VI_YANKMARK */
2815#ifdef BB_FEATURE_VI_READONLY
2816 printf("\tReadonly with -R command line arg\n");
2817#endif /* BB_FEATURE_VI_READONLY */
2818#ifdef BB_FEATURE_VI_SET
2819 printf("\tSome colon mode commands with \':\'\n");
2820#endif /* BB_FEATURE_VI_SET */
2821#ifdef BB_FEATURE_VI_SETOPTS
2822 printf("\tSettable options with \":set\"\n");
2823#endif /* BB_FEATURE_VI_SETOPTS */
2824#ifdef BB_FEATURE_VI_USE_SIGNALS
2825 printf("\tSignal catching- ^C\n");
2826 printf("\tJob suspend and resume with ^Z\n");
2827#endif /* BB_FEATURE_VI_USE_SIGNALS */
2828#ifdef BB_FEATURE_VI_WIN_RESIZE
2829 printf("\tAdapt to window re-sizes\n");
2830#endif /* BB_FEATURE_VI_WIN_RESIZE */
2831}
2832
2833static void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable
2834{
2835 Byte c, b[2];
2836
2837 b[1] = '\0';
2838 strcpy((char *) buf, ""); // init buf
2839 if (strlen((char *) s) <= 0)
2840 s = (Byte *) "(NULL)";
2841 for (; *s > '\0'; s++) {
2842 c = *s;
2843 if (*s > '~') {
2844 strcat((char *) buf, SOs);
2845 c = *s - 128;
2846 }
2847 if (*s < ' ') {
2848 strcat((char *) buf, "^");
2849 c += '@';
2850 }
2851 b[0] = c;
2852 strcat((char *) buf, (char *) b);
2853 if (*s > '~')
2854 strcat((char *) buf, SOn);
2855 if (*s == '\n') {
2856 strcat((char *) buf, "$");
2857 }
2858 }
2859}
2860
2861#ifdef BB_FEATURE_VI_DOT_CMD
2862static void start_new_cmd_q(Byte c)
2863{
2864 // release old cmd
2865 if (last_modifying_cmd != 0)
2866 free(last_modifying_cmd);
2867 // get buffer for new cmd
2868 last_modifying_cmd = (Byte *) malloc(BUFSIZ);
2869 memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue
2870 // if there is a current cmd count put it in the buffer first
2871 if (cmdcnt > 0)
2872 sprintf((char *) last_modifying_cmd, "%d", cmdcnt);
2873 // save char c onto queue
2874 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
2875 adding2q = 1;
2876 return;
2877}
2878
2879static void end_cmd_q()
2880{
2881#ifdef BB_FEATURE_VI_YANKMARK
2882 YDreg = 26; // go back to default Yank/Delete reg
2883#endif /* BB_FEATURE_VI_YANKMARK */
2884 adding2q = 0;
2885 return;
2886}
2887#endif /* BB_FEATURE_VI_DOT_CMD */
2888
2889#if defined(BB_FEATURE_VI_YANKMARK) || defined(BB_FEATURE_VI_COLON) || defined(BB_FEATURE_VI_CRASHME)
2890static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p'
2891{
2892 int cnt, i;
2893
2894 i = strlen((char *) s);
2895 p = text_hole_make(p, i);
2896 strncpy((char *) p, (char *) s, i);
2897 for (cnt = 0; *s != '\0'; s++) {
2898 if (*s == '\n')
2899 cnt++;
2900 }
2901#ifdef BB_FEATURE_VI_YANKMARK
2902 psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2903#endif /* BB_FEATURE_VI_YANKMARK */
2904 return (p);
2905}
2906#endif /* BB_FEATURE_VI_YANKMARK || BB_FEATURE_VI_COLON || BB_FEATURE_VI_CRASHME */
2907
2908#ifdef BB_FEATURE_VI_YANKMARK
2909static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register
2910{
2911 Byte *t;
2912 int cnt;
2913
2914 if (q < p) { // they are backwards- reverse them
2915 t = q;
2916 q = p;
2917 p = t;
2918 }
2919 cnt = q - p + 1;
2920 t = reg[dest];
2921 if (t != 0) { // if already a yank register
2922 free(t); // free it
2923 }
2924 t = (Byte *) malloc(cnt + 1); // get a new register
2925 memset(t, '\0', cnt + 1); // clear new text[]
2926 strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer
2927 reg[dest] = t;
2928 return (p);
2929}
2930
2931static Byte what_reg(void)
2932{
2933 Byte c;
2934 int i;
2935
2936 i = 0;
2937 c = 'D'; // default to D-reg
2938 if (0 <= YDreg && YDreg <= 25)
2939 c = 'a' + (Byte) YDreg;
2940 if (YDreg == 26)
2941 c = 'D';
2942 if (YDreg == 27)
2943 c = 'U';
2944 return (c);
2945}
2946
2947static void check_context(Byte cmd)
2948{
2949 // A context is defined to be "modifying text"
2950 // Any modifying command establishes a new context.
2951
2952 if (dot < context_start || dot > context_end) {
2953 if (strchr((char *) modifying_cmds, cmd) != NULL) {
2954 // we are trying to modify text[]- make this the current context
2955 mark[27] = mark[26]; // move cur to prev
2956 mark[26] = dot; // move local to cur
2957 context_start = prev_line(prev_line(dot));
2958 context_end = next_line(next_line(dot));
2959 //loiter= start_loiter= now;
2960 }
2961 }
2962 return;
2963}
2964
2965static Byte *swap_context(Byte * p) // goto new context for '' command make this the current context
2966{
2967 Byte *tmp;
2968
2969 // the current context is in mark[26]
2970 // the previous context is in mark[27]
2971 // only swap context if other context is valid
2972 if (text <= mark[27] && mark[27] <= end - 1) {
2973 tmp = mark[27];
2974 mark[27] = mark[26];
2975 mark[26] = tmp;
2976 p = mark[26]; // where we are going- previous context
2977 context_start = prev_line(prev_line(prev_line(p)));
2978 context_end = next_line(next_line(next_line(p)));
2979 }
2980 return (p);
2981}
2982#endif /* BB_FEATURE_VI_YANKMARK */
2983
2984static int isblnk(Byte c) // is the char a blank or tab
2985{
2986 return (c == ' ' || c == '\t');
2987}
2988
2989//----- Set terminal attributes --------------------------------
2990static void rawmode(void)
2991{
2992 tcgetattr(0, &term_orig);
2993 term_vi = term_orig;
2994 term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's
2995 term_vi.c_iflag &= (~IXON & ~ICRNL);
2996 term_vi.c_cc[VMIN] = 1;
2997 term_vi.c_cc[VTIME] = 0;
2998 erase_char = term_vi.c_cc[VERASE];
2999 tcsetattr(0, TCSANOW, &term_vi);
3000}
3001
3002static void cookmode(void)
3003{
3004 tcsetattr(0, TCSANOW, &term_orig);
3005}
3006
3007#ifdef BB_FEATURE_VI_WIN_RESIZE
3008//----- See what the window size currently is --------------------
3009static void window_size_get(int sig)
3010{
3011 int i;
3012
3013 i = ioctl(0, TIOCGWINSZ, &winsize);
3014 if (i != 0) {
3015 // force 24x80
3016 winsize.ws_row = 24;
3017 winsize.ws_col = 80;
3018 }
3019 if (winsize.ws_row <= 1) {
3020 winsize.ws_row = 24;
3021 }
3022 if (winsize.ws_col <= 1) {
3023 winsize.ws_col = 80;
3024 }
3025 rows = (int) winsize.ws_row;
3026 columns = (int) winsize.ws_col;
3027}
3028#endif /* BB_FEATURE_VI_WIN_RESIZE */
3029
3030//----- Come here when we get a window resize signal ---------
3031#ifdef BB_FEATURE_VI_USE_SIGNALS
3032static void winch_sig(int sig)
3033{
3034 signal(SIGWINCH, winch_sig);
3035#ifdef BB_FEATURE_VI_WIN_RESIZE
3036 window_size_get(0);
3037#endif /* BB_FEATURE_VI_WIN_RESIZE */
3038 new_screen(rows, columns); // get memory for virtual screen
3039 redraw(TRUE); // re-draw the screen
3040}
3041
3042//----- Come here when we get a continue signal -------------------
3043static void cont_sig(int sig)
3044{
3045 rawmode(); // terminal to "raw"
3046 *status_buffer = '\0'; // clear the status buffer
3047 redraw(TRUE); // re-draw the screen
3048
3049 signal(SIGTSTP, suspend_sig);
3050 signal(SIGCONT, SIG_DFL);
3051 kill(getpid(), SIGCONT);
3052}
3053
3054//----- Come here when we get a Suspend signal -------------------
3055static void suspend_sig(int sig)
3056{
3057 place_cursor(rows, 0); // go to bottom of screen
3058 clear_to_eol(); // Erase to end of line
3059 cookmode(); // terminal to "cooked"
3060
3061 signal(SIGCONT, cont_sig);
3062 signal(SIGTSTP, SIG_DFL);
3063 kill(getpid(), SIGTSTP);
3064}
3065
3066//----- Come here when we get a signal --------------------
3067static void catch_sig(int sig)
3068{
3069 signal(SIGHUP, catch_sig);
3070 signal(SIGINT, catch_sig);
3071 signal(SIGTERM, catch_sig);
3072 longjmp(restart, sig);
3073}
3074
3075static void alarm_sig(int sig)
3076{
3077 signal(SIGALRM, catch_sig);
3078 longjmp(restart, sig);
3079}
3080
3081//----- Come here when we get a core dump signal -----------------
3082static void core_sig(int sig)
3083{
3084 signal(SIGQUIT, core_sig);
3085 signal(SIGILL, core_sig);
3086 signal(SIGTRAP, core_sig);
3087 signal(SIGIOT, core_sig);
3088 signal(SIGABRT, core_sig);
3089 signal(SIGFPE, core_sig);
3090 signal(SIGBUS, core_sig);
3091 signal(SIGSEGV, core_sig);
Eric Andersend402edf2001-04-04 19:29:48 +00003092#ifdef SIGSYS
Eric Andersen3f980402001-04-04 17:31:15 +00003093 signal(SIGSYS, core_sig);
Eric Andersend402edf2001-04-04 19:29:48 +00003094#endif
Eric Andersen3f980402001-04-04 17:31:15 +00003095
3096 dot = bound_dot(dot); // make sure "dot" is valid
3097
3098 longjmp(restart, sig);
3099}
3100#endif /* BB_FEATURE_VI_USE_SIGNALS */
3101
3102static int mysleep(int hund) // sleep for 'h' 1/100 seconds
3103{
3104 // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000
3105 FD_ZERO(&rfds);
3106 FD_SET(0, &rfds);
3107 tv.tv_sec = 0;
3108 tv.tv_usec = hund * 10000;
3109 select(1, &rfds, NULL, NULL, &tv);
3110 return (FD_ISSET(0, &rfds));
3111}
3112
3113//----- IO Routines --------------------------------------------
3114static Byte readit(void) // read (maybe cursor) key from stdin
3115{
3116 Byte c;
3117 int i, bufsiz, cnt, cmdindex;
3118 struct esc_cmds {
3119 Byte *seq;
3120 Byte val;
3121 };
3122
3123 static struct esc_cmds esccmds[] = {
3124 {(Byte *) "OA", (Byte) VI_K_UP}, // cursor key Up
3125 {(Byte *) "OB", (Byte) VI_K_DOWN}, // cursor key Down
3126 {(Byte *) "OC", (Byte) VI_K_RIGHT}, // Cursor Key Right
3127 {(Byte *) "OD", (Byte) VI_K_LEFT}, // cursor key Left
3128 {(Byte *) "OH", (Byte) VI_K_HOME}, // Cursor Key Home
3129 {(Byte *) "OF", (Byte) VI_K_END}, // Cursor Key End
3130 {(Byte *) "", (Byte) VI_K_UP}, // cursor key Up
3131 {(Byte *) "", (Byte) VI_K_DOWN}, // cursor key Down
3132 {(Byte *) "", (Byte) VI_K_RIGHT}, // Cursor Key Right
3133 {(Byte *) "", (Byte) VI_K_LEFT}, // cursor key Left
3134 {(Byte *) "", (Byte) VI_K_HOME}, // Cursor Key Home
3135 {(Byte *) "", (Byte) VI_K_END}, // Cursor Key End
3136 {(Byte *) "[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert
3137 {(Byte *) "[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up
3138 {(Byte *) "[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down
3139 {(Byte *) "OP", (Byte) VI_K_FUN1}, // Function Key F1
3140 {(Byte *) "OQ", (Byte) VI_K_FUN2}, // Function Key F2
3141 {(Byte *) "OR", (Byte) VI_K_FUN3}, // Function Key F3
3142 {(Byte *) "OS", (Byte) VI_K_FUN4}, // Function Key F4
3143 {(Byte *) "[15~", (Byte) VI_K_FUN5}, // Function Key F5
3144 {(Byte *) "[17~", (Byte) VI_K_FUN6}, // Function Key F6
3145 {(Byte *) "[18~", (Byte) VI_K_FUN7}, // Function Key F7
3146 {(Byte *) "[19~", (Byte) VI_K_FUN8}, // Function Key F8
3147 {(Byte *) "[20~", (Byte) VI_K_FUN9}, // Function Key F9
3148 {(Byte *) "[21~", (Byte) VI_K_FUN10}, // Function Key F10
3149 {(Byte *) "[23~", (Byte) VI_K_FUN11}, // Function Key F11
3150 {(Byte *) "[24~", (Byte) VI_K_FUN12}, // Function Key F12
3151 {(Byte *) "[11~", (Byte) VI_K_FUN1}, // Function Key F1
3152 {(Byte *) "[12~", (Byte) VI_K_FUN2}, // Function Key F2
3153 {(Byte *) "[13~", (Byte) VI_K_FUN3}, // Function Key F3
3154 {(Byte *) "[14~", (Byte) VI_K_FUN4}, // Function Key F4
3155 };
3156
3157#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds))
3158
3159 (void) alarm(0); // turn alarm OFF while we wait for input
3160 // get input from User- are there already input chars in Q?
3161 bufsiz = strlen((char *) readbuffer);
3162 if (bufsiz <= 0) {
3163 ri0:
3164 // the Q is empty, wait for a typed char
3165 bufsiz = read(0, readbuffer, BUFSIZ - 1);
3166 if (bufsiz < 0) {
3167 if (errno == EINTR)
3168 goto ri0; // interrupted sys call
3169 if (errno == EBADF)
3170 editing = 0;
3171 if (errno == EFAULT)
3172 editing = 0;
3173 if (errno == EINVAL)
3174 editing = 0;
3175 if (errno == EIO)
3176 editing = 0;
3177 errno = 0;
3178 bufsiz = 0;
3179 }
3180 readbuffer[bufsiz] = '\0';
3181 }
3182 // return char if it is not part of ESC sequence
3183 if (readbuffer[0] != 27)
3184 goto ri1;
3185
3186 // This is an ESC char. Is this Esc sequence?
3187 // Could be bare Esc key. See if there are any
3188 // more chars to read after the ESC. This would
3189 // be a Function or Cursor Key sequence.
3190 FD_ZERO(&rfds);
3191 FD_SET(0, &rfds);
3192 tv.tv_sec = 0;
3193 tv.tv_usec = 10000; // Wait 1/100 seconds- 1 Sec=1000000
3194
3195 // keep reading while there are input chars and room in buffer
3196 while (select(1, &rfds, NULL, NULL, &tv) > 0 && bufsiz <= (BUFSIZ - 5)) {
3197 // read the rest of the ESC string
3198 i = read(0, (void *) (readbuffer + bufsiz), BUFSIZ - bufsiz);
3199 if (i > 0) {
3200 bufsiz += i;
3201 readbuffer[bufsiz] = '\0'; // Terminate the string
3202 }
3203 }
3204 // Maybe cursor or function key?
3205 for (cmdindex = 0; cmdindex < ESCCMDS_COUNT; cmdindex++) {
3206 cnt = strlen((char *) esccmds[cmdindex].seq);
3207 i = strncmp((char *) esccmds[cmdindex].seq, (char *) readbuffer, cnt);
3208 if (i == 0) {
3209 // is a Cursor key- put derived value back into Q
3210 readbuffer[0] = esccmds[cmdindex].val;
3211 // squeeze out the ESC sequence
3212 for (i = 1; i < cnt; i++) {
3213 memmove(readbuffer + 1, readbuffer + 2, BUFSIZ - 2);
3214 readbuffer[BUFSIZ - 1] = '\0';
3215 }
3216 break;
3217 }
3218 }
3219 ri1:
3220 c = readbuffer[0];
3221 // remove one char from Q
3222 memmove(readbuffer, readbuffer + 1, BUFSIZ - 1);
3223 readbuffer[BUFSIZ - 1] = '\0';
3224 (void) alarm(3); // we are done waiting for input, turn alarm ON
3225 return (c);
3226}
3227
3228//----- IO Routines --------------------------------------------
3229static Byte get_one_char()
3230{
3231 static Byte c;
3232
3233#ifdef BB_FEATURE_VI_DOT_CMD
3234 // ! adding2q && ioq == 0 read()
3235 // ! adding2q && ioq != 0 *ioq
3236 // adding2q *last_modifying_cmd= read()
3237 if (!adding2q) {
3238 // we are not adding to the q.
3239 // but, we may be reading from a q
3240 if (ioq == 0) {
3241 // there is no current q, read from STDIN
3242 c = readit(); // get the users input
3243 } else {
3244 // there is a queue to get chars from first
3245 c = *ioq++;
3246 if (c == '\0') {
3247 // the end of the q, read from STDIN
3248 free(ioq_start);
3249 ioq_start = ioq = 0;
3250 c = readit(); // get the users input
3251 }
3252 }
3253 } else {
3254 // adding STDIN chars to q
3255 c = readit(); // get the users input
3256 if (last_modifying_cmd != 0) {
3257 // add new char to q
3258 last_modifying_cmd[strlen((char *) last_modifying_cmd)] = c;
3259 }
3260 }
3261#else /* BB_FEATURE_VI_DOT_CMD */
3262 c = readit(); // get the users input
3263#endif /* BB_FEATURE_VI_DOT_CMD */
3264 return (c); // return the char, where ever it came from
3265}
3266
3267#if defined(BB_FEATURE_VI_SEARCH) || defined(BB_FEATURE_VI_COLON)
3268static Byte *get_input_line(Byte * prompt) // get input line- use "status line"
3269{
3270 Byte buf[500];
3271 Byte c;
3272 int i;
3273 static Byte *obufp = NULL;
3274
3275 strcpy((char *) buf, (char *) prompt);
3276 *status_buffer = '\0'; // clear the status buffer
3277 place_cursor(rows - 1, 0); // go to Status line, bottom of screen
3278 clear_to_eol(); // clear the line
3279 write(1, prompt, strlen((char *) prompt)); // write out the :, /, or ? prompt
3280
3281 for (i = strlen((char *) buf); i < 500;) {
3282 c = get_one_char(); // read user input
3283 if (c == '\n' || c == '\r')
3284 break; // is this end of input
3285 if (c == erase_char) { // user wants to erase prev char
3286 i--; // backup to prev char
3287 buf[i] = '\0'; // erase the char
3288 buf[i + 1] = '\0'; // null terminate buffer
3289 write(1, " ", 3); // erase char on screen
3290 if (i <= 0) { // user backs up before b-o-l, exit
3291 break;
3292 }
3293 } else {
3294 buf[i] = c; // save char in buffer
3295 buf[i + 1] = '\0'; // make sure buffer is null terminated
3296 write(1, buf + i, 1); // echo the char back to user
3297 i++;
3298 }
3299 }
3300 refresh(FALSE);
3301 if (obufp != NULL)
3302 free(obufp);
3303 obufp = (Byte *) strdup((char *) buf);
3304 return (obufp);
3305}
3306#endif /* BB_FEATURE_VI_SEARCH || BB_FEATURE_VI_COLON */
3307
3308static int file_size(Byte * fn) // what is the byte size of "fn"
3309{
3310 struct stat st_buf;
3311 int cnt, sr;
3312
3313 if (fn == 0)
3314 return (-1);
3315 cnt = -1;
3316 sr = stat((char *) fn, &st_buf); // see if file exists
3317 if (sr >= 0) {
3318 cnt = (int) st_buf.st_size;
3319 }
3320 return (cnt);
3321}
3322
3323static int file_insert(Byte * fn, Byte * p, int size)
3324{
3325 int fd, cnt, sr;
3326 struct stat st_buf;
3327
3328 cnt = -1;
3329 if (fn == 0) {
3330 psbs("No filename given");
3331 goto fi0;
3332 }
3333 sr = stat((char *) fn, &st_buf); // see if file exists
3334 if (sr < 0) {
3335 psbs("\"%s\" %s", fn, "count not stat file");
3336 goto fi0;
3337 }
3338 if (size <= 0 || (int) st_buf.st_size <= 0) {
3339 psbs("The file size (%d) is too small", size);
3340 if ((int) st_buf.st_size <= 0)
3341 psbs("\"%s\" is empty", fn);
3342 goto fi0;
3343 }
3344 // There is a file with content
3345 fd = open((char *) fn, O_RDWR);
3346 if (fd < 0) {
3347 psbs("\"%s\" %s", fn, "could not open file");
3348 goto fi0;
3349 }
3350 p = text_hole_make(p, size);
3351 cnt = read(fd, p, size);
3352 close(fd);
3353 if (cnt < 0) {
3354 cnt = -1;
3355 p = text_hole_delete(p, p + size - 1); // un-do buffer insert
3356 psbs("could not read file \"%s\"", fn);
3357 } else if (cnt < size) {
3358 // There was a partial read, shrink unused space text[]
3359 p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert
3360 psbs("could not read all of file \"%s\"", fn);
3361 }
3362 if (cnt >= size)
3363 file_modified = TRUE;
3364 fi0:
3365 return (cnt);
3366}
3367
3368static int file_write(Byte * fn, Byte * first, Byte * last)
3369{
3370 int fd, cnt, charcnt;
3371
3372 if (fn == 0) {
3373 psbs("No current filename");
3374 return (-1);
3375 }
3376 charcnt = 0;
3377 // FIXIT- use the correct umask()
3378 fd = open((char *) fn, (O_RDWR | O_CREAT | O_TRUNC), 0664);
3379 if (fd < 0)
3380 return (-1);
3381 cnt = last - first + 1;
3382 charcnt = write(fd, first, cnt);
3383 if (charcnt == cnt) {
3384 // good write
3385 //file_modified= FALSE; // the file has not been modified
3386 } else {
3387 charcnt = 0;
3388 }
3389 close(fd);
3390 return (charcnt);
3391}
3392
3393//----- Terminal Drawing ---------------------------------------
3394// The terminal is made up of 'rows' line of 'columns' columns.
3395// classicly this would be 24 x 80.
3396// screen coordinates
3397// 0,0 ... 0,79
3398// 1,0 ... 1,79
3399// . ... .
3400// . ... .
3401// 22,0 ... 22,79
3402// 23,0 ... 23,79 status line
3403//
3404
3405//----- Move the cursor to row x col (count from 0, not 1) -------
3406static void place_cursor(int row, int col)
3407{
3408 Byte buf[30];
3409 int l;
3410
3411 if (row < 0)
3412 row = 0;
3413 if (row >= rows)
3414 row = rows - 1;
3415 if (col < 0)
3416 col = 0;
3417 if (col >= columns)
3418 col = columns - 1;
3419 sprintf((char *) buf, "%c[%d;%dH", 0x1b, row + 1, col + 1);
3420 l = strlen((char *) buf);
3421 write(1, buf, l);
3422}
3423
3424//----- Erase from cursor to end of line -----------------------
3425static void clear_to_eol()
3426{
3427 write(1, "\033[0K", 4); // Erase from cursor to end of line
3428}
3429
3430//----- Erase from cursor to end of screen -----------------------
3431static void clear_to_eos()
3432{
3433 write(1, "\033[0J", 4); // Erase from cursor to end of screen
3434}
3435
3436//----- Start standout mode ------------------------------------
3437static void standout_start() // send "start reverse video" sequence
3438{
3439 write(1, "\033[7m", 4); // Start reverse video mode
3440}
3441
3442//----- End standout mode --------------------------------------
3443static void standout_end() // send "end reverse video" sequence
3444{
3445 write(1, "\033[0m", 4); // End reverse video mode
3446}
3447
3448//----- Flash the screen --------------------------------------
3449static void flash(int h)
3450{
3451 standout_start(); // send "start reverse video" sequence
3452 redraw(TRUE);
3453 (void) mysleep(h);
3454 standout_end(); // send "end reverse video" sequence
3455 redraw(TRUE);
3456}
3457
3458static void beep()
3459{
3460 write(1, "\007", 1); // send out a bell character
3461}
3462
3463static void indicate_error(char c)
3464{
3465#ifdef BB_FEATURE_VI_CRASHME
3466 if (crashme > 0)
3467 return; // generate a random command
3468#endif /* BB_FEATURE_VI_CRASHME */
3469 if (err_method == 0) {
3470 beep();
3471 } else {
3472 flash(10);
3473 }
3474}
3475
3476//----- Screen[] Routines --------------------------------------
3477//----- Erase the Screen[] memory ------------------------------
3478static void screen_erase()
3479{
3480 int i;
3481
3482 for (i = 0; i < screensize; i++) {
3483 screen[i] = '\0';
3484 }
3485}
3486
3487//----- Draw the status line at bottom of the screen -------------
3488static void show_status_line(void)
3489{
3490 int cnt;
3491
3492 cnt = strlen((char *) status_buffer);
3493 place_cursor(rows - 1, 0); // put cursor on status line
3494 if (cnt > 0) {
3495 write(1, status_buffer, cnt);
3496 }
3497 clear_to_eol();
3498 place_cursor(crow, ccol); // put cursor back in correct place
3499}
3500
3501//----- format the status buffer, the bottom line of screen ------
3502// print status buffer, with STANDOUT mode
3503static void psbs(char *format, ...)
3504{
3505 va_list args;
3506
3507 va_start(args, format);
3508 strcpy((char *) status_buffer, "\033[7m"); // Terminal standout mode on
3509 vsprintf((char *) status_buffer + strlen((char *) status_buffer), format,
3510 args);
3511 strcat((char *) status_buffer, "\033[0m"); // Terminal standout mode off
3512 va_end(args);
3513
3514 return;
3515}
3516
3517// print status buffer
3518static void psb(char *format, ...)
3519{
3520 va_list args;
3521
3522 va_start(args, format);
3523 vsprintf((char *) status_buffer, format, args);
3524 va_end(args);
3525 return;
3526}
3527
3528static void ni(Byte * s) // display messages
3529{
3530 Byte buf[9];
3531
3532 print_literal(buf, s);
3533 psbs("\'%s\' is not implemented", buf);
3534}
3535
3536static void edit_status(void) // show file status on status line
3537{
3538 int cur, tot, percent;
3539
3540 cur = count_lines(text, dot);
3541 tot = count_lines(text, end - 1);
3542 // current line percent
3543 // ------------- ~~ ----------
3544 // total lines 100
3545 if (tot > 0) {
3546 percent = (100 * cur) / tot;
3547 } else {
3548 cur = tot = 0;
3549 percent = 100;
3550 }
3551 psb("\"%s\""
3552#ifdef BB_FEATURE_VI_READONLY
3553 "%s"
3554#endif /* BB_FEATURE_VI_READONLY */
3555 "%s line %d of %d --%d%%-- (%dx%d)",
3556 (cfn != 0 ? (char *) cfn : "No file"),
3557#ifdef BB_FEATURE_VI_READONLY
3558 (readonly == TRUE ? " [Read only]" : ""),
3559#endif /* BB_FEATURE_VI_READONLY */
3560 (file_modified == TRUE ? " [modified]" : ""),
3561 cur, tot, percent, rows, columns);
3562}
3563
3564//----- Force refresh of all Lines -----------------------------
3565static void redraw(int full_screen)
3566{
3567 place_cursor(0, 0); // put cursor in correct place
3568 clear_to_eos(); // tel terminal to erase display
3569 screen_erase(); // erase the internal screen buffer
3570 refresh(full_screen); // this will redraw the entire display
3571}
3572
3573//----- Refresh the changed screen lines -----------------------
3574// Copy the source line from text[] into the buffer and note
3575// if the current screenline is different from the new buffer.
3576// If they differ then that line needs redrawing on the terminal.
3577//
3578static void refresh(int full_screen)
3579{
3580 static int old_offset;
3581 int li, co, changed;
3582 Byte c, buf[MAX_SCR_COLS];
3583 Byte *tp, *sp; // pointer into text[] and screen[]
3584
3585#ifdef BB_FEATURE_VI_WIN_RESIZE
3586 window_size_get(0);
3587#endif /* BB_FEATURE_VI_WIN_RESIZE */
3588 sync_cursor(dot, &crow, &ccol);
3589 tp = screenbegin; // index into text[] of top line
3590 // compare text[] to screen[] and mark screen[] lines that need updating
3591 for (li = 0; li < rows - 1; li++) {
3592 // format current text line into buf with "columns" wide
3593 for (co = 0; co < columns + offset;) {
3594 c = ' '; // assume blank
3595 if (li > 0 && co == 0) {
3596 c = '~'; // not first line, assume Tilde
3597 }
3598 // are there chars in text[]
3599 // and have we gone past the end
3600 if (text < end && tp < end) {
3601 c = *tp++;
3602 }
3603 if (c == '\n')
3604 break;
3605 if (c < ' ' || c > '~') {
3606 if (c == '\t') {
3607 c = ' ';
3608 // co % 8 != 7
3609 for (; (co % tabstop) != (tabstop - 1); co++) {
3610 buf[co] = c;
3611 }
3612 } else {
3613 buf[co++] = '^';
3614 c |= '@'; // make it visible
3615 c &= 0x7f; // get rid of hi bit
3616 }
3617 }
3618 // the co++ is done here so that the column will
3619 // not be overwritten when we blank-out the rest of line
3620 buf[co++] = c;
3621 if (tp >= end)
3622 break;
3623 }
3624 if (co >= columns + offset) {
3625 // skip to the end of the current text[] line
3626 while (tp < end && *tp++ != '\n');
3627 }
3628 // try to keep the cursor near it's current position
3629 // remember how many chars in this row- where the cursor sits
3630 // blank out the rest of the buffer
3631 while (co < MAX_SCR_COLS - 1) {
3632 buf[co++] = ' ';
3633 }
3634 buf[co++] = 0; // NULL terminate the buffer
3635
3636 // if necessary, update virtual screen[] and terminal from buf[]
3637 changed = FALSE; // assume no change
3638 sp = &screen[li * columns]; // start of screen line
3639 for (co = 0; co < columns; co++) {
3640 if (sp[co] != buf[co + offset]) {
3641 sp[co] = buf[co + offset];
3642 changed = TRUE; // mark for redraw
3643 }
3644 }
3645 // if horz offset has changed, force a redraw
3646 if (offset != old_offset)
3647 changed = TRUE;
3648
3649 // write all marked screen lines out to terminal
3650 if (changed == TRUE) {
3651 place_cursor(li, 0); // put cursor in correct place
3652 clear_to_eol(); // Erase to end of line
3653 if (full_screen == FALSE) {
3654 // don't redraw every column on terminal
3655 // look backwards for last non-blank
3656 for (co = columns + offset; co >= 0; co--) {
3657 // break;
3658 if (buf[co] != ' ')
3659 break;
3660 }
3661 co++;
3662 } else {
3663 // redraw every column on terminal
3664 co = columns;
3665 }
3666 // write line out to terminal
3667 write(1, buf + offset, co);
3668 }
3669 }
3670
3671 place_cursor(crow, ccol);
3672 if (offset != old_offset)
3673 old_offset = offset;
3674}