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