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