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