1/*
2 * zle_move.c - editor movement
3 *
4 * This file is part of zsh, the Z shell.
5 *
6 * Copyright (c) 1992-1997 Paul Falstad
7 * All rights reserved.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and to distribute modified versions of this software for any
12 * purpose, provided that the above copyright notice and the following
13 * two paragraphs appear in all copies of this software.
14 *
15 * In no event shall Paul Falstad or the Zsh Development Group be liable
16 * to any party for direct, indirect, special, incidental, or consequential
17 * damages arising out of the use of this software and its documentation,
18 * even if Paul Falstad and the Zsh Development Group have been advised of
19 * the possibility of such damage.
20 *
21 * Paul Falstad and the Zsh Development Group specifically disclaim any
22 * warranties, including, but not limited to, the implied warranties of
23 * merchantability and fitness for a particular purpose.  The software
24 * provided hereunder is on an "as is" basis, and Paul Falstad and the
25 * Zsh Development Group have no obligation to provide maintenance,
26 * support, updates, enhancements, or modifications.
27 *
28 */
29
30#include "zle.mdh"
31#include "zle_move.pro"
32
33static int vimarkcs[27], vimarkline[27];
34
35#ifdef MULTIBYTE_SUPPORT
36/*
37 * Take account of combining characters when moving left.  If
38 * we are on a zero-width printable wide character and are
39 * treating these as part of the base character for display purposes,
40 * move left until we reach a non-zero-width printable character
41 * (the base character).  If we reach something else first, stay where we
42 * were.
43 *
44 * If setpos is non-zero, update zlecs on success.
45 * Return 1 if we were on a combining char and could move, else 0.
46 */
47/**/
48int
49alignmultiwordleft(int *pos, int setpos)
50{
51    int loccs = *pos;
52
53    /* generic nothing to do test */
54    if (!isset(COMBININGCHARS) || loccs == zlell || loccs == 0)
55	return 0;
56
57    /* need to be on combining character */
58    if (!IS_COMBINING(zleline[loccs]))
59	 return 0;
60
61    /* yes, go left */
62    loccs--;
63
64    for (;;) {
65	if (IS_BASECHAR(zleline[loccs])) {
66	    /* found start position */
67	    if (setpos)
68		*pos = loccs;
69	    return 1;
70	} else if (!IS_COMBINING(zleline[loccs])) {
71	    /* no go */
72	    return 0;
73	}
74	/* combining char, keep going */
75	if (loccs-- == 0)
76	    return 0;
77    }
78}
79
80
81/*
82 * Same principle when moving right.  We need to check if
83 * alignmultiwordleft() would be successful in order to decide
84 * if we're on a combining character, and if so we move right to
85 * anything that isn't one.
86 */
87/**/
88int
89alignmultiwordright(int *pos, int setpos)
90{
91    int loccs;
92
93    /*
94     * Are we on a suitable character?
95     */
96    if (!alignmultiwordleft(pos, 0))
97	return 0;
98
99    /* yes, go right */
100    loccs = *pos + 1;
101
102    while (loccs < zlell) {
103	/* Anything other than a combining char will do here */
104	if (!IS_COMBINING(zleline[loccs])) {
105	    if (setpos)
106		*pos = loccs;
107	    return 1;
108	}
109	loccs++;
110    }
111
112    if (setpos)
113	*pos = loccs;
114    return 1;
115}
116
117
118/* Move cursor right, checking for combining characters */
119
120/**/
121mod_export void
122inccs(void)
123{
124    zlecs++;
125    alignmultiwordright(&zlecs, 1);
126}
127
128
129/* Move cursor left, checking for combining characters */
130
131/**/
132mod_export void
133deccs(void)
134{
135    zlecs--;
136    alignmultiwordleft(&zlecs, 1);
137}
138
139/* Same utilities for general position */
140
141/**/
142mod_export void
143incpos(int *pos)
144{
145    (*pos)++;
146    alignmultiwordright(pos, 1);
147}
148
149
150/**/
151mod_export void
152decpos(int *pos)
153{
154    (*pos)--;
155    alignmultiwordleft(pos, 1);
156}
157#endif
158
159
160/* Size of buffer in the following function */
161#define BMC_BUFSIZE MB_CUR_MAX
162/*
163 * For a metafied string that starts at "start" and where the
164 * current position is "ptr", go back one full character,
165 * taking account of combining characters if necessary.
166 */
167
168/**/
169char *
170backwardmetafiedchar(char *start, char *endptr, convchar_t *retchr)
171{
172#ifdef MULTIBYTE_SUPPORT
173    int charlen = 0;
174    char *last = NULL, *bufptr, *ptr = endptr;
175    convchar_t lastc = (convchar_t)0; /* not used, silence compiler */
176    mbstate_t mbs;
177    size_t ret;
178    wchar_t wc;
179    VARARR(char, buf, BMC_BUFSIZE);
180
181    bufptr = buf + BMC_BUFSIZE;
182    while (ptr > start) {
183	ptr--;
184	/*
185	 * Scanning backwards we're not guaranteed ever to find a
186	 * valid character.  If we've looked as far as we should
187	 * need to, give up.
188	 */
189	if (bufptr-- == buf)
190	    break;
191	charlen++;
192	if (ptr > start && ptr[-1] == Meta)
193	    *bufptr = *ptr-- ^ 32;
194	else
195	    *bufptr = *ptr;
196
197	/* we always need to restart the character from scratch */
198	memset(&mbs, 0, sizeof(mbs));
199	ret = mbrtowc(&wc, bufptr, charlen, &mbs);
200	if (ret == 0) {
201	    /* NULL: unlikely, but handle anyway. */
202	    if (last) {
203		if (retchr)
204		    *retchr = lastc;
205		return last;
206	    } else {
207		if (retchr)
208		    *retchr = wc;
209		return ptr;
210	    }
211	}
212	if (ret != (size_t)-1) {
213	    if (ret < (size_t)charlen) {
214		/* The last character didn't convert, so use it raw. */
215		break;
216	    }
217	    if (!isset(COMBININGCHARS)) {
218		if (retchr)
219		    *retchr = wc;
220		return ptr;
221	    }
222	    if (!IS_COMBINING(wc)) {
223		/* not a combining character... */
224		if (last) {
225		    /*
226		     * ... but we were looking for a suitable base character,
227		     * test it.
228		     */
229		    if (IS_BASECHAR(wc)) {
230			/*
231			 * Yes, this will do.
232			 */
233			if (retchr)
234			    *retchr = wc;
235			return ptr;
236		    } else {
237			/* No, just return the first character we found */
238			if (retchr)
239			    *retchr = lastc;
240			return last;
241		    }
242		}
243		/* This is the first character, so just return it. */
244		if (retchr)
245		    *retchr = wc;
246		return ptr;
247	    }
248	    if (!last) {
249		/* still looking for the character immediately before ptr */
250		last = ptr;
251		lastc = wc;
252	    }
253	    /* searching for base character of combining character */
254	    charlen = 0;
255	    bufptr = buf + BMC_BUFSIZE;
256	}
257	/*
258	 * Else keep scanning this character even if MB_INVALID:  we can't
259	 * expect MB_INCOMPLETE to work when moving backwards.
260	 */
261    }
262    /*
263     * Found something we didn't like, was there a good character
264     * immediately before ptr?
265     */
266    if (last) {
267	if (retchr)
268	    *retchr = lastc;
269	return last;
270    }
271    /*
272     * No, we couldn't find any good character, so just treat
273     * the last unmetafied byte we found as a character.
274     */
275#endif
276    if (endptr > start) {
277	if (endptr > start - 1 && endptr[-2] == Meta)
278	{
279	    if (retchr)
280		*retchr = (convchar_t)(endptr[-1] ^ 32);
281	    return endptr - 2;
282	}
283	else
284	{
285	    if (retchr)
286		*retchr = (convchar_t)endptr[-1];
287	    return endptr - 1;
288	}
289    }
290    if (retchr)
291	*retchr = (convchar_t)0;
292    return endptr;
293}
294
295
296/**/
297int
298beginningofline(char **args)
299{
300    int n = zmult;
301
302    if (n < 0) {
303	int ret;
304	zmult = -n;
305	ret = endofline(args);
306	zmult = n;
307	return ret;
308    }
309    while (n--) {
310	int pos;
311
312	if (zlecs == 0)
313	    return 0;
314	pos = zlecs;
315	DECPOS(pos);
316	if (zleline[pos] == '\n') {
317	    zlecs = pos;
318	    if (!zlecs)
319		return 0;
320	}
321
322	/* works OK with combining chars since '\n' must be on its own */
323	while (zlecs && zleline[zlecs - 1] != '\n')
324	    zlecs--;
325    }
326    return 0;
327}
328
329/**/
330int
331endofline(char **args)
332{
333    int n = zmult;
334
335    if (n < 0) {
336	int ret;
337	zmult = -n;
338	ret = beginningofline(args);
339	zmult = n;
340	return ret;
341    }
342    while (n--) {
343	if (zlecs >= zlell) {
344	    zlecs = zlell;
345	    return 0;
346	}
347	if (zleline[zlecs] == '\n')
348	    if (++zlecs == zlell)
349		return 0;
350	while (zlecs != zlell && zleline[zlecs] != '\n')
351	    zlecs++;
352    }
353    return 0;
354}
355
356/**/
357int
358beginningoflinehist(char **args)
359{
360    int n = zmult;
361
362    if (n < 0) {
363	int ret;
364	zmult = -n;
365	ret = endoflinehist(args);
366	zmult = n;
367	return ret;
368    }
369    while (n) {
370	int pos;
371
372	if (zlecs == 0)
373	    break;
374	pos = zlecs;
375	DECPOS(pos);
376	if (zleline[pos] == '\n') {
377	    zlecs = pos;
378	    if (!pos)
379		break;
380	}
381
382	/* works OK with combining chars since '\n' must be on its own */
383	while (zlecs && zleline[zlecs - 1] != '\n')
384	    zlecs--;
385	n--;
386    }
387    if (n) {
388	int m = zmult, ret;
389
390	zmult = n;
391	ret = uphistory(args);
392	zmult = m;
393	zlecs = 0;
394	return ret;
395    }
396    return 0;
397}
398
399/**/
400int
401endoflinehist(char **args)
402{
403    int n = zmult;
404
405    if (n < 0) {
406	int ret;
407	zmult = -n;
408	ret = beginningoflinehist(args);
409	zmult = n;
410	return ret;
411    }
412    while (n) {
413	if (zlecs >= zlell) {
414	    zlecs = zlell;
415	    break;
416	}
417	if (zleline[zlecs] == '\n')
418	    if (++zlecs == zlell)
419		break;
420	while (zlecs != zlell && zleline[zlecs] != '\n')
421	    zlecs++;
422	n--;
423    }
424    if (n) {
425	int m = zmult, ret;
426
427	zmult = n;
428	ret = downhistory(args);
429	zmult = m;
430	return ret;
431    }
432    return 0;
433}
434
435/**/
436int
437forwardchar(char **args)
438{
439    int n = zmult;
440
441    if (n < 0) {
442	int ret;
443	zmult = -n;
444	ret = backwardchar(args);
445	zmult = n;
446	return ret;
447    }
448
449    /*
450     * If handling combining characters with the base character,
451     * we skip over the whole set in one go, so need to check.
452     */
453    while (zlecs < zlell && n--)
454	INCCS();
455    return 0;
456}
457
458/**/
459int
460backwardchar(char **args)
461{
462    int n = zmult;
463
464    if (n < 0) {
465	int ret;
466	zmult = -n;
467	ret = forwardchar(args);
468	zmult = n;
469	return ret;
470    }
471
472    while (zlecs > 0 && n--)
473	DECCS();
474    return 0;
475}
476
477/**/
478int
479setmarkcommand(UNUSED(char **args))
480{
481    if (zmult < 0) {
482	region_active = 0;
483	return 0;
484    }
485    mark = zlecs;
486    region_active = 1;
487    return 0;
488}
489
490/**/
491int
492exchangepointandmark(UNUSED(char **args))
493{
494    int x;
495
496    if (zmult == 0) {
497	region_active = 1;
498	return 0;
499    }
500    x = mark;
501    mark = zlecs;
502    zlecs = x;
503    if (zlecs > zlell)
504	zlecs = zlell;
505    if (zmult > 0)
506	region_active = 1;
507    return 0;
508}
509
510/**/
511int
512vigotocolumn(UNUSED(char **args))
513{
514    int x, y, n = zmult;
515
516    findline(&x, &y);
517    if (n >= 0) {
518	if (n)
519	    n--;
520	zlecs = x;
521	while (zlecs < y && n--)
522	    INCCS();
523    } else {
524	zlecs = y;
525	n = -n;
526	while (zlecs > x && n--)
527	    DECCS();
528    }
529    return 0;
530}
531
532/**/
533int
534vimatchbracket(UNUSED(char **args))
535{
536    int ocs = zlecs, dir, ct;
537    unsigned char oth, me;
538
539    if ((zlecs == zlell || zleline[zlecs] == '\n') && zlecs > 0)
540	DECCS();
541
542  otog:
543    if (zlecs == zlell || zleline[zlecs] == '\n') {
544	zlecs = ocs;
545	return 1;
546    }
547    switch (me = zleline[zlecs]) {
548    case '{':
549	dir = 1;
550	oth = '}';
551	break;
552    case /*{*/ '}':
553	virangeflag = -virangeflag;
554	dir = -1;
555	oth = '{'; /*}*/
556	break;
557    case '(':
558	dir = 1;
559	oth = ')';
560	break;
561    case ')':
562	virangeflag = -virangeflag;
563	dir = -1;
564	oth = '(';
565	break;
566    case '[':
567	dir = 1;
568	oth = ']';
569	break;
570    case ']':
571	virangeflag = -virangeflag;
572	dir = -1;
573	oth = '[';
574	break;
575    default:
576	INCCS();
577	goto otog;
578    }
579    ct = 1;
580    while (zlecs >= 0 && zlecs < zlell && ct) {
581	if (dir < 0)
582	    DECCS();
583	else
584	    INCCS();
585	if (zleline[zlecs] == oth)
586	    ct--;
587	else if (zleline[zlecs] == me)
588	    ct++;
589    }
590    if (zlecs < 0 || zlecs >= zlell) {
591	zlecs = ocs;
592	return 1;
593    } else if(dir > 0 && virangeflag)
594	INCCS();
595    return 0;
596}
597
598/**/
599int
600viforwardchar(char **args)
601{
602    int lim = findeol() - invicmdmode();
603    int n = zmult;
604
605    if (n < 0) {
606	int ret;
607	zmult = -n;
608	ret = vibackwardchar(args);
609	zmult = n;
610	return ret;
611    }
612    if (zlecs >= lim)
613	return 1;
614    while (n-- && zlecs < lim)
615	INCCS();
616    return 0;
617}
618
619/**/
620int
621vibackwardchar(char **args)
622{
623    int n = zmult;
624
625    if (n < 0) {
626	int ret;
627	zmult = -n;
628	ret = viforwardchar(args);
629	zmult = n;
630	return ret;
631    }
632    if (zlecs == findbol())
633	return 1;
634    while (n-- && zlecs > 0) {
635	DECCS();
636	if (zleline[zlecs] == '\n') {
637	    zlecs++;
638	    break;
639	}
640    }
641    return 0;
642}
643
644/**/
645int
646viendofline(UNUSED(char **args))
647{
648    int oldcs = zlecs, n = zmult;
649
650    if (n < 1)
651	return 1;
652    while(n--) {
653	if (zlecs > zlell) {
654	    zlecs = oldcs;
655	    return 1;
656	}
657	zlecs = findeol() + 1;
658    }
659    DECCS();
660    lastcol = 1<<30;
661    return 0;
662}
663
664/**/
665int
666vibeginningofline(UNUSED(char **args))
667{
668    zlecs = findbol();
669    return 0;
670}
671
672static ZLE_INT_T vfindchar;
673static int vfinddir, tailadd;
674
675/**/
676int
677vifindnextchar(char **args)
678{
679    if ((vfindchar = vigetkey()) != ZLEEOF) {
680	vfinddir = 1;
681	tailadd = 0;
682	return vifindchar(0, args);
683    }
684    return 1;
685}
686
687/**/
688int
689vifindprevchar(char **args)
690{
691    if ((vfindchar = vigetkey()) != ZLEEOF) {
692	vfinddir = -1;
693	tailadd = 0;
694	return vifindchar(0, args);
695    }
696    return 1;
697}
698
699/**/
700int
701vifindnextcharskip(char **args)
702{
703    if ((vfindchar = vigetkey()) != ZLEEOF) {
704	vfinddir = 1;
705	tailadd = -1;
706	return vifindchar(0, args);
707    }
708    return 1;
709}
710
711/**/
712int
713vifindprevcharskip(char **args)
714{
715    if ((vfindchar = vigetkey()) != ZLEEOF) {
716	vfinddir = -1;
717	tailadd = 1;
718	return vifindchar(0, args);
719    }
720    return 1;
721}
722
723/**/
724int
725vifindchar(int repeat, char **args)
726{
727    int ocs = zlecs, n = zmult;
728
729    if (!vfinddir)
730	return 1;
731    if (n < 0) {
732	int ret;
733	zmult = -n;
734	ret = virevrepeatfind(args);
735	zmult = n;
736	return ret;
737    }
738    if (repeat && tailadd != 0) {
739	if (vfinddir > 0) {
740	    if(zlecs < zlell && (ZLE_INT_T)zleline[zlecs+1] == vfindchar)
741		INCCS();
742	}
743	else {
744	    if(zlecs > 0 && (ZLE_INT_T)zleline[zlecs-1] == vfindchar)
745		DECCS();
746	}
747    }
748    while (n--) {
749	do {
750	    if (vfinddir > 0)
751		INCCS();
752	    else
753		DECCS();
754	} while (zlecs >= 0 && zlecs < zlell
755	    && (ZLE_INT_T)zleline[zlecs] != vfindchar
756	    && zleline[zlecs] != ZWC('\n'));
757	if (zlecs < 0 || zlecs >= zlell || zleline[zlecs] == ZWC('\n')) {
758	    zlecs = ocs;
759	    return 1;
760	}
761    }
762    if (tailadd > 0)
763	INCCS();
764    else if (tailadd < 0)
765	DECCS();
766    if (vfinddir == 1 && virangeflag)
767	INCCS();
768    return 0;
769}
770
771/**/
772int
773virepeatfind(char **args)
774{
775    return vifindchar(1, args);
776}
777
778/**/
779int
780virevrepeatfind(char **args)
781{
782    int ret;
783
784    if (zmult < 0) {
785	zmult = -zmult;
786	ret = vifindchar(1, args);
787	zmult = -zmult;
788	return ret;
789    }
790    tailadd = -tailadd;
791    vfinddir = -vfinddir;
792    ret = vifindchar(1, args);
793    vfinddir = -vfinddir;
794    tailadd = -tailadd;
795    return ret;
796}
797
798/**/
799int
800vifirstnonblank(UNUSED(char **args))
801{
802    zlecs = findbol();
803    while (zlecs != zlell && ZC_iblank(zleline[zlecs]))
804	INCCS();
805    return 0;
806}
807
808/**/
809int
810visetmark(UNUSED(char **args))
811{
812    ZLE_INT_T ch;
813
814    ch = getfullchar(0);
815    if (ch < ZWC('a') || ch > ZWC('z'))
816	return 1;
817    ch -= ZWC('a');
818    vimarkcs[ch] = zlecs;
819    vimarkline[ch] = histline;
820    return 0;
821}
822
823/**/
824int
825vigotomark(UNUSED(char **args))
826{
827    ZLE_INT_T ch;
828    int oldcs = zlecs;
829    int oldline = histline;
830
831    ch = getfullchar(0);
832    if (ch == ZWC('\'') || ch == ZWC('`'))
833	ch = 26;
834    else {
835	if (ch < ZWC('a') || ch > ZWC('z'))
836	    return 1;
837	ch -= ZWC('a');
838    }
839    if (!vimarkline[ch])
840	return 1;
841    if (curhist != vimarkline[ch] && !zle_goto_hist(vimarkline[ch], 0, 0)) {
842	vimarkline[ch] = 0;
843	return 1;
844    }
845    zlecs = vimarkcs[ch];
846    vimarkcs[26] = oldcs;
847    vimarkline[26] = oldline;
848    if (zlecs > zlell)
849	zlecs = zlell;
850    return 0;
851}
852
853/**/
854int
855vigotomarkline(char **args)
856{
857    vigotomark(args);
858    return vifirstnonblank(zlenoargs);
859}
860