summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDenys Vlasenko <vda.linux@googlemail.com>2019-04-01 14:18:02 +0200
committerDenys Vlasenko <vda.linux@googlemail.com>2019-04-01 14:18:02 +0200
commitd4f2e7ff71f253ee993e11cf7ce6a1244dec52e0 (patch)
tree9f64679b80593f42bc43b932686f3daac6c852e3
parentde69775838eed0acd02f40de5e988d80611557ab (diff)
downloadbusybox-w32-d4f2e7ff71f253ee993e11cf7ce6a1244dec52e0.tar.gz
busybox-w32-d4f2e7ff71f253ee993e11cf7ce6a1244dec52e0.tar.bz2
busybox-w32-d4f2e7ff71f253ee993e11cf7ce6a1244dec52e0.zip
vi: rearrange functions, no logic changes
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--editors/vi.c2915
1 files changed, 1407 insertions, 1508 deletions
diff --git a/editors/vi.c b/editors/vi.c
index 9db763ccd..a0a2b7a82 100644
--- a/editors/vi.c
+++ b/editors/vi.c
@@ -483,123 +483,8 @@ struct globals {
483} while (0) 483} while (0)
484 484
485 485
486static void do_cmd(int); // execute a command
487static int next_tabstop(int);
488static void sync_cursor(char *, int *, int *); // synchronize the screen cursor to dot
489static char *begin_line(char *); // return pointer to cur line B-o-l
490static char *end_line(char *); // return pointer to cur line E-o-l
491static char *prev_line(char *); // return pointer to prev line B-o-l
492static char *next_line(char *); // return pointer to next line B-o-l
493static char *end_screen(void); // get pointer to last char on screen
494static int count_lines(char *, char *); // count line from start to stop
495static char *find_line(int); // find beginning of line #li
496static char *move_to_col(char *, int); // move "p" to column l
497static void dot_left(void); // move dot left- dont leave line
498static void dot_right(void); // move dot right- dont leave line
499static void dot_begin(void); // move dot to B-o-l
500static void dot_end(void); // move dot to E-o-l
501static void dot_next(void); // move dot to next line B-o-l
502static void dot_prev(void); // move dot to prev line B-o-l
503static void dot_scroll(int, int); // move the screen up or down
504static void dot_skip_over_ws(void); // move dot pat WS
505static char *bound_dot(char *); // make sure text[0] <= P < "end"
506static char *new_screen(int, int); // malloc virtual screen memory
507#if !ENABLE_FEATURE_VI_UNDO
508#define char_insert(a,b,c) char_insert(a,b)
509#endif
510static char *char_insert(char *, char, int); // insert the char c at 'p'
511// might reallocate text[]! use p += stupid_insert(p, ...),
512// and be careful to not use pointers into potentially freed text[]!
513static uintptr_t stupid_insert(char *, char); // stupidly insert the char c at 'p'
514static int st_test(char *, int, int, char *); // helper for skip_thing()
515static char *skip_thing(char *, int, int, int); // skip some object
516static char *find_pair(char *, char); // find matching pair () [] {}
517#if !ENABLE_FEATURE_VI_UNDO
518#define text_hole_delete(a,b,c) text_hole_delete(a,b)
519#endif
520static char *text_hole_delete(char *, char *, int); // at "p", delete a 'size' byte hole
521// might reallocate text[]! use p += text_hole_make(p, ...),
522// and be careful to not use pointers into potentially freed text[]!
523static uintptr_t text_hole_make(char *, int); // at "p", make a 'size' byte hole
524#if !ENABLE_FEATURE_VI_UNDO
525#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
526#endif
527static char *yank_delete(char *, char *, int, int, int); // yank text[] into register then delete
528static void rawmode(void); // set "raw" mode on tty
529static void cookmode(void); // return to "cooked" mode on tty
530// sleep for 'h' 1/100 seconds, return 1/0 if stdin is (ready for read)/(not ready)
531static int mysleep(int);
532static int get_one_char(void); // read 1 char from stdin
533// file_insert might reallocate text[]!
534static int file_insert(const char *, char *, int);
535static int file_write(char *, char *, char *);
536static void screen_erase(void);
537static void go_bottom_and_clear_to_eol(void);
538static void standout_start(void); // send "start reverse video" sequence
539static void standout_end(void); // send "end reverse video" sequence
540static void flash(int); // flash the terminal screen
541static void show_status_line(void); // put a message on the bottom line 486static void show_status_line(void); // put a message on the bottom line
542static void status_line(const char *, ...); // print to status buf
543static void status_line_bold(const char *, ...); 487static void status_line_bold(const char *, ...);
544static void status_line_bold_errno(const char *fn);
545static void not_implemented(const char *); // display "Not implemented" message
546static int format_edit_status(void); // format file status on status line
547static void redraw(int); // force a full screen refresh
548static char* format_line(char* /*, int*/);
549static void refresh(int); // update the terminal from screen[]
550
551static void indicate_error(void); // use flash or beep to indicate error
552static void Hit_Return(void);
553
554#if ENABLE_FEATURE_VI_SEARCH
555static char *char_search(char *, const char *, int); // search for pattern starting at p
556#endif
557#if ENABLE_FEATURE_VI_COLON
558static char *get_one_address(char *, int *); // get colon addr, if present
559static char *get_address(char *, int *, int *); // get two colon addrs, if present
560#endif
561static void colon(char *); // execute the "colon" mode cmds
562#if ENABLE_FEATURE_VI_USE_SIGNALS
563static void winch_handler(int); // catch window size changes
564static void tstp_handler(int); // catch ctrl-Z
565static void int_handler(int); // catch ctrl-C
566#endif
567#if ENABLE_FEATURE_VI_DOT_CMD
568static void start_new_cmd_q(char); // new queue for command
569static void end_cmd_q(void); // stop saving input chars
570#else
571#define end_cmd_q() ((void)0)
572#endif
573#if ENABLE_FEATURE_VI_SETOPTS
574static void showmatching(char *); // show the matching pair () [] {}
575#endif
576#if ENABLE_FEATURE_VI_YANKMARK || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) || ENABLE_FEATURE_VI_CRASHME
577// might reallocate text[]! use p += string_insert(p, ...),
578// and be careful to not use pointers into potentially freed text[]!
579# if !ENABLE_FEATURE_VI_UNDO
580#define string_insert(a,b,c) string_insert(a,b)
581# endif
582static uintptr_t string_insert(char *, const char *, int); // insert the string at 'p'
583#endif
584#if ENABLE_FEATURE_VI_YANKMARK
585static char *text_yank(char *, char *, int); // save copy of "p" into a register
586static char what_reg(void); // what is letter of current YDreg
587static void check_context(char); // remember context for '' command
588#endif
589#if ENABLE_FEATURE_VI_UNDO
590static void flush_undo_data(void);
591static void undo_push(char *, unsigned, unsigned char); // push an operation on the undo stack
592static void undo_push_insert(char *, int, int); // convenience function
593static void undo_pop(void); // undo the last operation
594# if ENABLE_FEATURE_VI_UNDO_QUEUE
595static void undo_queue_commit(void); // flush any queued objects to the undo stack
596# else
597# define undo_queue_commit() ((void)0)
598# endif
599#else
600#define flush_undo_data() ((void)0)
601#define undo_queue_commit() ((void)0)
602#endif
603 488
604#if ENABLE_FEATURE_VI_CRASHME 489#if ENABLE_FEATURE_VI_CRASHME
605static void crash_dummy(); 490static void crash_dummy();
@@ -645,37 +530,6 @@ static void write1(const char *out)
645 fputs(out, stdout); 530 fputs(out, stdout);
646} 531}
647 532
648/* read text from file or create an empty buf */
649/* will also update current_filename */
650static int init_text_buffer(char *fn)
651{
652 int rc;
653
654 /* allocate/reallocate text buffer */
655 free(text);
656 text_size = 10240;
657 screenbegin = dot = end = text = xzalloc(text_size);
658
659 if (fn != current_filename) {
660 free(current_filename);
661 current_filename = xstrdup(fn);
662 }
663 rc = file_insert(fn, text, 1);
664 if (rc < 0) {
665 // file doesnt exist. Start empty buf with dummy line
666 char_insert(text, '\n', NO_UNDO);
667 }
668
669 flush_undo_data();
670 modified_count = 0;
671 last_modified_count = -1;
672#if ENABLE_FEATURE_VI_YANKMARK
673 /* init the marks */
674 memset(mark, 0, sizeof(mark));
675#endif
676 return rc;
677}
678
679#if ENABLE_FEATURE_VI_WIN_RESIZE 533#if ENABLE_FEATURE_VI_WIN_RESIZE
680static int query_screen_dimensions(void) 534static int query_screen_dimensions(void)
681{ 535{
@@ -969,6 +823,1413 @@ static NOINLINE void sync_cursor(char *d, int *row, int *col)
969 *col = co; 823 *col = co;
970} 824}
971 825
826//----- Format a text[] line into a buffer ---------------------
827static char* format_line(char *src /*, int li*/)
828{
829 unsigned char c;
830 int co;
831 int ofs = offset;
832 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
833
834 c = '~'; // char in col 0 in non-existent lines is '~'
835 co = 0;
836 while (co < columns + tabstop) {
837 // have we gone past the end?
838 if (src < end) {
839 c = *src++;
840 if (c == '\n')
841 break;
842 if ((c & 0x80) && !Isprint(c)) {
843 c = '.';
844 }
845 if (c < ' ' || c == 0x7f) {
846 if (c == '\t') {
847 c = ' ';
848 // co % 8 != 7
849 while ((co % tabstop) != (tabstop - 1)) {
850 dest[co++] = c;
851 }
852 } else {
853 dest[co++] = '^';
854 if (c == 0x7f)
855 c = '?';
856 else
857 c += '@'; // Ctrl-X -> 'X'
858 }
859 }
860 }
861 dest[co++] = c;
862 // discard scrolled-off-to-the-left portion,
863 // in tabstop-sized pieces
864 if (ofs >= tabstop && co >= tabstop) {
865 memmove(dest, dest + tabstop, co);
866 co -= tabstop;
867 ofs -= tabstop;
868 }
869 if (src >= end)
870 break;
871 }
872 // check "short line, gigantic offset" case
873 if (co < ofs)
874 ofs = co;
875 // discard last scrolled off part
876 co -= ofs;
877 dest += ofs;
878 // fill the rest with spaces
879 if (co < columns)
880 memset(&dest[co], ' ', columns - co);
881 return dest;
882}
883
884//----- Refresh the changed screen lines -----------------------
885// Copy the source line from text[] into the buffer and note
886// if the current screenline is different from the new buffer.
887// If they differ then that line needs redrawing on the terminal.
888//
889static void refresh(int full_screen)
890{
891#define old_offset refresh__old_offset
892
893 int li, changed;
894 char *tp, *sp; // pointer into text[] and screen[]
895
896 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
897 unsigned c = columns, r = rows;
898 query_screen_dimensions();
899#if ENABLE_FEATURE_VI_USE_SIGNALS
900 full_screen |= (c - columns) | (r - rows);
901#else
902 if (c != columns || r != rows) {
903 full_screen = TRUE;
904 // update screen memory since SIGWINCH won't have done it
905 new_screen(rows, columns);
906 }
907#endif
908 }
909 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
910 tp = screenbegin; // index into text[] of top line
911
912 // compare text[] to screen[] and mark screen[] lines that need updating
913 for (li = 0; li < rows - 1; li++) {
914 int cs, ce; // column start & end
915 char *out_buf;
916 // format current text line
917 out_buf = format_line(tp /*, li*/);
918
919 // skip to the end of the current text[] line
920 if (tp < end) {
921 char *t = memchr(tp, '\n', end - tp);
922 if (!t) t = end - 1;
923 tp = t + 1;
924 }
925
926 // see if there are any changes between virtual screen and out_buf
927 changed = FALSE; // assume no change
928 cs = 0;
929 ce = columns - 1;
930 sp = &screen[li * columns]; // start of screen line
931 if (full_screen) {
932 // force re-draw of every single column from 0 - columns-1
933 goto re0;
934 }
935 // compare newly formatted buffer with virtual screen
936 // look forward for first difference between buf and screen
937 for (; cs <= ce; cs++) {
938 if (out_buf[cs] != sp[cs]) {
939 changed = TRUE; // mark for redraw
940 break;
941 }
942 }
943
944 // look backward for last difference between out_buf and screen
945 for (; ce >= cs; ce--) {
946 if (out_buf[ce] != sp[ce]) {
947 changed = TRUE; // mark for redraw
948 break;
949 }
950 }
951 // now, cs is index of first diff, and ce is index of last diff
952
953 // if horz offset has changed, force a redraw
954 if (offset != old_offset) {
955 re0:
956 changed = TRUE;
957 }
958
959 // make a sanity check of columns indexes
960 if (cs < 0) cs = 0;
961 if (ce > columns - 1) ce = columns - 1;
962 if (cs > ce) { cs = 0; ce = columns - 1; }
963 // is there a change between virtual screen and out_buf
964 if (changed) {
965 // copy changed part of buffer to virtual screen
966 memcpy(sp+cs, out_buf+cs, ce-cs+1);
967 place_cursor(li, cs);
968 // write line out to terminal
969 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
970 }
971 }
972
973 place_cursor(crow, ccol);
974
975 old_offset = offset;
976#undef old_offset
977}
978
979//----- Force refresh of all Lines -----------------------------
980static void redraw(int full_screen)
981{
982 // cursor to top,left; clear to the end of screen
983 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
984 screen_erase(); // erase the internal screen buffer
985 last_status_cksum = 0; // force status update
986 refresh(full_screen); // this will redraw the entire display
987 show_status_line();
988}
989
990//----- Flash the screen --------------------------------------
991static void flash(int h)
992{
993 standout_start();
994 redraw(TRUE);
995 mysleep(h);
996 standout_end();
997 redraw(TRUE);
998}
999
1000static void indicate_error(void)
1001{
1002#if ENABLE_FEATURE_VI_CRASHME
1003 if (crashme > 0)
1004 return;
1005#endif
1006 if (!err_method) {
1007 write1(ESC_BELL);
1008 } else {
1009 flash(10);
1010 }
1011}
1012
1013//----- IO Routines --------------------------------------------
1014static int readit(void) // read (maybe cursor) key from stdin
1015{
1016 int c;
1017
1018 fflush_all();
1019
1020 // Wait for input. TIMEOUT = -1 makes read_key wait even
1021 // on nonblocking stdin.
1022 // Note: read_key sets errno to 0 on success.
1023 again:
1024 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
1025 if (c == -1) { // EOF/error
1026 if (errno == EAGAIN) // paranoia
1027 goto again;
1028 go_bottom_and_clear_to_eol();
1029 cookmode(); // terminal to "cooked"
1030 bb_error_msg_and_die("can't read user input");
1031 }
1032 return c;
1033}
1034
1035static int get_one_char(void)
1036{
1037 int c;
1038
1039#if ENABLE_FEATURE_VI_DOT_CMD
1040 if (!adding2q) {
1041 // we are not adding to the q.
1042 // but, we may be reading from a q
1043 if (ioq == 0) {
1044 // there is no current q, read from STDIN
1045 c = readit(); // get the users input
1046 } else {
1047 // there is a queue to get chars from first
1048 // careful with correct sign expansion!
1049 c = (unsigned char)*ioq++;
1050 if (c == '\0') {
1051 // the end of the q, read from STDIN
1052 free(ioq_start);
1053 ioq_start = ioq = 0;
1054 c = readit(); // get the users input
1055 }
1056 }
1057 } else {
1058 // adding STDIN chars to q
1059 c = readit(); // get the users input
1060 if (lmc_len >= MAX_INPUT_LEN - 1) {
1061 status_line_bold("last_modifying_cmd overrun");
1062 } else {
1063 // add new char to q
1064 last_modifying_cmd[lmc_len++] = c;
1065 }
1066 }
1067#else
1068 c = readit(); // get the users input
1069#endif /* FEATURE_VI_DOT_CMD */
1070 return c;
1071}
1072
1073// Get input line (uses "status line" area)
1074static char *get_input_line(const char *prompt)
1075{
1076 // char [MAX_INPUT_LEN]
1077#define buf get_input_line__buf
1078
1079 int c;
1080 int i;
1081
1082 strcpy(buf, prompt);
1083 last_status_cksum = 0; // force status update
1084 go_bottom_and_clear_to_eol();
1085 write1(prompt); // write out the :, /, or ? prompt
1086
1087 i = strlen(buf);
1088 while (i < MAX_INPUT_LEN) {
1089 c = get_one_char();
1090 if (c == '\n' || c == '\r' || c == 27)
1091 break; // this is end of input
1092 if (c == erase_char || c == 8 || c == 127) {
1093 // user wants to erase prev char
1094 buf[--i] = '\0';
1095 write1("\b \b"); // erase char on screen
1096 if (i <= 0) // user backs up before b-o-l, exit
1097 break;
1098 } else if (c > 0 && c < 256) { // exclude Unicode
1099 // (TODO: need to handle Unicode)
1100 buf[i] = c;
1101 buf[++i] = '\0';
1102 bb_putchar(c);
1103 }
1104 }
1105 refresh(FALSE);
1106 return buf;
1107#undef buf
1108}
1109
1110static void Hit_Return(void)
1111{
1112 int c;
1113
1114 standout_start();
1115 write1("[Hit return to continue]");
1116 standout_end();
1117 while ((c = get_one_char()) != '\n' && c != '\r')
1118 continue;
1119 redraw(TRUE); // force redraw all
1120}
1121
1122//----- Draw the status line at bottom of the screen -------------
1123// show file status on status line
1124static int format_edit_status(void)
1125{
1126 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
1127
1128#define tot format_edit_status__tot
1129
1130 int cur, percent, ret, trunc_at;
1131
1132 // modified_count is now a counter rather than a flag. this
1133 // helps reduce the amount of line counting we need to do.
1134 // (this will cause a mis-reporting of modified status
1135 // once every MAXINT editing operations.)
1136
1137 // it would be nice to do a similar optimization here -- if
1138 // we haven't done a motion that could have changed which line
1139 // we're on, then we shouldn't have to do this count_lines()
1140 cur = count_lines(text, dot);
1141
1142 // count_lines() is expensive.
1143 // Call it only if something was changed since last time
1144 // we were here:
1145 if (modified_count != last_modified_count) {
1146 tot = cur + count_lines(dot, end - 1) - 1;
1147 last_modified_count = modified_count;
1148 }
1149
1150 // current line percent
1151 // ------------- ~~ ----------
1152 // total lines 100
1153 if (tot > 0) {
1154 percent = (100 * cur) / tot;
1155 } else {
1156 cur = tot = 0;
1157 percent = 100;
1158 }
1159
1160 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
1161 columns : STATUS_BUFFER_LEN-1;
1162
1163 ret = snprintf(status_buffer, trunc_at+1,
1164#if ENABLE_FEATURE_VI_READONLY
1165 "%c %s%s%s %d/%d %d%%",
1166#else
1167 "%c %s%s %d/%d %d%%",
1168#endif
1169 cmd_mode_indicator[cmd_mode & 3],
1170 (current_filename != NULL ? current_filename : "No file"),
1171#if ENABLE_FEATURE_VI_READONLY
1172 (readonly_mode ? " [Readonly]" : ""),
1173#endif
1174 (modified_count ? " [Modified]" : ""),
1175 cur, tot, percent);
1176
1177 if (ret >= 0 && ret < trunc_at)
1178 return ret; // it all fit
1179
1180 return trunc_at; // had to truncate
1181#undef tot
1182}
1183
1184static int bufsum(char *buf, int count)
1185{
1186 int sum = 0;
1187 char *e = buf + count;
1188 while (buf < e)
1189 sum += (unsigned char) *buf++;
1190 return sum;
1191}
1192
1193static void show_status_line(void)
1194{
1195 int cnt = 0, cksum = 0;
1196
1197 // either we already have an error or status message, or we
1198 // create one.
1199 if (!have_status_msg) {
1200 cnt = format_edit_status();
1201 cksum = bufsum(status_buffer, cnt);
1202 }
1203 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
1204 last_status_cksum = cksum; // remember if we have seen this line
1205 go_bottom_and_clear_to_eol();
1206 write1(status_buffer);
1207 if (have_status_msg) {
1208 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
1209 (columns - 1) ) {
1210 have_status_msg = 0;
1211 Hit_Return();
1212 }
1213 have_status_msg = 0;
1214 }
1215 place_cursor(crow, ccol); // put cursor back in correct place
1216 }
1217 fflush_all();
1218}
1219
1220//----- format the status buffer, the bottom line of screen ------
1221// format status buffer, with STANDOUT mode
1222static void status_line_bold(const char *format, ...)
1223{
1224 va_list args;
1225
1226 va_start(args, format);
1227 strcpy(status_buffer, ESC_BOLD_TEXT);
1228 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
1229 strcat(status_buffer, ESC_NORM_TEXT);
1230 va_end(args);
1231
1232 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
1233}
1234
1235static void status_line_bold_errno(const char *fn)
1236{
1237 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
1238}
1239
1240// format status buffer
1241static void status_line(const char *format, ...)
1242{
1243 va_list args;
1244
1245 va_start(args, format);
1246 vsprintf(status_buffer, format, args);
1247 va_end(args);
1248
1249 have_status_msg = 1;
1250}
1251
1252// copy s to buf, convert unprintable
1253static void print_literal(char *buf, const char *s)
1254{
1255 char *d;
1256 unsigned char c;
1257
1258 buf[0] = '\0';
1259 if (!s[0])
1260 s = "(NULL)";
1261
1262 d = buf;
1263 for (; *s; s++) {
1264 int c_is_no_print;
1265
1266 c = *s;
1267 c_is_no_print = (c & 0x80) && !Isprint(c);
1268 if (c_is_no_print) {
1269 strcpy(d, ESC_NORM_TEXT);
1270 d += sizeof(ESC_NORM_TEXT)-1;
1271 c = '.';
1272 }
1273 if (c < ' ' || c == 0x7f) {
1274 *d++ = '^';
1275 c |= '@'; // 0x40
1276 if (c == 0x7f)
1277 c = '?';
1278 }
1279 *d++ = c;
1280 *d = '\0';
1281 if (c_is_no_print) {
1282 strcpy(d, ESC_BOLD_TEXT);
1283 d += sizeof(ESC_BOLD_TEXT)-1;
1284 }
1285 if (*s == '\n') {
1286 *d++ = '$';
1287 *d = '\0';
1288 }
1289 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
1290 break;
1291 }
1292}
1293
1294static void not_implemented(const char *s)
1295{
1296 char buf[MAX_INPUT_LEN];
1297
1298 print_literal(buf, s);
1299 status_line_bold("\'%s\' is not implemented", buf);
1300}
1301
1302#if ENABLE_FEATURE_VI_YANKMARK
1303static char *text_yank(char *p, char *q, int dest) // copy text into a register
1304{
1305 int cnt = q - p;
1306 if (cnt < 0) { // they are backwards- reverse them
1307 p = q;
1308 cnt = -cnt;
1309 }
1310 free(reg[dest]); // if already a yank register, free it
1311 reg[dest] = xstrndup(p, cnt + 1);
1312 return p;
1313}
1314
1315static char what_reg(void)
1316{
1317 char c;
1318
1319 c = 'D'; // default to D-reg
1320 if (0 <= YDreg && YDreg <= 25)
1321 c = 'a' + (char) YDreg;
1322 if (YDreg == 26)
1323 c = 'D';
1324 if (YDreg == 27)
1325 c = 'U';
1326 return c;
1327}
1328
1329static void check_context(char cmd)
1330{
1331 // A context is defined to be "modifying text"
1332 // Any modifying command establishes a new context.
1333
1334 if (dot < context_start || dot > context_end) {
1335 if (strchr(modifying_cmds, cmd) != NULL) {
1336 // we are trying to modify text[]- make this the current context
1337 mark[27] = mark[26]; // move cur to prev
1338 mark[26] = dot; // move local to cur
1339 context_start = prev_line(prev_line(dot));
1340 context_end = next_line(next_line(dot));
1341 //loiter= start_loiter= now;
1342 }
1343 }
1344}
1345
1346static char *swap_context(char *p) // goto new context for '' command make this the current context
1347{
1348 char *tmp;
1349
1350 // the current context is in mark[26]
1351 // the previous context is in mark[27]
1352 // only swap context if other context is valid
1353 if (text <= mark[27] && mark[27] <= end - 1) {
1354 tmp = mark[27];
1355 mark[27] = p;
1356 mark[26] = p = tmp;
1357 context_start = prev_line(prev_line(prev_line(p)));
1358 context_end = next_line(next_line(next_line(p)));
1359 }
1360 return p;
1361}
1362#endif /* FEATURE_VI_YANKMARK */
1363
1364#if ENABLE_FEATURE_VI_UNDO
1365static void undo_push(char *, unsigned, unsigned char);
1366#endif
1367
1368// open a hole in text[]
1369// might reallocate text[]! use p += text_hole_make(p, ...),
1370// and be careful to not use pointers into potentially freed text[]!
1371static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
1372{
1373 uintptr_t bias = 0;
1374
1375 if (size <= 0)
1376 return bias;
1377 end += size; // adjust the new END
1378 if (end >= (text + text_size)) {
1379 char *new_text;
1380 text_size += end - (text + text_size) + 10240;
1381 new_text = xrealloc(text, text_size);
1382 bias = (new_text - text);
1383 screenbegin += bias;
1384 dot += bias;
1385 end += bias;
1386 p += bias;
1387#if ENABLE_FEATURE_VI_YANKMARK
1388 {
1389 int i;
1390 for (i = 0; i < ARRAY_SIZE(mark); i++)
1391 if (mark[i])
1392 mark[i] += bias;
1393 }
1394#endif
1395 text = new_text;
1396 }
1397 memmove(p + size, p, end - size - p);
1398 memset(p, ' ', size); // clear new hole
1399 return bias;
1400}
1401
1402// close a hole in text[] - delete "p" through "q", inclusive
1403// "undo" value indicates if this operation should be undo-able
1404#if !ENABLE_FEATURE_VI_UNDO
1405#define text_hole_delete(a,b,c) text_hole_delete(a,b)
1406#endif
1407static char *text_hole_delete(char *p, char *q, int undo)
1408{
1409 char *src, *dest;
1410 int cnt, hole_size;
1411
1412 // move forwards, from beginning
1413 // assume p <= q
1414 src = q + 1;
1415 dest = p;
1416 if (q < p) { // they are backward- swap them
1417 src = p + 1;
1418 dest = q;
1419 }
1420 hole_size = q - p + 1;
1421 cnt = end - src;
1422#if ENABLE_FEATURE_VI_UNDO
1423 switch (undo) {
1424 case NO_UNDO:
1425 break;
1426 case ALLOW_UNDO:
1427 undo_push(p, hole_size, UNDO_DEL);
1428 break;
1429 case ALLOW_UNDO_CHAIN:
1430 undo_push(p, hole_size, UNDO_DEL_CHAIN);
1431 break;
1432# if ENABLE_FEATURE_VI_UNDO_QUEUE
1433 case ALLOW_UNDO_QUEUED:
1434 undo_push(p, hole_size, UNDO_DEL_QUEUED);
1435 break;
1436# endif
1437 }
1438 modified_count--;
1439#endif
1440 if (src < text || src > end)
1441 goto thd0;
1442 if (dest < text || dest >= end)
1443 goto thd0;
1444 modified_count++;
1445 if (src >= end)
1446 goto thd_atend; // just delete the end of the buffer
1447 memmove(dest, src, cnt);
1448 thd_atend:
1449 end = end - hole_size; // adjust the new END
1450 if (dest >= end)
1451 dest = end - 1; // make sure dest in below end-1
1452 if (end <= text)
1453 dest = end = text; // keep pointers valid
1454 thd0:
1455 return dest;
1456}
1457
1458#if ENABLE_FEATURE_VI_UNDO
1459
1460# if ENABLE_FEATURE_VI_UNDO_QUEUE
1461// Flush any queued objects to the undo stack
1462static void undo_queue_commit(void)
1463{
1464 // Pushes the queue object onto the undo stack
1465 if (undo_q > 0) {
1466 // Deleted character undo events grow from the end
1467 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
1468 undo_q,
1469 (undo_queue_state | UNDO_USE_SPOS)
1470 );
1471 undo_queue_state = UNDO_EMPTY;
1472 undo_q = 0;
1473 }
1474}
1475# else
1476# define undo_queue_commit() ((void)0)
1477# endif
1478
1479static void flush_undo_data(void)
1480{
1481 struct undo_object *undo_entry;
1482
1483 while (undo_stack_tail) {
1484 undo_entry = undo_stack_tail;
1485 undo_stack_tail = undo_entry->prev;
1486 free(undo_entry);
1487 }
1488}
1489
1490// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
1491// Add to the undo stack
1492static void undo_push(char *src, unsigned length, uint8_t u_type)
1493{
1494 struct undo_object *undo_entry;
1495
1496 // "u_type" values
1497 // UNDO_INS: insertion, undo will remove from buffer
1498 // UNDO_DEL: deleted text, undo will restore to buffer
1499 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
1500 // The CHAIN operations are for handling multiple operations that the user
1501 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
1502 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
1503 // for the INS/DEL operation. The raw values should be equal to the values of
1504 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
1505
1506# if ENABLE_FEATURE_VI_UNDO_QUEUE
1507 // This undo queuing functionality groups multiple character typing or backspaces
1508 // into a single large undo object. This greatly reduces calls to malloc() for
1509 // single-character operations while typing and has the side benefit of letting
1510 // an undo operation remove chunks of text rather than a single character.
1511 switch (u_type) {
1512 case UNDO_EMPTY: // Just in case this ever happens...
1513 return;
1514 case UNDO_DEL_QUEUED:
1515 if (length != 1)
1516 return; // Only queue single characters
1517 switch (undo_queue_state) {
1518 case UNDO_EMPTY:
1519 undo_queue_state = UNDO_DEL;
1520 case UNDO_DEL:
1521 undo_queue_spos = src;
1522 undo_q++;
1523 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
1524 // If queue is full, dump it into an object
1525 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1526 undo_queue_commit();
1527 return;
1528 case UNDO_INS:
1529 // Switch from storing inserted text to deleted text
1530 undo_queue_commit();
1531 undo_push(src, length, UNDO_DEL_QUEUED);
1532 return;
1533 }
1534 break;
1535 case UNDO_INS_QUEUED:
1536 if (length < 1)
1537 return;
1538 switch (undo_queue_state) {
1539 case UNDO_EMPTY:
1540 undo_queue_state = UNDO_INS;
1541 undo_queue_spos = src;
1542 case UNDO_INS:
1543 while (length--) {
1544 undo_q++; // Don't need to save any data for insertions
1545 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
1546 undo_queue_commit();
1547 }
1548 return;
1549 case UNDO_DEL:
1550 // Switch from storing deleted text to inserted text
1551 undo_queue_commit();
1552 undo_push(src, length, UNDO_INS_QUEUED);
1553 return;
1554 }
1555 break;
1556 }
1557# else
1558 // If undo queuing is disabled, ignore the queuing flag entirely
1559 u_type = u_type & ~UNDO_QUEUED_FLAG;
1560# endif
1561
1562 // Allocate a new undo object
1563 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
1564 // For UNDO_DEL objects, save deleted text
1565 if ((text + length) == end)
1566 length--;
1567 // If this deletion empties text[], strip the newline. When the buffer becomes
1568 // zero-length, a newline is added back, which requires this to compensate.
1569 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
1570 memcpy(undo_entry->undo_text, src, length);
1571 } else {
1572 undo_entry = xzalloc(sizeof(*undo_entry));
1573 }
1574 undo_entry->length = length;
1575# if ENABLE_FEATURE_VI_UNDO_QUEUE
1576 if ((u_type & UNDO_USE_SPOS) != 0) {
1577 undo_entry->start = undo_queue_spos - text; // use start position from queue
1578 } else {
1579 undo_entry->start = src - text; // use offset from start of text buffer
1580 }
1581 u_type = (u_type & ~UNDO_USE_SPOS);
1582# else
1583 undo_entry->start = src - text;
1584# endif
1585 undo_entry->u_type = u_type;
1586
1587 // Push it on undo stack
1588 undo_entry->prev = undo_stack_tail;
1589 undo_stack_tail = undo_entry;
1590 modified_count++;
1591}
1592
1593static void undo_push_insert(char *p, int len, int undo)
1594{
1595 switch (undo) {
1596 case ALLOW_UNDO:
1597 undo_push(p, len, UNDO_INS);
1598 break;
1599 case ALLOW_UNDO_CHAIN:
1600 undo_push(p, len, UNDO_INS_CHAIN);
1601 break;
1602# if ENABLE_FEATURE_VI_UNDO_QUEUE
1603 case ALLOW_UNDO_QUEUED:
1604 undo_push(p, len, UNDO_INS_QUEUED);
1605 break;
1606# endif
1607 }
1608}
1609
1610// Undo the last operation
1611static void undo_pop(void)
1612{
1613 int repeat;
1614 char *u_start, *u_end;
1615 struct undo_object *undo_entry;
1616
1617 // Commit pending undo queue before popping (should be unnecessary)
1618 undo_queue_commit();
1619
1620 undo_entry = undo_stack_tail;
1621 // Check for an empty undo stack
1622 if (!undo_entry) {
1623 status_line("Already at oldest change");
1624 return;
1625 }
1626
1627 switch (undo_entry->u_type) {
1628 case UNDO_DEL:
1629 case UNDO_DEL_CHAIN:
1630 // make hole and put in text that was deleted; deallocate text
1631 u_start = text + undo_entry->start;
1632 text_hole_make(u_start, undo_entry->length);
1633 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
1634 status_line("Undo [%d] %s %d chars at position %d",
1635 modified_count, "restored",
1636 undo_entry->length, undo_entry->start
1637 );
1638 break;
1639 case UNDO_INS:
1640 case UNDO_INS_CHAIN:
1641 // delete what was inserted
1642 u_start = undo_entry->start + text;
1643 u_end = u_start - 1 + undo_entry->length;
1644 text_hole_delete(u_start, u_end, NO_UNDO);
1645 status_line("Undo [%d] %s %d chars at position %d",
1646 modified_count, "deleted",
1647 undo_entry->length, undo_entry->start
1648 );
1649 break;
1650 }
1651 repeat = 0;
1652 switch (undo_entry->u_type) {
1653 // If this is the end of a chain, lower modification count and refresh display
1654 case UNDO_DEL:
1655 case UNDO_INS:
1656 dot = (text + undo_entry->start);
1657 refresh(FALSE);
1658 break;
1659 case UNDO_DEL_CHAIN:
1660 case UNDO_INS_CHAIN:
1661 repeat = 1;
1662 break;
1663 }
1664 // Deallocate the undo object we just processed
1665 undo_stack_tail = undo_entry->prev;
1666 free(undo_entry);
1667 modified_count--;
1668 // For chained operations, continue popping all the way down the chain.
1669 if (repeat) {
1670 undo_pop(); // Follow the undo chain if one exists
1671 }
1672}
1673
1674#else
1675# define flush_undo_data() ((void)0)
1676# define undo_queue_commit() ((void)0)
1677#endif /* ENABLE_FEATURE_VI_UNDO */
1678
1679//----- Dot Movement Routines ----------------------------------
1680static void dot_left(void)
1681{
1682 undo_queue_commit();
1683 if (dot > text && dot[-1] != '\n')
1684 dot--;
1685}
1686
1687static void dot_right(void)
1688{
1689 undo_queue_commit();
1690 if (dot < end - 1 && *dot != '\n')
1691 dot++;
1692}
1693
1694static void dot_begin(void)
1695{
1696 undo_queue_commit();
1697 dot = begin_line(dot); // return pointer to first char cur line
1698}
1699
1700static void dot_end(void)
1701{
1702 undo_queue_commit();
1703 dot = end_line(dot); // return pointer to last char cur line
1704}
1705
1706static char *move_to_col(char *p, int l)
1707{
1708 int co;
1709
1710 p = begin_line(p);
1711 co = 0;
1712 while (co < l && p < end) {
1713 if (*p == '\n') //vda || *p == '\0')
1714 break;
1715 if (*p == '\t') {
1716 co = next_tabstop(co);
1717 } else if (*p < ' ' || *p == 127) {
1718 co++; // display as ^X, use 2 columns
1719 }
1720 co++;
1721 p++;
1722 }
1723 return p;
1724}
1725
1726static void dot_next(void)
1727{
1728 undo_queue_commit();
1729 dot = next_line(dot);
1730}
1731
1732static void dot_prev(void)
1733{
1734 undo_queue_commit();
1735 dot = prev_line(dot);
1736}
1737
1738static void dot_skip_over_ws(void)
1739{
1740 // skip WS
1741 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1742 dot++;
1743}
1744
1745static void dot_scroll(int cnt, int dir)
1746{
1747 char *q;
1748
1749 undo_queue_commit();
1750 for (; cnt > 0; cnt--) {
1751 if (dir < 0) {
1752 // scroll Backwards
1753 // ctrl-Y scroll up one line
1754 screenbegin = prev_line(screenbegin);
1755 } else {
1756 // scroll Forwards
1757 // ctrl-E scroll down one line
1758 screenbegin = next_line(screenbegin);
1759 }
1760 }
1761 // make sure "dot" stays on the screen so we dont scroll off
1762 if (dot < screenbegin)
1763 dot = screenbegin;
1764 q = end_screen(); // find new bottom line
1765 if (dot > q)
1766 dot = begin_line(q); // is dot is below bottom line?
1767 dot_skip_over_ws();
1768}
1769
1770static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1771{
1772 if (p >= end && end > text) {
1773 p = end - 1;
1774 indicate_error();
1775 }
1776 if (p < text) {
1777 p = text;
1778 indicate_error();
1779 }
1780 return p;
1781}
1782
1783#if ENABLE_FEATURE_VI_DOT_CMD
1784static void start_new_cmd_q(char c)
1785{
1786 // get buffer for new cmd
1787 // if there is a current cmd count put it in the buffer first
1788 if (cmdcnt > 0) {
1789 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
1790 } else { // just save char c onto queue
1791 last_modifying_cmd[0] = c;
1792 lmc_len = 1;
1793 }
1794 adding2q = 1;
1795}
1796static void end_cmd_q(void)
1797{
1798# if ENABLE_FEATURE_VI_YANKMARK
1799 YDreg = 26; // go back to default Yank/Delete reg
1800# endif
1801 adding2q = 0;
1802}
1803#else
1804# define end_cmd_q() ((void)0)
1805#endif /* FEATURE_VI_DOT_CMD */
1806
1807// copy text into register, then delete text.
1808// if dist <= 0, do not include, or go past, a NewLine
1809//
1810#if !ENABLE_FEATURE_VI_UNDO
1811#define yank_delete(a,b,c,d,e) yank_delete(a,b,c,d)
1812#endif
1813static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
1814{
1815 char *p;
1816
1817 // make sure start <= stop
1818 if (start > stop) {
1819 // they are backwards, reverse them
1820 p = start;
1821 start = stop;
1822 stop = p;
1823 }
1824 if (dist <= 0) {
1825 // we cannot cross NL boundaries
1826 p = start;
1827 if (*p == '\n')
1828 return p;
1829 // dont go past a NewLine
1830 for (; p + 1 <= stop; p++) {
1831 if (p[1] == '\n') {
1832 stop = p; // "stop" just before NewLine
1833 break;
1834 }
1835 }
1836 }
1837 p = start;
1838#if ENABLE_FEATURE_VI_YANKMARK
1839 text_yank(start, stop, YDreg);
1840#endif
1841 if (yf == YANKDEL) {
1842 p = text_hole_delete(start, stop, undo);
1843 } // delete lines
1844 return p;
1845}
1846
1847// might reallocate text[]!
1848static int file_insert(const char *fn, char *p, int initial)
1849{
1850 int cnt = -1;
1851 int fd, size;
1852 struct stat statbuf;
1853
1854 if (p < text)
1855 p = text;
1856 if (p > end)
1857 p = end;
1858
1859 fd = open(fn, O_RDONLY);
1860 if (fd < 0) {
1861 if (!initial)
1862 status_line_bold_errno(fn);
1863 return cnt;
1864 }
1865
1866 // Validate file
1867 if (fstat(fd, &statbuf) < 0) {
1868 status_line_bold_errno(fn);
1869 goto fi;
1870 }
1871 if (!S_ISREG(statbuf.st_mode)) {
1872 status_line_bold("'%s' is not a regular file", fn);
1873 goto fi;
1874 }
1875 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
1876 p += text_hole_make(p, size);
1877 cnt = full_read(fd, p, size);
1878 if (cnt < 0) {
1879 status_line_bold_errno(fn);
1880 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
1881 } else if (cnt < size) {
1882 // There was a partial read, shrink unused space
1883 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
1884 status_line_bold("can't read '%s'", fn);
1885 }
1886 fi:
1887 close(fd);
1888
1889#if ENABLE_FEATURE_VI_READONLY
1890 if (initial
1891 && ((access(fn, W_OK) < 0) ||
1892 // root will always have access()
1893 // so we check fileperms too
1894 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
1895 )
1896 ) {
1897 SET_READONLY_FILE(readonly_mode);
1898 }
1899#endif
1900 return cnt;
1901}
1902
1903// find matching char of pair () [] {}
1904// will crash if c is not one of these
1905static char *find_pair(char *p, const char c)
1906{
1907 const char *braces = "()[]{}";
1908 char match;
1909 int dir, level;
1910
1911 dir = strchr(braces, c) - braces;
1912 dir ^= 1;
1913 match = braces[dir];
1914 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
1915
1916 // look for match, count levels of pairs (( ))
1917 level = 1;
1918 for (;;) {
1919 p += dir;
1920 if (p < text || p >= end)
1921 return NULL;
1922 if (*p == c)
1923 level++; // increase pair levels
1924 if (*p == match) {
1925 level--; // reduce pair level
1926 if (level == 0)
1927 return p; // found matching pair
1928 }
1929 }
1930}
1931
1932#if ENABLE_FEATURE_VI_SETOPTS
1933// show the matching char of a pair, () [] {}
1934static void showmatching(char *p)
1935{
1936 char *q, *save_dot;
1937
1938 // we found half of a pair
1939 q = find_pair(p, *p); // get loc of matching char
1940 if (q == NULL) {
1941 indicate_error(); // no matching char
1942 } else {
1943 // "q" now points to matching pair
1944 save_dot = dot; // remember where we are
1945 dot = q; // go to new loc
1946 refresh(FALSE); // let the user see it
1947 mysleep(40); // give user some time
1948 dot = save_dot; // go back to old loc
1949 refresh(FALSE);
1950 }
1951}
1952#endif /* FEATURE_VI_SETOPTS */
1953
1954// might reallocate text[]! use p += stupid_insert(p, ...),
1955// and be careful to not use pointers into potentially freed text[]!
1956static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1957{
1958 uintptr_t bias;
1959 bias = text_hole_make(p, 1);
1960 p += bias;
1961 *p = c;
1962 return bias;
1963}
1964
1965#if !ENABLE_FEATURE_VI_UNDO
1966#define char_insert(a,b,c) char_insert(a,b)
1967#endif
1968static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1969{
1970 if (c == 22) { // Is this an ctrl-V?
1971 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1972 refresh(FALSE); // show the ^
1973 c = get_one_char();
1974 *p = c;
1975#if ENABLE_FEATURE_VI_UNDO
1976 undo_push_insert(p, 1, undo);
1977#else
1978 modified_count++;
1979#endif /* ENABLE_FEATURE_VI_UNDO */
1980 p++;
1981 } else if (c == 27) { // Is this an ESC?
1982 cmd_mode = 0;
1983 undo_queue_commit();
1984 cmdcnt = 0;
1985 end_cmd_q(); // stop adding to q
1986 last_status_cksum = 0; // force status update
1987 if ((p[-1] != '\n') && (dot > text)) {
1988 p--;
1989 }
1990 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1991 if (p > text) {
1992 p--;
1993 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1994 }
1995 } else {
1996 // insert a char into text[]
1997 if (c == 13)
1998 c = '\n'; // translate \r to \n
1999#if ENABLE_FEATURE_VI_UNDO
2000# if ENABLE_FEATURE_VI_UNDO_QUEUE
2001 if (c == '\n')
2002 undo_queue_commit();
2003# endif
2004 undo_push_insert(p, 1, undo);
2005#else
2006 modified_count++;
2007#endif /* ENABLE_FEATURE_VI_UNDO */
2008 p += 1 + stupid_insert(p, c); // insert the char
2009#if ENABLE_FEATURE_VI_SETOPTS
2010 if (showmatch && strchr(")]}", c) != NULL) {
2011 showmatching(p - 1);
2012 }
2013 if (autoindent && c == '\n') { // auto indent the new line
2014 char *q;
2015 size_t len;
2016 q = prev_line(p); // use prev line as template
2017 len = strspn(q, " \t"); // space or tab
2018 if (len) {
2019 uintptr_t bias;
2020 bias = text_hole_make(p, len);
2021 p += bias;
2022 q += bias;
2023#if ENABLE_FEATURE_VI_UNDO
2024 undo_push_insert(p, len, undo);
2025#endif
2026 memcpy(p, q, len);
2027 p += len;
2028 }
2029 }
2030#endif
2031 }
2032 return p;
2033}
2034
2035// read text from file or create an empty buf
2036// will also update current_filename
2037static int init_text_buffer(char *fn)
2038{
2039 int rc;
2040
2041 // allocate/reallocate text buffer
2042 free(text);
2043 text_size = 10240;
2044 screenbegin = dot = end = text = xzalloc(text_size);
2045
2046 if (fn != current_filename) {
2047 free(current_filename);
2048 current_filename = xstrdup(fn);
2049 }
2050 rc = file_insert(fn, text, 1);
2051 if (rc < 0) {
2052 // file doesnt exist. Start empty buf with dummy line
2053 char_insert(text, '\n', NO_UNDO);
2054 }
2055
2056 flush_undo_data();
2057 modified_count = 0;
2058 last_modified_count = -1;
2059#if ENABLE_FEATURE_VI_YANKMARK
2060 // init the marks
2061 memset(mark, 0, sizeof(mark));
2062#endif
2063 return rc;
2064}
2065
2066#if ENABLE_FEATURE_VI_YANKMARK \
2067 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2068 || ENABLE_FEATURE_VI_CRASHME
2069// might reallocate text[]! use p += string_insert(p, ...),
2070// and be careful to not use pointers into potentially freed text[]!
2071# if !ENABLE_FEATURE_VI_UNDO
2072# define string_insert(a,b,c) string_insert(a,b)
2073# endif
2074static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2075{
2076 uintptr_t bias;
2077 int i;
2078
2079 i = strlen(s);
2080#if ENABLE_FEATURE_VI_UNDO
2081 undo_push_insert(p, i, undo);
2082#endif
2083 bias = text_hole_make(p, i);
2084 p += bias;
2085 memcpy(p, s, i);
2086#if ENABLE_FEATURE_VI_YANKMARK
2087 {
2088 int cnt;
2089 for (cnt = 0; *s != '\0'; s++) {
2090 if (*s == '\n')
2091 cnt++;
2092 }
2093 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2094 }
2095#endif
2096 return bias;
2097}
2098#endif
2099
2100static int file_write(char *fn, char *first, char *last)
2101{
2102 int fd, cnt, charcnt;
2103
2104 if (fn == 0) {
2105 status_line_bold("No current filename");
2106 return -2;
2107 }
2108 // By popular request we do not open file with O_TRUNC,
2109 // but instead ftruncate() it _after_ successful write.
2110 // Might reduce amount of data lost on power fail etc.
2111 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2112 if (fd < 0)
2113 return -1;
2114 cnt = last - first + 1;
2115 charcnt = full_write(fd, first, cnt);
2116 ftruncate(fd, charcnt);
2117 if (charcnt == cnt) {
2118 // good write
2119 //modified_count = FALSE;
2120 } else {
2121 charcnt = 0;
2122 }
2123 close(fd);
2124 return charcnt;
2125}
2126
2127#if ENABLE_FEATURE_VI_SEARCH
2128# if ENABLE_FEATURE_VI_REGEX_SEARCH
2129// search for pattern starting at p
2130static char *char_search(char *p, const char *pat, int dir_and_range)
2131{
2132 struct re_pattern_buffer preg;
2133 const char *err;
2134 char *q;
2135 int i;
2136 int size;
2137 int range;
2138
2139 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
2140 if (ignorecase)
2141 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
2142
2143 memset(&preg, 0, sizeof(preg));
2144 err = re_compile_pattern(pat, strlen(pat), &preg);
2145 if (err != NULL) {
2146 status_line_bold("bad search pattern '%s': %s", pat, err);
2147 return p;
2148 }
2149
2150 range = (dir_and_range & 1);
2151 q = end - 1; // if FULL
2152 if (range == LIMITED)
2153 q = next_line(p);
2154 if (dir_and_range < 0) { // BACK?
2155 q = text;
2156 if (range == LIMITED)
2157 q = prev_line(p);
2158 }
2159
2160 // RANGE could be negative if we are searching backwards
2161 range = q - p;
2162 q = p;
2163 size = range;
2164 if (range < 0) {
2165 size = -size;
2166 q = p - size;
2167 if (q < text)
2168 q = text;
2169 }
2170 // search for the compiled pattern, preg, in p[]
2171 // range < 0: search backward
2172 // range > 0: search forward
2173 // 0 < start < size
2174 // re_search() < 0: not found or error
2175 // re_search() >= 0: index of found pattern
2176 // struct pattern char int int int struct reg
2177 // re_search(*pattern_buffer, *string, size, start, range, *regs)
2178 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
2179 regfree(&preg);
2180 if (i < 0)
2181 return NULL;
2182 if (dir_and_range > 0) // FORWARD?
2183 p = p + i;
2184 else
2185 p = p - i;
2186 return p;
2187}
2188# else
2189# if ENABLE_FEATURE_VI_SETOPTS
2190static int mycmp(const char *s1, const char *s2, int len)
2191{
2192 if (ignorecase) {
2193 return strncasecmp(s1, s2, len);
2194 }
2195 return strncmp(s1, s2, len);
2196}
2197# else
2198# define mycmp strncmp
2199# endif
2200static char *char_search(char *p, const char *pat, int dir_and_range)
2201{
2202 char *start, *stop;
2203 int len;
2204 int range;
2205
2206 len = strlen(pat);
2207 range = (dir_and_range & 1);
2208 if (dir_and_range > 0) { //FORWARD?
2209 stop = end - 1; // assume range is p..end-1
2210 if (range == LIMITED)
2211 stop = next_line(p); // range is to next line
2212 for (start = p; start < stop; start++) {
2213 if (mycmp(start, pat, len) == 0) {
2214 return start;
2215 }
2216 }
2217 } else { //BACK
2218 stop = text; // assume range is text..p
2219 if (range == LIMITED)
2220 stop = prev_line(p); // range is to prev line
2221 for (start = p - len; start >= stop; start--) {
2222 if (mycmp(start, pat, len) == 0) {
2223 return start;
2224 }
2225 }
2226 }
2227 // pattern not found
2228 return NULL;
2229}
2230# endif
2231#endif /* FEATURE_VI_SEARCH */
2232
972//----- The Colon commands ------------------------------------- 2233//----- The Colon commands -------------------------------------
973#if ENABLE_FEATURE_VI_COLON 2234#if ENABLE_FEATURE_VI_COLON
974static char *get_one_address(char *p, int *addr) // get colon addr, if present 2235static char *get_one_address(char *p, int *addr) // get colon addr, if present
@@ -1619,122 +2880,6 @@ static void colon(char *buf)
1619#endif /* FEATURE_VI_COLON */ 2880#endif /* FEATURE_VI_COLON */
1620} 2881}
1621 2882
1622static void Hit_Return(void)
1623{
1624 int c;
1625
1626 standout_start();
1627 write1("[Hit return to continue]");
1628 standout_end();
1629 while ((c = get_one_char()) != '\n' && c != '\r')
1630 continue;
1631 redraw(TRUE); // force redraw all
1632}
1633
1634//----- Dot Movement Routines ----------------------------------
1635static void dot_left(void)
1636{
1637 undo_queue_commit();
1638 if (dot > text && dot[-1] != '\n')
1639 dot--;
1640}
1641
1642static void dot_right(void)
1643{
1644 undo_queue_commit();
1645 if (dot < end - 1 && *dot != '\n')
1646 dot++;
1647}
1648
1649static void dot_begin(void)
1650{
1651 undo_queue_commit();
1652 dot = begin_line(dot); // return pointer to first char cur line
1653}
1654
1655static void dot_end(void)
1656{
1657 undo_queue_commit();
1658 dot = end_line(dot); // return pointer to last char cur line
1659}
1660
1661static char *move_to_col(char *p, int l)
1662{
1663 int co;
1664
1665 p = begin_line(p);
1666 co = 0;
1667 while (co < l && p < end) {
1668 if (*p == '\n') //vda || *p == '\0')
1669 break;
1670 if (*p == '\t') {
1671 co = next_tabstop(co);
1672 } else if (*p < ' ' || *p == 127) {
1673 co++; // display as ^X, use 2 columns
1674 }
1675 co++;
1676 p++;
1677 }
1678 return p;
1679}
1680
1681static void dot_next(void)
1682{
1683 undo_queue_commit();
1684 dot = next_line(dot);
1685}
1686
1687static void dot_prev(void)
1688{
1689 undo_queue_commit();
1690 dot = prev_line(dot);
1691}
1692
1693static void dot_scroll(int cnt, int dir)
1694{
1695 char *q;
1696
1697 undo_queue_commit();
1698 for (; cnt > 0; cnt--) {
1699 if (dir < 0) {
1700 // scroll Backwards
1701 // ctrl-Y scroll up one line
1702 screenbegin = prev_line(screenbegin);
1703 } else {
1704 // scroll Forwards
1705 // ctrl-E scroll down one line
1706 screenbegin = next_line(screenbegin);
1707 }
1708 }
1709 // make sure "dot" stays on the screen so we dont scroll off
1710 if (dot < screenbegin)
1711 dot = screenbegin;
1712 q = end_screen(); // find new bottom line
1713 if (dot > q)
1714 dot = begin_line(q); // is dot is below bottom line?
1715 dot_skip_over_ws();
1716}
1717
1718static void dot_skip_over_ws(void)
1719{
1720 // skip WS
1721 while (isspace(*dot) && *dot != '\n' && dot < end - 1)
1722 dot++;
1723}
1724
1725static char *bound_dot(char *p) // make sure text[0] <= P < "end"
1726{
1727 if (p >= end && end > text) {
1728 p = end - 1;
1729 indicate_error();
1730 }
1731 if (p < text) {
1732 p = text;
1733 indicate_error();
1734 }
1735 return p;
1736}
1737
1738//----- Helper Utility Routines -------------------------------- 2883//----- Helper Utility Routines --------------------------------
1739 2884
1740//---------------------------------------------------------------- 2885//----------------------------------------------------------------
@@ -1764,197 +2909,6 @@ static char *new_screen(int ro, int co)
1764 return screen; 2909 return screen;
1765} 2910}
1766 2911
1767#if ENABLE_FEATURE_VI_SEARCH
1768
1769# if ENABLE_FEATURE_VI_REGEX_SEARCH
1770
1771// search for pattern starting at p
1772static char *char_search(char *p, const char *pat, int dir_and_range)
1773{
1774 struct re_pattern_buffer preg;
1775 const char *err;
1776 char *q;
1777 int i;
1778 int size;
1779 int range;
1780
1781 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED;
1782 if (ignorecase)
1783 re_syntax_options = RE_SYNTAX_POSIX_EXTENDED | RE_ICASE;
1784
1785 memset(&preg, 0, sizeof(preg));
1786 err = re_compile_pattern(pat, strlen(pat), &preg);
1787 if (err != NULL) {
1788 status_line_bold("bad search pattern '%s': %s", pat, err);
1789 return p;
1790 }
1791
1792 range = (dir_and_range & 1);
1793 q = end - 1; // if FULL
1794 if (range == LIMITED)
1795 q = next_line(p);
1796 if (dir_and_range < 0) { // BACK?
1797 q = text;
1798 if (range == LIMITED)
1799 q = prev_line(p);
1800 }
1801
1802 // RANGE could be negative if we are searching backwards
1803 range = q - p;
1804 q = p;
1805 size = range;
1806 if (range < 0) {
1807 size = -size;
1808 q = p - size;
1809 if (q < text)
1810 q = text;
1811 }
1812 // search for the compiled pattern, preg, in p[]
1813 // range < 0: search backward
1814 // range > 0: search forward
1815 // 0 < start < size
1816 // re_search() < 0: not found or error
1817 // re_search() >= 0: index of found pattern
1818 // struct pattern char int int int struct reg
1819 // re_search(*pattern_buffer, *string, size, start, range, *regs)
1820 i = re_search(&preg, q, size, /*start:*/ 0, range, /*struct re_registers*:*/ NULL);
1821 regfree(&preg);
1822 if (i < 0)
1823 return NULL;
1824 if (dir_and_range > 0) // FORWARD?
1825 p = p + i;
1826 else
1827 p = p - i;
1828 return p;
1829}
1830
1831# else
1832
1833# if ENABLE_FEATURE_VI_SETOPTS
1834static int mycmp(const char *s1, const char *s2, int len)
1835{
1836 if (ignorecase) {
1837 return strncasecmp(s1, s2, len);
1838 }
1839 return strncmp(s1, s2, len);
1840}
1841# else
1842# define mycmp strncmp
1843# endif
1844
1845static char *char_search(char *p, const char *pat, int dir_and_range)
1846{
1847 char *start, *stop;
1848 int len;
1849 int range;
1850
1851 len = strlen(pat);
1852 range = (dir_and_range & 1);
1853 if (dir_and_range > 0) { //FORWARD?
1854 stop = end - 1; // assume range is p..end-1
1855 if (range == LIMITED)
1856 stop = next_line(p); // range is to next line
1857 for (start = p; start < stop; start++) {
1858 if (mycmp(start, pat, len) == 0) {
1859 return start;
1860 }
1861 }
1862 } else { //BACK
1863 stop = text; // assume range is text..p
1864 if (range == LIMITED)
1865 stop = prev_line(p); // range is to prev line
1866 for (start = p - len; start >= stop; start--) {
1867 if (mycmp(start, pat, len) == 0) {
1868 return start;
1869 }
1870 }
1871 }
1872 // pattern not found
1873 return NULL;
1874}
1875
1876# endif
1877
1878#endif /* FEATURE_VI_SEARCH */
1879
1880static char *char_insert(char *p, char c, int undo) // insert the char c at 'p'
1881{
1882 if (c == 22) { // Is this an ctrl-V?
1883 p += stupid_insert(p, '^'); // use ^ to indicate literal next
1884 refresh(FALSE); // show the ^
1885 c = get_one_char();
1886 *p = c;
1887#if ENABLE_FEATURE_VI_UNDO
1888 undo_push_insert(p, 1, undo);
1889#else
1890 modified_count++;
1891#endif /* ENABLE_FEATURE_VI_UNDO */
1892 p++;
1893 } else if (c == 27) { // Is this an ESC?
1894 cmd_mode = 0;
1895 undo_queue_commit();
1896 cmdcnt = 0;
1897 end_cmd_q(); // stop adding to q
1898 last_status_cksum = 0; // force status update
1899 if ((p[-1] != '\n') && (dot > text)) {
1900 p--;
1901 }
1902 } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS
1903 if (p > text) {
1904 p--;
1905 p = text_hole_delete(p, p, ALLOW_UNDO_QUEUED); // shrink buffer 1 char
1906 }
1907 } else {
1908 // insert a char into text[]
1909 if (c == 13)
1910 c = '\n'; // translate \r to \n
1911#if ENABLE_FEATURE_VI_UNDO
1912# if ENABLE_FEATURE_VI_UNDO_QUEUE
1913 if (c == '\n')
1914 undo_queue_commit();
1915# endif
1916 undo_push_insert(p, 1, undo);
1917#else
1918 modified_count++;
1919#endif /* ENABLE_FEATURE_VI_UNDO */
1920 p += 1 + stupid_insert(p, c); // insert the char
1921#if ENABLE_FEATURE_VI_SETOPTS
1922 if (showmatch && strchr(")]}", c) != NULL) {
1923 showmatching(p - 1);
1924 }
1925 if (autoindent && c == '\n') { // auto indent the new line
1926 char *q;
1927 size_t len;
1928 q = prev_line(p); // use prev line as template
1929 len = strspn(q, " \t"); // space or tab
1930 if (len) {
1931 uintptr_t bias;
1932 bias = text_hole_make(p, len);
1933 p += bias;
1934 q += bias;
1935#if ENABLE_FEATURE_VI_UNDO
1936 undo_push_insert(p, len, undo);
1937#endif
1938 memcpy(p, q, len);
1939 p += len;
1940 }
1941 }
1942#endif
1943 }
1944 return p;
1945}
1946
1947// might reallocate text[]! use p += stupid_insert(p, ...),
1948// and be careful to not use pointers into potentially freed text[]!
1949static uintptr_t stupid_insert(char *p, char c) // stupidly insert the char c at 'p'
1950{
1951 uintptr_t bias;
1952 bias = text_hole_make(p, 1);
1953 p += bias;
1954 *p = c;
1955 return bias;
1956}
1957
1958static int st_test(char *p, int type, int dir, char *tested) 2912static int st_test(char *p, int type, int dir, char *tested)
1959{ 2913{
1960 char c, c0, ci; 2914 char c, c0, ci;
@@ -2006,1061 +2960,6 @@ static char *skip_thing(char *p, int linecnt, int dir, int type)
2006 return p; 2960 return p;
2007} 2961}
2008 2962
2009// find matching char of pair () [] {}
2010// will crash if c is not one of these
2011static char *find_pair(char *p, const char c)
2012{
2013 const char *braces = "()[]{}";
2014 char match;
2015 int dir, level;
2016
2017 dir = strchr(braces, c) - braces;
2018 dir ^= 1;
2019 match = braces[dir];
2020 dir = ((dir & 1) << 1) - 1; // 1 for ([{, -1 for )\}
2021
2022 // look for match, count levels of pairs (( ))
2023 level = 1;
2024 for (;;) {
2025 p += dir;
2026 if (p < text || p >= end)
2027 return NULL;
2028 if (*p == c)
2029 level++; // increase pair levels
2030 if (*p == match) {
2031 level--; // reduce pair level
2032 if (level == 0)
2033 return p; // found matching pair
2034 }
2035 }
2036}
2037
2038#if ENABLE_FEATURE_VI_SETOPTS
2039// show the matching char of a pair, () [] {}
2040static void showmatching(char *p)
2041{
2042 char *q, *save_dot;
2043
2044 // we found half of a pair
2045 q = find_pair(p, *p); // get loc of matching char
2046 if (q == NULL) {
2047 indicate_error(); // no matching char
2048 } else {
2049 // "q" now points to matching pair
2050 save_dot = dot; // remember where we are
2051 dot = q; // go to new loc
2052 refresh(FALSE); // let the user see it
2053 mysleep(40); // give user some time
2054 dot = save_dot; // go back to old loc
2055 refresh(FALSE);
2056 }
2057}
2058#endif /* FEATURE_VI_SETOPTS */
2059
2060#if ENABLE_FEATURE_VI_UNDO
2061static void flush_undo_data(void)
2062{
2063 struct undo_object *undo_entry;
2064
2065 while (undo_stack_tail) {
2066 undo_entry = undo_stack_tail;
2067 undo_stack_tail = undo_entry->prev;
2068 free(undo_entry);
2069 }
2070}
2071
2072// Undo functions and hooks added by Jody Bruchon (jody@jodybruchon.com)
2073// Add to the undo stack
2074static void undo_push(char *src, unsigned length, uint8_t u_type)
2075{
2076 struct undo_object *undo_entry;
2077
2078 // "u_type" values
2079 // UNDO_INS: insertion, undo will remove from buffer
2080 // UNDO_DEL: deleted text, undo will restore to buffer
2081 // UNDO_{INS,DEL}_CHAIN: Same as above but also calls undo_pop() when complete
2082 // The CHAIN operations are for handling multiple operations that the user
2083 // performs with a single action, i.e. REPLACE mode or find-and-replace commands
2084 // UNDO_{INS,DEL}_QUEUED: If queuing feature is enabled, allow use of the queue
2085 // for the INS/DEL operation. The raw values should be equal to the values of
2086 // UNDO_{INS,DEL} ORed with UNDO_QUEUED_FLAG
2087
2088#if ENABLE_FEATURE_VI_UNDO_QUEUE
2089 // This undo queuing functionality groups multiple character typing or backspaces
2090 // into a single large undo object. This greatly reduces calls to malloc() for
2091 // single-character operations while typing and has the side benefit of letting
2092 // an undo operation remove chunks of text rather than a single character.
2093 switch (u_type) {
2094 case UNDO_EMPTY: // Just in case this ever happens...
2095 return;
2096 case UNDO_DEL_QUEUED:
2097 if (length != 1)
2098 return; // Only queue single characters
2099 switch (undo_queue_state) {
2100 case UNDO_EMPTY:
2101 undo_queue_state = UNDO_DEL;
2102 case UNDO_DEL:
2103 undo_queue_spos = src;
2104 undo_q++;
2105 undo_queue[CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q] = *src;
2106 // If queue is full, dump it into an object
2107 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2108 undo_queue_commit();
2109 return;
2110 case UNDO_INS:
2111 // Switch from storing inserted text to deleted text
2112 undo_queue_commit();
2113 undo_push(src, length, UNDO_DEL_QUEUED);
2114 return;
2115 }
2116 break;
2117 case UNDO_INS_QUEUED:
2118 if (length < 1)
2119 return;
2120 switch (undo_queue_state) {
2121 case UNDO_EMPTY:
2122 undo_queue_state = UNDO_INS;
2123 undo_queue_spos = src;
2124 case UNDO_INS:
2125 while (length--) {
2126 undo_q++; // Don't need to save any data for insertions
2127 if (undo_q == CONFIG_FEATURE_VI_UNDO_QUEUE_MAX)
2128 undo_queue_commit();
2129 }
2130 return;
2131 case UNDO_DEL:
2132 // Switch from storing deleted text to inserted text
2133 undo_queue_commit();
2134 undo_push(src, length, UNDO_INS_QUEUED);
2135 return;
2136 }
2137 break;
2138 }
2139#else
2140 // If undo queuing is disabled, ignore the queuing flag entirely
2141 u_type = u_type & ~UNDO_QUEUED_FLAG;
2142#endif
2143
2144 // Allocate a new undo object
2145 if (u_type == UNDO_DEL || u_type == UNDO_DEL_CHAIN) {
2146 // For UNDO_DEL objects, save deleted text
2147 if ((text + length) == end)
2148 length--;
2149 // If this deletion empties text[], strip the newline. When the buffer becomes
2150 // zero-length, a newline is added back, which requires this to compensate.
2151 undo_entry = xzalloc(offsetof(struct undo_object, undo_text) + length);
2152 memcpy(undo_entry->undo_text, src, length);
2153 } else {
2154 undo_entry = xzalloc(sizeof(*undo_entry));
2155 }
2156 undo_entry->length = length;
2157#if ENABLE_FEATURE_VI_UNDO_QUEUE
2158 if ((u_type & UNDO_USE_SPOS) != 0) {
2159 undo_entry->start = undo_queue_spos - text; // use start position from queue
2160 } else {
2161 undo_entry->start = src - text; // use offset from start of text buffer
2162 }
2163 u_type = (u_type & ~UNDO_USE_SPOS);
2164#else
2165 undo_entry->start = src - text;
2166#endif
2167 undo_entry->u_type = u_type;
2168
2169 // Push it on undo stack
2170 undo_entry->prev = undo_stack_tail;
2171 undo_stack_tail = undo_entry;
2172 modified_count++;
2173}
2174
2175static void undo_push_insert(char *p, int len, int undo)
2176{
2177 switch (undo) {
2178 case ALLOW_UNDO:
2179 undo_push(p, len, UNDO_INS);
2180 break;
2181 case ALLOW_UNDO_CHAIN:
2182 undo_push(p, len, UNDO_INS_CHAIN);
2183 break;
2184# if ENABLE_FEATURE_VI_UNDO_QUEUE
2185 case ALLOW_UNDO_QUEUED:
2186 undo_push(p, len, UNDO_INS_QUEUED);
2187 break;
2188# endif
2189 }
2190}
2191
2192// Undo the last operation
2193static void undo_pop(void)
2194{
2195 int repeat;
2196 char *u_start, *u_end;
2197 struct undo_object *undo_entry;
2198
2199 // Commit pending undo queue before popping (should be unnecessary)
2200 undo_queue_commit();
2201
2202 undo_entry = undo_stack_tail;
2203 // Check for an empty undo stack
2204 if (!undo_entry) {
2205 status_line("Already at oldest change");
2206 return;
2207 }
2208
2209 switch (undo_entry->u_type) {
2210 case UNDO_DEL:
2211 case UNDO_DEL_CHAIN:
2212 // make hole and put in text that was deleted; deallocate text
2213 u_start = text + undo_entry->start;
2214 text_hole_make(u_start, undo_entry->length);
2215 memcpy(u_start, undo_entry->undo_text, undo_entry->length);
2216 status_line("Undo [%d] %s %d chars at position %d",
2217 modified_count, "restored",
2218 undo_entry->length, undo_entry->start
2219 );
2220 break;
2221 case UNDO_INS:
2222 case UNDO_INS_CHAIN:
2223 // delete what was inserted
2224 u_start = undo_entry->start + text;
2225 u_end = u_start - 1 + undo_entry->length;
2226 text_hole_delete(u_start, u_end, NO_UNDO);
2227 status_line("Undo [%d] %s %d chars at position %d",
2228 modified_count, "deleted",
2229 undo_entry->length, undo_entry->start
2230 );
2231 break;
2232 }
2233 repeat = 0;
2234 switch (undo_entry->u_type) {
2235 // If this is the end of a chain, lower modification count and refresh display
2236 case UNDO_DEL:
2237 case UNDO_INS:
2238 dot = (text + undo_entry->start);
2239 refresh(FALSE);
2240 break;
2241 case UNDO_DEL_CHAIN:
2242 case UNDO_INS_CHAIN:
2243 repeat = 1;
2244 break;
2245 }
2246 // Deallocate the undo object we just processed
2247 undo_stack_tail = undo_entry->prev;
2248 free(undo_entry);
2249 modified_count--;
2250 // For chained operations, continue popping all the way down the chain.
2251 if (repeat) {
2252 undo_pop(); // Follow the undo chain if one exists
2253 }
2254}
2255
2256#if ENABLE_FEATURE_VI_UNDO_QUEUE
2257// Flush any queued objects to the undo stack
2258static void undo_queue_commit(void)
2259{
2260 // Pushes the queue object onto the undo stack
2261 if (undo_q > 0) {
2262 // Deleted character undo events grow from the end
2263 undo_push(undo_queue + CONFIG_FEATURE_VI_UNDO_QUEUE_MAX - undo_q,
2264 undo_q,
2265 (undo_queue_state | UNDO_USE_SPOS)
2266 );
2267 undo_queue_state = UNDO_EMPTY;
2268 undo_q = 0;
2269 }
2270}
2271#endif
2272
2273#endif /* ENABLE_FEATURE_VI_UNDO */
2274
2275// open a hole in text[]
2276// might reallocate text[]! use p += text_hole_make(p, ...),
2277// and be careful to not use pointers into potentially freed text[]!
2278static uintptr_t text_hole_make(char *p, int size) // at "p", make a 'size' byte hole
2279{
2280 uintptr_t bias = 0;
2281
2282 if (size <= 0)
2283 return bias;
2284 end += size; // adjust the new END
2285 if (end >= (text + text_size)) {
2286 char *new_text;
2287 text_size += end - (text + text_size) + 10240;
2288 new_text = xrealloc(text, text_size);
2289 bias = (new_text - text);
2290 screenbegin += bias;
2291 dot += bias;
2292 end += bias;
2293 p += bias;
2294#if ENABLE_FEATURE_VI_YANKMARK
2295 {
2296 int i;
2297 for (i = 0; i < ARRAY_SIZE(mark); i++)
2298 if (mark[i])
2299 mark[i] += bias;
2300 }
2301#endif
2302 text = new_text;
2303 }
2304 memmove(p + size, p, end - size - p);
2305 memset(p, ' ', size); // clear new hole
2306 return bias;
2307}
2308
2309// close a hole in text[]
2310// "undo" value indicates if this operation should be undo-able
2311static char *text_hole_delete(char *p, char *q, int undo) // delete "p" through "q", inclusive
2312{
2313 char *src, *dest;
2314 int cnt, hole_size;
2315
2316 // move forwards, from beginning
2317 // assume p <= q
2318 src = q + 1;
2319 dest = p;
2320 if (q < p) { // they are backward- swap them
2321 src = p + 1;
2322 dest = q;
2323 }
2324 hole_size = q - p + 1;
2325 cnt = end - src;
2326#if ENABLE_FEATURE_VI_UNDO
2327 switch (undo) {
2328 case NO_UNDO:
2329 break;
2330 case ALLOW_UNDO:
2331 undo_push(p, hole_size, UNDO_DEL);
2332 break;
2333 case ALLOW_UNDO_CHAIN:
2334 undo_push(p, hole_size, UNDO_DEL_CHAIN);
2335 break;
2336# if ENABLE_FEATURE_VI_UNDO_QUEUE
2337 case ALLOW_UNDO_QUEUED:
2338 undo_push(p, hole_size, UNDO_DEL_QUEUED);
2339 break;
2340# endif
2341 }
2342 modified_count--;
2343#endif
2344 if (src < text || src > end)
2345 goto thd0;
2346 if (dest < text || dest >= end)
2347 goto thd0;
2348 modified_count++;
2349 if (src >= end)
2350 goto thd_atend; // just delete the end of the buffer
2351 memmove(dest, src, cnt);
2352 thd_atend:
2353 end = end - hole_size; // adjust the new END
2354 if (dest >= end)
2355 dest = end - 1; // make sure dest in below end-1
2356 if (end <= text)
2357 dest = end = text; // keep pointers valid
2358 thd0:
2359 return dest;
2360}
2361
2362// copy text into register, then delete text.
2363// if dist <= 0, do not include, or go past, a NewLine
2364//
2365static char *yank_delete(char *start, char *stop, int dist, int yf, int undo)
2366{
2367 char *p;
2368
2369 // make sure start <= stop
2370 if (start > stop) {
2371 // they are backwards, reverse them
2372 p = start;
2373 start = stop;
2374 stop = p;
2375 }
2376 if (dist <= 0) {
2377 // we cannot cross NL boundaries
2378 p = start;
2379 if (*p == '\n')
2380 return p;
2381 // dont go past a NewLine
2382 for (; p + 1 <= stop; p++) {
2383 if (p[1] == '\n') {
2384 stop = p; // "stop" just before NewLine
2385 break;
2386 }
2387 }
2388 }
2389 p = start;
2390#if ENABLE_FEATURE_VI_YANKMARK
2391 text_yank(start, stop, YDreg);
2392#endif
2393 if (yf == YANKDEL) {
2394 p = text_hole_delete(start, stop, undo);
2395 } // delete lines
2396 return p;
2397}
2398
2399#if ENABLE_FEATURE_VI_DOT_CMD
2400static void start_new_cmd_q(char c)
2401{
2402 // get buffer for new cmd
2403 // if there is a current cmd count put it in the buffer first
2404 if (cmdcnt > 0) {
2405 lmc_len = sprintf(last_modifying_cmd, "%d%c", cmdcnt, c);
2406 } else { // just save char c onto queue
2407 last_modifying_cmd[0] = c;
2408 lmc_len = 1;
2409 }
2410 adding2q = 1;
2411}
2412
2413static void end_cmd_q(void)
2414{
2415#if ENABLE_FEATURE_VI_YANKMARK
2416 YDreg = 26; // go back to default Yank/Delete reg
2417#endif
2418 adding2q = 0;
2419}
2420#endif /* FEATURE_VI_DOT_CMD */
2421
2422#if ENABLE_FEATURE_VI_YANKMARK \
2423 || (ENABLE_FEATURE_VI_COLON && ENABLE_FEATURE_VI_SEARCH) \
2424 || ENABLE_FEATURE_VI_CRASHME
2425// might reallocate text[]! use p += string_insert(p, ...),
2426// and be careful to not use pointers into potentially freed text[]!
2427static uintptr_t string_insert(char *p, const char *s, int undo) // insert the string at 'p'
2428{
2429 uintptr_t bias;
2430 int i;
2431
2432 i = strlen(s);
2433#if ENABLE_FEATURE_VI_UNDO
2434 undo_push_insert(p, i, undo);
2435#endif
2436 bias = text_hole_make(p, i);
2437 p += bias;
2438 memcpy(p, s, i);
2439#if ENABLE_FEATURE_VI_YANKMARK
2440 {
2441 int cnt;
2442 for (cnt = 0; *s != '\0'; s++) {
2443 if (*s == '\n')
2444 cnt++;
2445 }
2446 status_line("Put %d lines (%d chars) from [%c]", cnt, i, what_reg());
2447 }
2448#endif
2449 return bias;
2450}
2451#endif
2452
2453#if ENABLE_FEATURE_VI_YANKMARK
2454static char *text_yank(char *p, char *q, int dest) // copy text into a register
2455{
2456 int cnt = q - p;
2457 if (cnt < 0) { // they are backwards- reverse them
2458 p = q;
2459 cnt = -cnt;
2460 }
2461 free(reg[dest]); // if already a yank register, free it
2462 reg[dest] = xstrndup(p, cnt + 1);
2463 return p;
2464}
2465
2466static char what_reg(void)
2467{
2468 char c;
2469
2470 c = 'D'; // default to D-reg
2471 if (0 <= YDreg && YDreg <= 25)
2472 c = 'a' + (char) YDreg;
2473 if (YDreg == 26)
2474 c = 'D';
2475 if (YDreg == 27)
2476 c = 'U';
2477 return c;
2478}
2479
2480static void check_context(char cmd)
2481{
2482 // A context is defined to be "modifying text"
2483 // Any modifying command establishes a new context.
2484
2485 if (dot < context_start || dot > context_end) {
2486 if (strchr(modifying_cmds, cmd) != NULL) {
2487 // we are trying to modify text[]- make this the current context
2488 mark[27] = mark[26]; // move cur to prev
2489 mark[26] = dot; // move local to cur
2490 context_start = prev_line(prev_line(dot));
2491 context_end = next_line(next_line(dot));
2492 //loiter= start_loiter= now;
2493 }
2494 }
2495}
2496
2497static char *swap_context(char *p) // goto new context for '' command make this the current context
2498{
2499 char *tmp;
2500
2501 // the current context is in mark[26]
2502 // the previous context is in mark[27]
2503 // only swap context if other context is valid
2504 if (text <= mark[27] && mark[27] <= end - 1) {
2505 tmp = mark[27];
2506 mark[27] = p;
2507 mark[26] = p = tmp;
2508 context_start = prev_line(prev_line(prev_line(p)));
2509 context_end = next_line(next_line(next_line(p)));
2510 }
2511 return p;
2512}
2513#endif /* FEATURE_VI_YANKMARK */
2514
2515//----- IO Routines --------------------------------------------
2516static int readit(void) // read (maybe cursor) key from stdin
2517{
2518 int c;
2519
2520 fflush_all();
2521
2522 // Wait for input. TIMEOUT = -1 makes read_key wait even
2523 // on nonblocking stdin.
2524 // Note: read_key sets errno to 0 on success.
2525 again:
2526 c = read_key(STDIN_FILENO, readbuffer, /*timeout:*/ -1);
2527 if (c == -1) { // EOF/error
2528 if (errno == EAGAIN) // paranoia
2529 goto again;
2530 go_bottom_and_clear_to_eol();
2531 cookmode(); // terminal to "cooked"
2532 bb_error_msg_and_die("can't read user input");
2533 }
2534 return c;
2535}
2536
2537//----- IO Routines --------------------------------------------
2538static int get_one_char(void)
2539{
2540 int c;
2541
2542#if ENABLE_FEATURE_VI_DOT_CMD
2543 if (!adding2q) {
2544 // we are not adding to the q.
2545 // but, we may be reading from a q
2546 if (ioq == 0) {
2547 // there is no current q, read from STDIN
2548 c = readit(); // get the users input
2549 } else {
2550 // there is a queue to get chars from first
2551 // careful with correct sign expansion!
2552 c = (unsigned char)*ioq++;
2553 if (c == '\0') {
2554 // the end of the q, read from STDIN
2555 free(ioq_start);
2556 ioq_start = ioq = 0;
2557 c = readit(); // get the users input
2558 }
2559 }
2560 } else {
2561 // adding STDIN chars to q
2562 c = readit(); // get the users input
2563 if (lmc_len >= MAX_INPUT_LEN - 1) {
2564 status_line_bold("last_modifying_cmd overrun");
2565 } else {
2566 // add new char to q
2567 last_modifying_cmd[lmc_len++] = c;
2568 }
2569 }
2570#else
2571 c = readit(); // get the users input
2572#endif /* FEATURE_VI_DOT_CMD */
2573 return c;
2574}
2575
2576// Get input line (uses "status line" area)
2577static char *get_input_line(const char *prompt)
2578{
2579 // char [MAX_INPUT_LEN]
2580#define buf get_input_line__buf
2581
2582 int c;
2583 int i;
2584
2585 strcpy(buf, prompt);
2586 last_status_cksum = 0; // force status update
2587 go_bottom_and_clear_to_eol();
2588 write1(prompt); // write out the :, /, or ? prompt
2589
2590 i = strlen(buf);
2591 while (i < MAX_INPUT_LEN) {
2592 c = get_one_char();
2593 if (c == '\n' || c == '\r' || c == 27)
2594 break; // this is end of input
2595 if (c == erase_char || c == 8 || c == 127) {
2596 // user wants to erase prev char
2597 buf[--i] = '\0';
2598 write1("\b \b"); // erase char on screen
2599 if (i <= 0) // user backs up before b-o-l, exit
2600 break;
2601 } else if (c > 0 && c < 256) { // exclude Unicode
2602 // (TODO: need to handle Unicode)
2603 buf[i] = c;
2604 buf[++i] = '\0';
2605 bb_putchar(c);
2606 }
2607 }
2608 refresh(FALSE);
2609 return buf;
2610#undef buf
2611}
2612
2613// might reallocate text[]!
2614static int file_insert(const char *fn, char *p, int initial)
2615{
2616 int cnt = -1;
2617 int fd, size;
2618 struct stat statbuf;
2619
2620 if (p < text)
2621 p = text;
2622 if (p > end)
2623 p = end;
2624
2625 fd = open(fn, O_RDONLY);
2626 if (fd < 0) {
2627 if (!initial)
2628 status_line_bold_errno(fn);
2629 return cnt;
2630 }
2631
2632 // Validate file
2633 if (fstat(fd, &statbuf) < 0) {
2634 status_line_bold_errno(fn);
2635 goto fi;
2636 }
2637 if (!S_ISREG(statbuf.st_mode)) {
2638 status_line_bold("'%s' is not a regular file", fn);
2639 goto fi;
2640 }
2641 size = (statbuf.st_size < INT_MAX ? (int)statbuf.st_size : INT_MAX);
2642 p += text_hole_make(p, size);
2643 cnt = full_read(fd, p, size);
2644 if (cnt < 0) {
2645 status_line_bold_errno(fn);
2646 p = text_hole_delete(p, p + size - 1, NO_UNDO); // un-do buffer insert
2647 } else if (cnt < size) {
2648 // There was a partial read, shrink unused space
2649 p = text_hole_delete(p + cnt, p + size - 1, NO_UNDO);
2650 status_line_bold("can't read '%s'", fn);
2651 }
2652 fi:
2653 close(fd);
2654
2655#if ENABLE_FEATURE_VI_READONLY
2656 if (initial
2657 && ((access(fn, W_OK) < 0) ||
2658 // root will always have access()
2659 // so we check fileperms too
2660 !(statbuf.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH))
2661 )
2662 ) {
2663 SET_READONLY_FILE(readonly_mode);
2664 }
2665#endif
2666 return cnt;
2667}
2668
2669static int file_write(char *fn, char *first, char *last)
2670{
2671 int fd, cnt, charcnt;
2672
2673 if (fn == 0) {
2674 status_line_bold("No current filename");
2675 return -2;
2676 }
2677 // By popular request we do not open file with O_TRUNC,
2678 // but instead ftruncate() it _after_ successful write.
2679 // Might reduce amount of data lost on power fail etc.
2680 fd = open(fn, (O_WRONLY | O_CREAT), 0666);
2681 if (fd < 0)
2682 return -1;
2683 cnt = last - first + 1;
2684 charcnt = full_write(fd, first, cnt);
2685 ftruncate(fd, charcnt);
2686 if (charcnt == cnt) {
2687 // good write
2688 //modified_count = FALSE;
2689 } else {
2690 charcnt = 0;
2691 }
2692 close(fd);
2693 return charcnt;
2694}
2695
2696//----- Flash the screen --------------------------------------
2697static void flash(int h)
2698{
2699 standout_start();
2700 redraw(TRUE);
2701 mysleep(h);
2702 standout_end();
2703 redraw(TRUE);
2704}
2705
2706static void indicate_error(void)
2707{
2708#if ENABLE_FEATURE_VI_CRASHME
2709 if (crashme > 0)
2710 return;
2711#endif
2712 if (!err_method) {
2713 write1(ESC_BELL);
2714 } else {
2715 flash(10);
2716 }
2717}
2718
2719static int bufsum(char *buf, int count)
2720{
2721 int sum = 0;
2722 char *e = buf + count;
2723
2724 while (buf < e)
2725 sum += (unsigned char) *buf++;
2726 return sum;
2727}
2728
2729//----- Draw the status line at bottom of the screen -------------
2730static void show_status_line(void)
2731{
2732 int cnt = 0, cksum = 0;
2733
2734 // either we already have an error or status message, or we
2735 // create one.
2736 if (!have_status_msg) {
2737 cnt = format_edit_status();
2738 cksum = bufsum(status_buffer, cnt);
2739 }
2740 if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) {
2741 last_status_cksum = cksum; // remember if we have seen this line
2742 go_bottom_and_clear_to_eol();
2743 write1(status_buffer);
2744 if (have_status_msg) {
2745 if (((int)strlen(status_buffer) - (have_status_msg - 1)) >
2746 (columns - 1) ) {
2747 have_status_msg = 0;
2748 Hit_Return();
2749 }
2750 have_status_msg = 0;
2751 }
2752 place_cursor(crow, ccol); // put cursor back in correct place
2753 }
2754 fflush_all();
2755}
2756
2757//----- format the status buffer, the bottom line of screen ------
2758// format status buffer, with STANDOUT mode
2759static void status_line_bold(const char *format, ...)
2760{
2761 va_list args;
2762
2763 va_start(args, format);
2764 strcpy(status_buffer, ESC_BOLD_TEXT);
2765 vsprintf(status_buffer + sizeof(ESC_BOLD_TEXT)-1, format, args);
2766 strcat(status_buffer, ESC_NORM_TEXT);
2767 va_end(args);
2768
2769 have_status_msg = 1 + sizeof(ESC_BOLD_TEXT) + sizeof(ESC_NORM_TEXT) - 2;
2770}
2771
2772static void status_line_bold_errno(const char *fn)
2773{
2774 status_line_bold("'%s' "STRERROR_FMT, fn STRERROR_ERRNO);
2775}
2776
2777// format status buffer
2778static void status_line(const char *format, ...)
2779{
2780 va_list args;
2781
2782 va_start(args, format);
2783 vsprintf(status_buffer, format, args);
2784 va_end(args);
2785
2786 have_status_msg = 1;
2787}
2788
2789// copy s to buf, convert unprintable
2790static void print_literal(char *buf, const char *s)
2791{
2792 char *d;
2793 unsigned char c;
2794
2795 buf[0] = '\0';
2796 if (!s[0])
2797 s = "(NULL)";
2798
2799 d = buf;
2800 for (; *s; s++) {
2801 int c_is_no_print;
2802
2803 c = *s;
2804 c_is_no_print = (c & 0x80) && !Isprint(c);
2805 if (c_is_no_print) {
2806 strcpy(d, ESC_NORM_TEXT);
2807 d += sizeof(ESC_NORM_TEXT)-1;
2808 c = '.';
2809 }
2810 if (c < ' ' || c == 0x7f) {
2811 *d++ = '^';
2812 c |= '@'; // 0x40
2813 if (c == 0x7f)
2814 c = '?';
2815 }
2816 *d++ = c;
2817 *d = '\0';
2818 if (c_is_no_print) {
2819 strcpy(d, ESC_BOLD_TEXT);
2820 d += sizeof(ESC_BOLD_TEXT)-1;
2821 }
2822 if (*s == '\n') {
2823 *d++ = '$';
2824 *d = '\0';
2825 }
2826 if (d - buf > MAX_INPUT_LEN - 10) // paranoia
2827 break;
2828 }
2829}
2830
2831static void not_implemented(const char *s)
2832{
2833 char buf[MAX_INPUT_LEN];
2834
2835 print_literal(buf, s);
2836 status_line_bold("\'%s\' is not implemented", buf);
2837}
2838
2839// show file status on status line
2840static int format_edit_status(void)
2841{
2842 static const char cmd_mode_indicator[] ALIGN1 = "-IR-";
2843
2844#define tot format_edit_status__tot
2845
2846 int cur, percent, ret, trunc_at;
2847
2848 // modified_count is now a counter rather than a flag. this
2849 // helps reduce the amount of line counting we need to do.
2850 // (this will cause a mis-reporting of modified status
2851 // once every MAXINT editing operations.)
2852
2853 // it would be nice to do a similar optimization here -- if
2854 // we haven't done a motion that could have changed which line
2855 // we're on, then we shouldn't have to do this count_lines()
2856 cur = count_lines(text, dot);
2857
2858 // count_lines() is expensive.
2859 // Call it only if something was changed since last time
2860 // we were here:
2861 if (modified_count != last_modified_count) {
2862 tot = cur + count_lines(dot, end - 1) - 1;
2863 last_modified_count = modified_count;
2864 }
2865
2866 // current line percent
2867 // ------------- ~~ ----------
2868 // total lines 100
2869 if (tot > 0) {
2870 percent = (100 * cur) / tot;
2871 } else {
2872 cur = tot = 0;
2873 percent = 100;
2874 }
2875
2876 trunc_at = columns < STATUS_BUFFER_LEN-1 ?
2877 columns : STATUS_BUFFER_LEN-1;
2878
2879 ret = snprintf(status_buffer, trunc_at+1,
2880#if ENABLE_FEATURE_VI_READONLY
2881 "%c %s%s%s %d/%d %d%%",
2882#else
2883 "%c %s%s %d/%d %d%%",
2884#endif
2885 cmd_mode_indicator[cmd_mode & 3],
2886 (current_filename != NULL ? current_filename : "No file"),
2887#if ENABLE_FEATURE_VI_READONLY
2888 (readonly_mode ? " [Readonly]" : ""),
2889#endif
2890 (modified_count ? " [Modified]" : ""),
2891 cur, tot, percent);
2892
2893 if (ret >= 0 && ret < trunc_at)
2894 return ret; // it all fit
2895
2896 return trunc_at; // had to truncate
2897#undef tot
2898}
2899
2900//----- Force refresh of all Lines -----------------------------
2901static void redraw(int full_screen)
2902{
2903 // cursor to top,left; clear to the end of screen
2904 write1(ESC_SET_CURSOR_TOPLEFT ESC_CLEAR2EOS);
2905 screen_erase(); // erase the internal screen buffer
2906 last_status_cksum = 0; // force status update
2907 refresh(full_screen); // this will redraw the entire display
2908 show_status_line();
2909}
2910
2911//----- Format a text[] line into a buffer ---------------------
2912static char* format_line(char *src /*, int li*/)
2913{
2914 unsigned char c;
2915 int co;
2916 int ofs = offset;
2917 char *dest = scr_out_buf; // [MAX_SCR_COLS + MAX_TABSTOP * 2]
2918
2919 c = '~'; // char in col 0 in non-existent lines is '~'
2920 co = 0;
2921 while (co < columns + tabstop) {
2922 // have we gone past the end?
2923 if (src < end) {
2924 c = *src++;
2925 if (c == '\n')
2926 break;
2927 if ((c & 0x80) && !Isprint(c)) {
2928 c = '.';
2929 }
2930 if (c < ' ' || c == 0x7f) {
2931 if (c == '\t') {
2932 c = ' ';
2933 // co % 8 != 7
2934 while ((co % tabstop) != (tabstop - 1)) {
2935 dest[co++] = c;
2936 }
2937 } else {
2938 dest[co++] = '^';
2939 if (c == 0x7f)
2940 c = '?';
2941 else
2942 c += '@'; // Ctrl-X -> 'X'
2943 }
2944 }
2945 }
2946 dest[co++] = c;
2947 // discard scrolled-off-to-the-left portion,
2948 // in tabstop-sized pieces
2949 if (ofs >= tabstop && co >= tabstop) {
2950 memmove(dest, dest + tabstop, co);
2951 co -= tabstop;
2952 ofs -= tabstop;
2953 }
2954 if (src >= end)
2955 break;
2956 }
2957 // check "short line, gigantic offset" case
2958 if (co < ofs)
2959 ofs = co;
2960 // discard last scrolled off part
2961 co -= ofs;
2962 dest += ofs;
2963 // fill the rest with spaces
2964 if (co < columns)
2965 memset(&dest[co], ' ', columns - co);
2966 return dest;
2967}
2968
2969//----- Refresh the changed screen lines -----------------------
2970// Copy the source line from text[] into the buffer and note
2971// if the current screenline is different from the new buffer.
2972// If they differ then that line needs redrawing on the terminal.
2973//
2974static void refresh(int full_screen)
2975{
2976#define old_offset refresh__old_offset
2977
2978 int li, changed;
2979 char *tp, *sp; // pointer into text[] and screen[]
2980
2981 if (ENABLE_FEATURE_VI_WIN_RESIZE IF_FEATURE_VI_ASK_TERMINAL(&& !G.get_rowcol_error) ) {
2982 unsigned c = columns, r = rows;
2983 query_screen_dimensions();
2984#if ENABLE_FEATURE_VI_USE_SIGNALS
2985 full_screen |= (c - columns) | (r - rows);
2986#else
2987 if (c != columns || r != rows) {
2988 full_screen = TRUE;
2989 // update screen memory since SIGWINCH won't have done it
2990 new_screen(rows, columns);
2991 }
2992#endif
2993 }
2994 sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot")
2995 tp = screenbegin; // index into text[] of top line
2996
2997 // compare text[] to screen[] and mark screen[] lines that need updating
2998 for (li = 0; li < rows - 1; li++) {
2999 int cs, ce; // column start & end
3000 char *out_buf;
3001 // format current text line
3002 out_buf = format_line(tp /*, li*/);
3003
3004 // skip to the end of the current text[] line
3005 if (tp < end) {
3006 char *t = memchr(tp, '\n', end - tp);
3007 if (!t) t = end - 1;
3008 tp = t + 1;
3009 }
3010
3011 // see if there are any changes between virtual screen and out_buf
3012 changed = FALSE; // assume no change
3013 cs = 0;
3014 ce = columns - 1;
3015 sp = &screen[li * columns]; // start of screen line
3016 if (full_screen) {
3017 // force re-draw of every single column from 0 - columns-1
3018 goto re0;
3019 }
3020 // compare newly formatted buffer with virtual screen
3021 // look forward for first difference between buf and screen
3022 for (; cs <= ce; cs++) {
3023 if (out_buf[cs] != sp[cs]) {
3024 changed = TRUE; // mark for redraw
3025 break;
3026 }
3027 }
3028
3029 // look backward for last difference between out_buf and screen
3030 for (; ce >= cs; ce--) {
3031 if (out_buf[ce] != sp[ce]) {
3032 changed = TRUE; // mark for redraw
3033 break;
3034 }
3035 }
3036 // now, cs is index of first diff, and ce is index of last diff
3037
3038 // if horz offset has changed, force a redraw
3039 if (offset != old_offset) {
3040 re0:
3041 changed = TRUE;
3042 }
3043
3044 // make a sanity check of columns indexes
3045 if (cs < 0) cs = 0;
3046 if (ce > columns - 1) ce = columns - 1;
3047 if (cs > ce) { cs = 0; ce = columns - 1; }
3048 // is there a change between virtual screen and out_buf
3049 if (changed) {
3050 // copy changed part of buffer to virtual screen
3051 memcpy(sp+cs, out_buf+cs, ce-cs+1);
3052 place_cursor(li, cs);
3053 // write line out to terminal
3054 fwrite(&sp[cs], ce - cs + 1, 1, stdout);
3055 }
3056 }
3057
3058 place_cursor(crow, ccol);
3059
3060 old_offset = offset;
3061#undef old_offset
3062}
3063
3064#if ENABLE_FEATURE_VI_USE_SIGNALS 2963#if ENABLE_FEATURE_VI_USE_SIGNALS
3065static void winch_handler(int sig UNUSED_PARAM) 2964static void winch_handler(int sig UNUSED_PARAM)
3066{ 2965{