1 /*	SC	A Spreadsheet Calculator
2  *
3  *	One line vi emulation
4  *	$Revision: 1.1 $
5  */
6 
7 #include <config.h>
8 #include <curses.h>
9 #include <sys/types.h>
10 
11 #include <signal.h>
12 #ifdef HAVE_X11_X_H
13 #include <X11/Xlib.h>
14 #include <X11/Xutil.h>
15 #endif /* HAVE_X11_X_H */
16 
17 #include <ctype.h>
18 #include "sc.h"
19 
20 #ifdef HAVE_X11_X_H
21 #include "scXstuff.h"
22 #endif
23 
24 #define istext(a) (isalnum(a) || ((a) == '_'))
25 
26 static void	append_line PROTO((void));
27 static void	back_hist PROTO((void));
28 static int	back_line PROTO((void));
29 static int	back_word PROTO((void));
30 static void	back_space PROTO((void));
31 static void	change_cmd PROTO((void));
32 static void	col_0 PROTO((void));
33 static void	delete_cmd PROTO((void));
34 static void	del_chars PROTO((int, int));
35 static void	del_in_line PROTO((void));
36 static void	del_to_end PROTO((void));
37 static void	dotcmd PROTO((void));
38 static int	end_line PROTO((void));
39 static int	find_char PROTO((void));
40 static void	for_hist PROTO((void));
41 static int	for_line PROTO((int));
42 static int	for_word PROTO((int));
43 static int	get_motion PROTO((void));
44 static void	ins_in_line PROTO((int));
45 static void	last_col PROTO((void));
46 static void	rep_char PROTO((void));
47 static void	replace_in_line PROTO((int));
48 static void	replace_mode PROTO((void));
49 static void	restore_it PROTO((void));
50 static void	savedot PROTO((int));
51 static void	save_hist PROTO((void));
52 static void	search_again PROTO((void));
53 static void	search_hist PROTO((void));
54 static void	search_mode PROTO((void));
55 static void	stop_edit PROTO((void));
56 static int	to_char PROTO((void));
57 static void	u_save PROTO((int));
58 
59 extern int showrange;
60 extern char mode_ind;		/* Mode indicator */
61 
62 /* values for mode below */
63 
64 #define INSERT_MODE	0	/* Insert mode */
65 #define EDIT_MODE       1	/* Edit mode */
66 #define REP_MODE        2	/* Replace mode */
67 #define SEARCH_MODE	3	/* Get arguments for '/' command */
68 
69 #define	DOTLEN		200
70 
71 static int mode = INSERT_MODE;
72 struct	hist {
73 	unsigned	int	len;
74 	char	*histline;
75 } history[HISTLEN];
76 
77 static int histp = -1;
78 static int lasthist = -1;
79 static int endhist = -1;
80 static char *last_search = NULL;
81 static char *undo_line = NULL;
82 static int undo_lim;
83 static char dotb[DOTLEN];
84 static int doti = 0;
85 static int do_dot = 0;
86 
87 void
write_line(c)88 write_line(c)
89 int c;
90 {
91     if (mode == EDIT_MODE) {
92 	switch(c) {
93 	case (ctl('h')):	linelim = back_line();		break;
94 	case (ctl('m')):  cr_line();			break;
95 	case ESC:	stop_edit();			break;
96 	case '+':	for_hist();			break;
97 	case '-':	back_hist();			break;
98 	case '$':	last_col();			break;
99 	case '.':	dotcmd();			break;
100 	case '/':	search_mode();			break;
101 	case '0':	col_0();			break;
102 	case 'C':	u_save(c);del_to_end(); last_col();
103 			append_line();			break;
104 	case 'D':	u_save(c);del_to_end();		break;
105 	case 'I':	u_save(c);col_0();insert_mode();break;
106 	case 'R':	replace_mode();			break;
107 	case 'X':	u_save(c); back_space();	break;
108 	case 'a':	u_save(c); append_line();	break;
109 	case 'A':	u_save(c);last_col();append_line();	break;
110 	case 'b':	linelim = back_word();		break;
111 	case 'c':	u_save(c); change_cmd();	break;
112 	case 'd':	u_save(c); delete_cmd();	break;
113 	case 'f':	linelim = find_char();		break;
114 	case 'h':	linelim = back_line();		break;
115 	case 'i':	u_save(c); insert_mode();	break;
116 	case 'j':	for_hist();			break;
117 	case 'k':	back_hist();			break;
118 	case ' ':
119 	case 'l':	linelim = for_line(0);		break;
120 	case 'n':	search_again();			break;
121 	case 'q':	stop_edit();			break;
122 	case 'r':	u_save(c); rep_char();		break;
123 	case 't':	linelim = to_char();		break;
124 	case 'u':	restore_it();			break;
125 	case 'w':	linelim = for_word(0);		break;
126 	case 'x':	u_save(c); del_in_line();	break;
127 	default:	break;
128 	}
129     } else if (mode == INSERT_MODE) {
130 	savedot(c);
131 	switch(c) {
132 	case (ctl('h')):	back_space();			break;
133 	case (ctl('m')):  cr_line();			break;
134 	case ESC:	edit_mode();			break;
135 	default:	ins_in_line(c);			break;
136 	}
137     } else if (mode == SEARCH_MODE) {
138 	switch(c) {
139 	case (ctl('h')):	back_space();			break;
140 	case (ctl('m')):  search_hist();			break;
141 	case ESC:	edit_mode();			break;
142 	default:	ins_in_line(c);			break;
143 	}
144    } else if (mode == REP_MODE) {
145 	savedot(c);
146 	switch(c) {
147 	case (ctl('h')):	back_space();			break;
148 	case (ctl('m')):  cr_line();			break;
149 	case ESC:	edit_mode();			break;
150 	default:	replace_in_line(c);		break;
151 	}
152     }
153 }
154 
155 void
edit_mode()156 edit_mode()
157 {
158     mode = EDIT_MODE;
159     mode_ind = 'e';
160     histp = -1;
161     if (linelim < 0)	/* -1 says stop editing, ...so we still aren't */
162 	return;
163     if (line[linelim] == '\0')
164 	linelim = back_line();
165 }
166 
167 void
insert_mode()168 insert_mode()
169 {
170     mode_ind = 'i';
171     mode = INSERT_MODE;
172 }
173 
174 static	void
search_mode()175 search_mode()
176 {
177     line[0] = '/';
178     line[1] = '\0';
179     linelim = 1;
180     histp = -1;
181     mode_ind = '/';
182     mode = SEARCH_MODE;
183 }
184 
185 static	void
replace_mode()186 replace_mode()
187 {
188     mode_ind = 'R';
189     mode = REP_MODE;
190 }
191 
192 /* dot command functions.  Saves info so we can redo on a '.' command */
193 
194 static	void
savedot(c)195 savedot(c)
196 int c;
197 {
198     if (do_dot || (c == '\n'))
199 	return;
200 
201     if (doti < DOTLEN-1)
202     {
203 	dotb[doti++] = c;
204 	dotb[doti] = '\0';
205     }
206 }
207 
208 static int dotcalled = 0;
209 
210 static	void
dotcmd()211 dotcmd()
212 {
213     int c;
214 
215     if (dotcalled)	/* stop recursive calling of dotcmd() */
216 	return;
217     do_dot = 1;
218     doti = 0;
219     while(dotb[doti] != '\0') {
220 	c = dotb[doti++];
221 	dotcalled = 1;
222 	write_line(c);
223     }
224     do_dot = 0;
225     doti = 0;
226     dotcalled = 0;
227 }
228 
229 int
vigetch()230 vigetch()
231 {
232     int c;
233 
234     if(do_dot) {
235 	if (dotb[doti] != '\0') {
236 	    return(dotb[doti++]);
237 	} else {
238 	    do_dot = 0;
239 	    doti = 0;
240 	    return(nmgetch());
241 	}
242     }
243     c = nmgetch();
244     savedot(c);
245     return(c);
246 }
247 
248 /* saves the current line for possible use by an undo cmd */
249 static	void
u_save(c)250 u_save(c)
251 int c;
252 {   static	unsigned	undolen = 0;
253 
254     if (strlen(line)+1 > undolen)
255     {	undolen = strlen(line)+40;
256 
257 	undo_line = scxrealloc(undo_line, undolen);
258     }
259     (void) strcpy(undo_line, line);
260 
261     undo_lim = linelim;
262 
263     /* reset dot command if not processing it. */
264 
265     if (!do_dot) {
266         doti = 0;
267 	savedot(c);
268     }
269 }
270 
271 /* Restores the current line saved by u_save() */
272 static	void
restore_it()273 restore_it()
274 {
275     static	char *tempc = NULL;
276     static	unsigned templen = 0;
277     int		tempi;
278 
279     if ((undo_line == NULL) || (*undo_line == '\0'))
280 	return;
281 
282     if (strlen(line)+1 > templen)
283     {	templen = strlen(line)+40;
284 	tempc = scxrealloc(tempc, templen);
285     }
286 
287     strcpy(tempc, line);
288     tempi = linelim;
289     (void) strcpy(line, undo_line);
290     linelim = undo_lim;
291     strcpy(undo_line, tempc);
292     undo_lim = tempi;
293 }
294 
295 /* This command stops the editing process. */
296 static	void
stop_edit()297 stop_edit()
298 {
299     showrange = 0;
300     linelim = -1;
301     if (!using_X)
302     {	(void) move(1, 0);
303 	(void) clrtoeol();
304     }
305 }
306 
307 /*
308  * Motion commands.  Forward motion commands take an argument
309  * which, when set, cause the forward motion to continue onto
310  * the null at the end of the line instead of stopping at the
311  * the last character of the line.
312  */
313 static	int
for_line(stop_null)314 for_line(stop_null)
315 int stop_null;
316 {
317     if (linelim >= 0 && line[linelim] != '\0' &&
318     		        (line[linelim+1] != '\0' || stop_null))
319 	return(linelim+1);
320     else
321 	return(linelim);
322 }
323 
324 static int
end_line()325 end_line()
326 {	return (strlen(line));
327 }
328 
329 
330 static	int
for_word(stop_null)331 for_word(stop_null)
332 int stop_null;
333 {
334     register int c;
335     register int cpos;
336 
337     cpos = linelim;
338 
339     if (line[cpos] == ' ') {
340 	while (line[cpos] == ' ')
341 	    cpos++;
342 	if (cpos > 0 && line[cpos] == '\0')
343 	    --cpos;
344 	return(cpos);
345     }
346 
347     if (istext(line[cpos])) {
348     	while ((c = line[cpos]) && istext(c))
349 		cpos++;
350     } else {
351 	while ((c = line[cpos]) && !istext(c) && c != ' ')
352 		cpos++;
353     }
354 
355     while (line[cpos] == ' ')
356         cpos++;
357 
358     if (cpos > 0 && line[cpos] == '\0' && !stop_null)
359         --cpos;
360 
361     return(cpos);
362 }
363 
364 static	int
back_line()365 back_line()
366 {
367     if (linelim)
368         return(linelim-1);
369     else
370 	return(0);
371 }
372 
373 static	int
back_word()374 back_word()
375 {
376     register int c;
377     register int cpos;
378 
379     cpos = linelim;
380 
381     if (line[cpos] == ' ') {
382 	/* Skip white space */
383         while (cpos > 0 && line[cpos] == ' ')
384 	    --cpos;
385     } else if (cpos > 0 && (line[cpos-1] == ' '
386 		     || (istext(line[cpos]) && !istext(line[cpos-1]))
387 		     || (!istext(line[cpos]) &&  istext(line[cpos-1])))) {
388 	/* Started on the first char of a word - back up to prev. word */
389 	--cpos;
390         while (cpos > 0 && line[cpos] == ' ')
391 	    --cpos;
392     }
393 
394     /* Skip across the word - goes 1 too far */
395     if (istext(line[cpos])) {
396     	while (cpos > 0 && (c = line[cpos]) && istext(c))
397 		--cpos;
398     } else {
399 	while (cpos > 0 && (c = line[cpos]) && !istext(c) && c != ' ')
400 		--cpos;
401     }
402 
403     /* We are done - fix up the one too far */
404     if (cpos > 0 && line[cpos] && line[cpos+1])
405 	cpos++;
406 
407     return(cpos);
408 }
409 
410 /* Text manipulation commands */
411 
412 static	void
del_in_line()413 del_in_line()
414 {
415     register int len, i;
416 
417     if (linelim >= 0) {
418 	len = strlen(line);
419 	if (linelim == len && linelim > 0)
420 	    linelim--;
421 	for (i = linelim; i < len; i++)
422 	    line[i] = line[i+1];
423     }
424     if (linelim > 0 && line[linelim] == '\0')
425 	--linelim;
426 }
427 
428 static	void
ins_in_line(c)429 ins_in_line(c)
430 int c;
431 {
432     register int i, len;
433 
434     if (linelim < 0)
435     {	*line = '\0';
436 	linelim = 0;
437     }
438     len = strlen(line);
439     for (i = len; i >= linelim; --i)
440 	line[i+1] = line[i];
441     line[linelim++] = c;
442     line[len+1] = '\0';
443 }
444 
445 void
ins_string(s)446 ins_string(s)
447 char *s;
448 {
449     while (*s)
450 	ins_in_line(*s++);
451 }
452 
453 static	void
append_line()454 append_line()
455 {
456     register int i;
457 
458     i = linelim;
459     if (i >= 0 && line[i])
460 	linelim++;
461     insert_mode();
462 }
463 
464 static	void
rep_char()465 rep_char()
466 {
467     int c;
468 
469     if (linelim < 0)
470     {	linelim = 0;
471 	*line = '\0';
472     }
473     c = vigetch();
474     if (line[linelim] != '\0') {
475     	line[linelim] = c;
476     } else {
477 	line[linelim] = c;
478 	line[linelim+1] = '\0';
479     }
480 }
481 
482 static	void
replace_in_line(c)483 replace_in_line(c)
484 int	c;
485 {
486     register int len;
487 
488     if (linelim < 0)
489     {	linelim = 0;
490 	*line = '\0';
491     }
492     len = strlen(line);
493     line[linelim++] = c;
494     if (linelim > len)
495 	line[linelim] = '\0';
496 }
497 
498 static	void
back_space()499 back_space()
500 {
501     if (linelim == 0)
502 	return;
503 
504     if (line[linelim] == '\0') {
505 	linelim = back_line();
506 	del_in_line();
507 	linelim = strlen(line);
508     } else {
509 	linelim = back_line();
510 	del_in_line();
511     }
512 }
513 
514 static	int
get_motion()515 get_motion()
516 {
517     int c;
518 
519     c = vigetch();
520     switch (c) {
521     case 'b':	return(back_word());
522     case 'f':	return(find_char()+1);
523     case 'h':	return(back_line());
524     case 'l':	return(for_line(1));
525     case 't':	return(to_char()+1);
526     case 'w':	return(for_word(1));
527     case '0':	return (0);
528     case '$': 	return(end_line());
529     default:	return(linelim);
530     }
531 }
532 
533 static del_end;
534 
535 static	void
delete_cmd()536 delete_cmd()
537 {
538     int cpos;
539     int	ll = strlen(line);
540 
541     del_end = 0;
542     cpos = get_motion();
543     if (cpos == ll || linelim == ll)
544 	del_end = 1;
545     del_chars(cpos, linelim);
546 }
547 
548 static	void
change_cmd()549 change_cmd()
550 {
551     delete_cmd();
552     if (del_end)
553 	append_line();
554     else
555 	insert_mode();
556 }
557 
558 static	void
del_chars(first,last)559 del_chars(first, last)
560 register int first, last;
561 {
562     int temp;
563 
564     if (first == last)
565 	return;
566 
567     if (last < first) {
568 	temp = last; last = first; first = temp;
569     }
570 
571     linelim = first;
572     while(first < last) {
573 	del_in_line();
574 	--last;
575     }
576 }
577 
578 static	void
del_to_end()579 del_to_end()
580 {
581     if (linelim < 0)
582 	return;
583     line[linelim] = '\0';
584     linelim = back_line();
585 }
586 
587 void
cr_line()588 cr_line()
589 {
590     insert_mode();
591     if (linelim != -1) {
592 	showrange = 0;
593 	save_hist();
594 	linelim = 0;
595 	(void) yyparse ();
596 	linelim = -1;
597     }
598     else	/* '\n' alone will put you into insert mode */
599     {	*line = '\0';
600 	linelim = 0;
601     }
602 }
603 
604 /* History functions */
605 
606 static	void
save_hist()607 save_hist()
608 {
609     if (lasthist < 0)
610     {	lasthist = 0;
611     }
612     else
613 	lasthist = (lasthist + 1) % HISTLEN;
614 
615     if (lasthist > endhist)
616 	endhist = lasthist;
617 
618     if (history[lasthist].len < strlen(line)+1)
619     {	history[lasthist].len = strlen(line)+40;
620 	history[lasthist].histline = scxrealloc(history[lasthist].histline,
621 					      history[lasthist].len);
622     }
623     (void) strcpy(history[lasthist].histline, line);
624 }
625 
626 static	void
back_hist()627 back_hist()
628 {
629     if (histp == -1)
630 	histp = lasthist;
631     else
632     if (histp == 0)
633     {	if (endhist != lasthist)
634 		histp = endhist;
635     }
636     else
637     if (histp != ((lasthist + 1) % (endhist + 1)))
638 	histp--;
639 
640     if (lasthist < 0)
641 	line[linelim = 0] = '\0';
642     else {
643     	(void) strcpy(line, history[histp].histline);
644 	linelim = 0;
645     }
646 }
647 
648 static	void
search_hist()649 search_hist()
650 {
651     static	unsigned lastsrchlen = 0;
652 
653     if(linelim < 1) {
654 	linelim = 0;
655 	edit_mode();
656 	return;
657     }
658 
659     if (strlen(line)+1 > lastsrchlen)
660     {	lastsrchlen = strlen(line)+40;
661 	last_search = scxrealloc(last_search, lastsrchlen);
662     }
663     (void)strcpy(last_search, line+1);
664     search_again();
665     mode = EDIT_MODE;
666 }
667 
668 static	void
search_again()669 search_again()
670 {
671     int found_it;
672     int do_next;
673     int prev_histp;
674     char *look_here;
675 
676     prev_histp = histp;
677     if ((last_search == NULL) || (*last_search == '\0'))
678 	return;
679 
680     do {
681 	back_hist();
682 	if (prev_histp == histp)
683 	    break;
684 	prev_histp = histp;
685 	look_here = line;
686 	found_it = do_next = 0;
687 	for ( look_here = strchr(look_here, last_search[0]);
688 	      look_here != NULL && !found_it && !do_next;
689 	      look_here = strchr(look_here, last_search[0]) )
690 	{
691 	    if (strncmp(look_here, last_search, strlen(last_search)) == 0)
692 		found_it++;
693 	    else if (look_here < line + strlen(line) - 1)
694 	        look_here++;
695 	    else
696 		do_next++;
697 	}
698     } while (!found_it);
699 }
700 
701 static	void
for_hist()702 for_hist()
703 {
704     if (histp == -1)
705 	histp = lasthist;
706     else
707     if (histp != lasthist)
708 	histp = (histp + 1) % (endhist + 1);
709 
710     if (lasthist < 0)
711 	line[linelim = 0] = '\0';
712     else {
713 	(void) strcpy(line, history[histp].histline);
714 	linelim = 0;
715     }
716 }
717 
718 static	void
col_0()719 col_0()
720 {
721     linelim = 0;
722 }
723 
724 static	void
last_col()725 last_col()
726 {
727     linelim = strlen(line);
728     if (linelim > 0)
729 	--linelim;
730 }
731 
732 static	int
find_char()733 find_char()
734 {
735     register int c;
736     register int i;
737 
738 
739     c = vigetch();
740     i = linelim;
741     while(line[i] && line[i] != c)
742 	i++;
743     if (!line[i])
744 	i = linelim;
745     return(i);
746 }
747 
748 static	int
to_char()749 to_char()
750 {
751     register int i;
752 
753     i = find_char();
754     if (i > 0 && i != linelim)
755 	--i;
756 
757     return(i);
758 }
759 
760 
761 char *
get_str(s,max_str_len)762 get_str(s, max_str_len)
763    char *s;               /* prompt and returned string */
764    int max_str_len;
765 {
766   static char buf[1024]; /* hold the characters as they are typed */
767 #ifdef HAVE_X11_X_H
768   int count=0;           /* how many characters have been entered */
769   int maxcount;          /* the max number of chars to be entered */
770   int done=0;            /* true when input is finished */
771   int slen;              /* length of prompt string */
772   XEvent event;          /* input event structure */
773   char keystr[3];        /* ASCII version of keypress */
774 
775  if (using_X)
776  {
777   clearlines(0,0);
778   slen = strlen(s);
779   max_str_len--;      /* decrease this to save room for null byte */
780   maxcount = maintextcols - slen;
781   maxcount = ((maxcount < max_str_len) ? maxcount : max_str_len);
782   buf[0]='_'; /* the "cursor" */
783 
784   if (slen)
785      XDrawImageString(dpy,mainwin,maingc, textcol(0), textrow(0), s, slen);
786 
787   XDrawImageString(dpy,mainwin,maingc, textcol(slen+1), textrow(0), "_", 1 );
788   while (!done){
789     XNextEvent(dpy, &event);
790     switch(event.type){
791 
792       case Expose:
793         update(FALSE);
794         if (slen)
795             XDrawImageString(dpy, mainwin, maingc,
796                            textcol(0), textrow(0), s, slen);
797         if (count)
798             XDrawImageString(dpy, mainwin, maingc,
799                            textcol(slen), textrow(0), buf, count);
800 
801         XDrawImageString(dpy, mainwin, maingc,
802                          textcol(slen+count), textrow(0), "_", 1 );
803         break;
804 
805       case MappingNotify:
806         XRefreshKeyboardMapping(&(event.xmapping));
807         break;
808 
809       case ConfigureNotify:
810         sc_handleresize(&event);
811         maxcount = maintextcols - slen;
812         maxcount = ((maxcount < max_str_len) ? maxcount : max_str_len);
813         break;
814 
815       case KeyPress:
816         if (XLookupString(&(event.xkey), keystr, 3, 0, 0)){
817           switch( keystr[0]){
818             case 10: /* linefeed */
819             case 13: /* carriage return */
820               done = 1;
821               break;
822             case ctl('h'):  /* backspace */
823             case ctl('?'):  /* delete */
824 	    case DEL:
825               if (count){
826                 buf[--count]='_';
827                 XDrawImageString(dpy,mainwin,maingc,
828                                  textcol(slen+count+1),textrow(0), "_ ", 2);
829               } else {
830                 XBell(dpy,50);
831                 /*fprintf(stderr,"\007");*/ /* bell */
832               }
833               break;
834 
835             default:
836                 if ((keystr[0]>=32) && (keystr[0]<127)){
837                     if (count<maxcount){
838                       buf[count++]=keystr[0];
839                       buf[count]='_';
840                       XDrawImageString(dpy,mainwin,maingc,
841                                    textcol(slen+count),textrow(0),
842 				   buf+count-1, 2);
843                     } else
844                        XBell(dpy,50);
845                        /*fprintf(stderr,"\007");*/
846                 } else
847                     XBell(dpy,50);
848                     /*fprintf(stderr,"\007");*/
849               break;
850 
851           } /* switch keystr[0] */
852         } /* if XLookupString */
853       } /* switch event.type */
854     } /* while !done */
855     buf[count] = 0;
856     strcpy(s, buf);
857     clearlines(0,0);
858     show_top_line();
859     return s;
860  } else
861 #endif /* HAVE_X11_X_H */
862  {
863 	move(0,0);
864 	clrtoeol();
865 	printw("%s", s);
866 	refresh();
867 
868 	echo();
869 	getstr(buf);
870 	noecho();
871 	strncpy(s, buf, max_str_len);
872 	s[max_str_len] = '\0';
873 	move(0,0);
874 	clrtoeol();
875 	show_top_line();
876 	return(s);
877  } /* HAVE_X11_X_H, end curses */
878 }
879 
880