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