1/*-
2 * Copyright (c) 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 * Copyright (c) 1993, 1994, 1995, 1996
5 *	Keith Bostic.  All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10#include "config.h"
11
12#ifndef lint
13static const char sccsid[] = "$Id: vs_relative.c,v 10.19 2011/12/01 15:22:59 zy Exp $";
14#endif /* not lint */
15
16#include <sys/types.h>
17#include <sys/queue.h>
18#include <sys/time.h>
19
20#include <bitstring.h>
21#include <limits.h>
22#include <stdio.h>
23#include <string.h>
24
25#include "../common/common.h"
26#include "vi.h"
27
28/*
29 * vs_column --
30 *	Return the logical column of the cursor in the line.
31 *
32 * PUBLIC: int vs_column __P((SCR *, size_t *));
33 */
34int
35vs_column(SCR *sp, size_t *colp)
36{
37	VI_PRIVATE *vip;
38
39	vip = VIP(sp);
40
41	*colp = (O_ISSET(sp, O_LEFTRIGHT) ?
42	    vip->sc_smap->coff : (vip->sc_smap->soff - 1) * sp->cols) +
43	    vip->sc_col - (O_ISSET(sp, O_NUMBER) ? O_NUMBER_LENGTH : 0);
44	return (0);
45}
46
47/*
48 * vs_screens --
49 *	Return the screens necessary to display the line, or if specified,
50 *	the physical character column within the line, including space
51 *	required for the O_NUMBER and O_LIST options.
52 *
53 * PUBLIC: size_t vs_screens __P((SCR *, recno_t, size_t *));
54 */
55size_t
56vs_screens(SCR *sp, recno_t lno, size_t *cnop)
57{
58	size_t cols, screens;
59
60	/* Left-right screens are simple, it's always 1. */
61	if (O_ISSET(sp, O_LEFTRIGHT))
62		return (1);
63
64	/*
65	 * Check for a cached value.  We maintain a cache because, if the
66	 * line is large, this routine gets called repeatedly.  One other
67	 * hack, lots of time the cursor is on column one, which is an easy
68	 * one.
69	 */
70	if (cnop == NULL) {
71		if (VIP(sp)->ss_lno == lno)
72			return (VIP(sp)->ss_screens);
73	} else if (*cnop == 0)
74		return (1);
75
76	/* Figure out how many columns the line/column needs. */
77	cols = vs_columns(sp, NULL, lno, cnop, NULL);
78
79	screens = (cols / sp->cols + (cols % sp->cols ? 1 : 0));
80	if (screens == 0)
81		screens = 1;
82
83	/* Cache the value. */
84	if (cnop == NULL) {
85		VIP(sp)->ss_lno = lno;
86		VIP(sp)->ss_screens = screens;
87	}
88	return (screens);
89}
90
91/*
92 * vs_columns --
93 *	Return the screen columns necessary to display the line, or,
94 *	if specified, the physical character column within the line.
95 *
96 * PUBLIC: size_t vs_columns __P((SCR *, CHAR_T *, recno_t, size_t *, size_t *));
97 */
98size_t
99vs_columns(SCR *sp, CHAR_T *lp, recno_t lno, size_t *cnop, size_t *diffp)
100{
101	size_t chlen, cno, curoff, last = 0, len, scno;
102	int ch, leftright, listset;
103	CHAR_T *p;
104
105	/*
106	 * Initialize the screen offset.
107	 */
108	scno = 0;
109
110	/* Leading number if O_NUMBER option set. */
111	if (O_ISSET(sp, O_NUMBER))
112		scno += O_NUMBER_LENGTH;
113
114	/* Need the line to go any further. */
115	if (lp == NULL) {
116		(void)db_get(sp, lno, 0, &lp, &len);
117		if (len == 0)
118			goto done;
119	}
120
121	/* Missing or empty lines are easy. */
122	if (lp == NULL) {
123done:		if (diffp != NULL)		/* XXX */
124			*diffp = 0;
125		return scno;
126	}
127
128	/* Store away the values of the list and leftright edit options. */
129	listset = O_ISSET(sp, O_LIST);
130	leftright = O_ISSET(sp, O_LEFTRIGHT);
131
132	/*
133	 * Initialize the pointer into the buffer and current offset.
134	 */
135	p = lp;
136	curoff = scno;
137
138	/* Macro to return the display length of any signal character. */
139#define	CHLEN(val) (ch = *(UCHAR_T *)p++) == '\t' &&			\
140	    !listset ? TAB_OFF(val) : KEY_COL(sp, ch);
141
142	/*
143	 * If folding screens (the historic vi screen format), past the end
144	 * of the current screen, and the character was a tab, reset the
145	 * current screen column to 0, and the total screen columns to the
146	 * last column of the screen.  Otherwise, display the rest of the
147	 * character in the next screen.
148	 */
149#define	TAB_RESET {							\
150	curoff += chlen;						\
151	if (!leftright && curoff >= sp->cols)				\
152		if (ch == '\t') {					\
153			curoff = 0;					\
154			scno -= scno % sp->cols;			\
155		} else							\
156			curoff -= sp->cols;				\
157}
158	if (cnop == NULL)
159		while (len--) {
160			chlen = CHLEN(curoff);
161			last = scno;
162			scno += chlen;
163			TAB_RESET;
164		}
165	else
166		for (cno = *cnop;; --cno) {
167			chlen = CHLEN(curoff);
168			last = scno;
169			scno += chlen;
170			TAB_RESET;
171			if (cno == 0)
172				break;
173		}
174
175	/* Add the trailing '$' if the O_LIST option set. */
176	if (listset && cnop == NULL)
177		scno += KEY_LEN(sp, '$');
178
179	/*
180	 * The text input screen code needs to know how much additional
181	 * room the last two characters required, so that it can handle
182	 * tab character displays correctly.
183	 */
184	if (diffp != NULL)
185		*diffp = scno - last;
186	return (scno);
187}
188
189/*
190 * vs_rcm --
191 *	Return the physical column from the line that will display a
192 *	character closest to the currently most attractive character
193 *	position (which is stored as a screen column).
194 *
195 * PUBLIC: size_t vs_rcm __P((SCR *, recno_t, int));
196 */
197size_t
198vs_rcm(SCR *sp, recno_t lno, int islast)
199{
200	size_t len;
201
202	/* Last character is easy, and common. */
203	if (islast) {
204		if (db_get(sp, lno, 0, NULL, &len) || len == 0)
205			return (0);
206		return (len - 1);
207	}
208
209	/* First character is easy, and common. */
210	if (sp->rcm == 0)
211		return (0);
212
213	return (vs_colpos(sp, lno, sp->rcm));
214}
215
216/*
217 * vs_colpos --
218 *	Return the physical column from the line that will display a
219 *	character closest to the specified screen column.
220 *
221 * PUBLIC: size_t vs_colpos __P((SCR *, recno_t, size_t));
222 */
223size_t
224vs_colpos(SCR *sp, recno_t lno, size_t cno)
225{
226	size_t chlen, curoff, len, llen, off, scno;
227	int ch = 0, leftright, listset;
228	CHAR_T *lp, *p;
229
230	/* Need the line to go any further. */
231	(void)db_get(sp, lno, 0, &lp, &llen);
232
233	/* Missing or empty lines are easy. */
234	if (lp == NULL || llen == 0)
235		return (0);
236
237	/* Store away the values of the list and leftright edit options. */
238	listset = O_ISSET(sp, O_LIST);
239	leftright = O_ISSET(sp, O_LEFTRIGHT);
240
241	/* Discard screen (logical) lines. */
242	off = cno / sp->cols;
243	cno %= sp->cols;
244	for (scno = 0, p = lp, len = llen; off--;) {
245		for (; len && scno < sp->cols; --len)
246			scno += CHLEN(scno);
247
248		/*
249		 * If reached the end of the physical line, return the last
250		 * physical character in the line.
251		 */
252		if (len == 0)
253			return (llen - 1);
254
255		/*
256		 * If folding screens (the historic vi screen format), past
257		 * the end of the current screen, and the character was a tab,
258		 * reset the current screen column to 0.  Otherwise, the rest
259		 * of the character is displayed in the next screen.
260		 */
261		if (leftright && ch == '\t')
262			scno = 0;
263		else
264			scno -= sp->cols;
265	}
266
267	/* Step through the line until reach the right character or EOL. */
268	for (curoff = scno; len--;) {
269		chlen = CHLEN(curoff);
270
271		/*
272		 * If we've reached the specific character, there are three
273		 * cases.
274		 *
275		 * 1: scno == cno, i.e. the current character ends at the
276		 *    screen character we care about.
277		 *	a: off < llen - 1, i.e. not the last character in
278		 *	   the line, return the offset of the next character.
279		 *	b: else return the offset of the last character.
280		 * 2: scno != cno, i.e. this character overruns the character
281		 *    we care about, return the offset of this character.
282		 */
283		if ((scno += chlen) >= cno) {
284			off = p - lp;
285			return (scno == cno ?
286			    (off < llen - 1 ? off : llen - 1) : off - 1);
287		}
288
289		TAB_RESET;
290	}
291
292	/* No such character; return the start of the last character. */
293	return (llen - 1);
294}
295