1 /*-
2  * Copyright (c) 2002 Jordan DeLong
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the author nor the names of contributors may be
14  *    used to endorse or promote products derived from this software
15  *    without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 #include "vhighlight.h"
30 
31 /* color to use to highlight numbers */
32 int vhighlight_numberclr;
33 
34 /* keysym for setting vhighlight language */
35 static int vhighlight_setlang		= KEYSYM_NOP;
36 
37 /* set the line considered valid for highlighting state */
vhighlight_vstate(vhighlight_t * v,line_t * line,int linenum)38 static __inline void vhighlight_vstate(vhighlight_t *v, line_t *line,
39 		int linenum) {
40 	if (v->valid.linenum > linenum) {
41 		v->valid.line = line;
42 		v->valid.linenum = linenum;
43 	}
44 }
45 
vhighlight_drawpart(vhighlight_t * v,line_t ** line,int * linenum,int * col,int maxcols,int * y,int * viewcol)46 static void vhighlight_drawpart(vhighlight_t *v, line_t **line, int *linenum,
47 		int *col, int maxcols, int *y, int *viewcol) {
48 	anchor_t *anchor;
49 	synitem_t *state;
50 	int idx, start;
51 	int color, topcol;
52 	int low, len;
53 
54 	/* vhighlights without syntax information can't draw */
55 	if (!v->syntax) {
56 		VIEWCALL(v->hdr.under, drawpart, line, linenum, col,
57 			maxcols, y, viewcol);
58 		return;
59 	}
60 
61 	topcol = *col + maxcols;
62 	draw_setxy(v->vdef, *viewcol, *y);
63 
64 	/*
65 	 * find the last valid anchor, if the valid state line is in front
66 	 * of the current one, we have to scan up to this line in order
67 	 * to get the state to use.
68 	 */
69 	anchor = anchor_findlast(&v->anchors, min(v->valid.linenum, *linenum));
70 	state = anchor ? anchor->state : NULL;
71 	while (v->valid.linenum < *linenum) {
72 		for (idx = 0; idx < v->valid.line->length;)
73 			syntax_highlight(v->syntax, v->valid.line,
74 				&idx, &state);
75 
76 		/*
77 		 * now this line has a valid state, add an anchor if the
78 		 * state is different from the previous anchor.
79 		 */
80 		v->valid.line = TAILQ_NEXT(v->valid.line, l_list);
81 		v->valid.linenum++;
82 		if (!anchor || state != anchor->state)
83 			anchor = anchor_add(&v->anchors, v->valid.line,
84 				v->valid.linenum, state);
85 	}
86 
87 	/*
88 	 * draw each portions of the line as the highlighter returns
89 	 * colors for them.
90 	 */
91 	idx = 0;
92 	while (idx < (*line)->length) {
93 		start = idx;
94 		color = syntax_highlight(v->syntax, *line, &idx, &state);
95 		assert(idx <= (*line)->length + 1);
96 		if (idx > (*line)->length)
97 			idx = (*line)->length;
98 		screen_setcolor(color);
99 
100 		/* draw the section if it's in the part we're drawing */
101 		low = max(start, *col);
102 		if (low < idx) {
103 			len = idx - low;
104 			if (*col + len > topcol)
105 				len = topcol - *col;
106 			draw_string((*line)->text + low, v->hdr.geom.width,
107 				len, v->vdef->view.col, viewcol);
108 			*col += len;
109 			if (*col >= topcol)
110 				break;
111 		}
112 	}
113 
114 	/* set up new valid state information */
115 	if (*col >= (*line)->length) {
116 		if (TAILQ_NEXT(*line, l_list)) {
117 			v->valid.line = TAILQ_NEXT(*line, l_list);
118 			v->valid.linenum = *linenum + 1;
119 			if (!anchor || state != anchor->state)
120 				anchor_add(&v->anchors, v->valid.line,
121 					v->valid.linenum, state);
122 		}
123 	} else
124 		vhighlight_vstate(v, *line, *linenum);
125 }
126 
vhighlight_input(vhighlight_t * v,int keysym)127 static void vhighlight_input(vhighlight_t *v, int keysym) {
128 	u_char *lang;
129 
130 	/*
131 	 * XXX: this should be cleaned up a bit.
132 	 */
133 	if (keysym == vhighlight_setlang) {
134 		lang = minibuff_prompt("Language", NULL);
135 		if (!lang)
136 			goto callunder;
137 		anchor_emptylist(&v->anchors);
138 		v->valid.line = TAILQ_FIRST(&v->hdr.buffer->lines);
139 		v->valid.linenum = 0;
140 		v->syntax = syntax_findlang(lang);
141 		v->vdef->redraw_text = 1;
142 		free(lang);
143 		return;
144 	}
145 
146 callunder:
147 	VIEWCALL(v->hdr.under, input, keysym);
148 }
149 
150 /*
151  * detect what language should be used to highlight for;
152  * use a few simple rules to detect most cases:
153  *
154  * - check file extensions for matches with the languages
155  * - check the first line for a #! /interpreter syntax
156  * - check for a Emacs-style -*- language -*- on the first line
157  */
detectlang(buffer_t * buffer)158 static syntax_t *detectlang(buffer_t *buffer) {
159 	syntax_t *syntax;
160 	u_char *lang, *s;
161 	line_t *first;
162 	int len, i;
163 
164 	/* extensions */
165 	lang = strrchr(buffer->name, '.');
166 	if (lang && (syntax = syntax_findlang(lang + 1)) != NULL)
167 		return syntax;
168 
169 	/* #! interpreter */
170 	syntax = NULL;
171 	first = TAILQ_FIRST(&buffer->lines);
172 	if (first->text[0] == '#' && first->text[1] == '!') {
173 		lang = first->text;
174 		len = first->length;
175 		while ((s = memchr(lang, '/', len - 1)) != NULL) {
176 			s++;
177 			len -= s - lang;
178 			lang = s;
179 		}
180 		if (lang == first->text)
181 			goto out;
182 
183 		/* make a copy so we can play with it */
184 		s = ckmalloc(len + 1);
185 		memcpy(s, lang, len);
186 		lang = s;
187 		lang[len] = '\0';
188 
189 		/* special case for 'env', use what we're enving */
190 		if (!memcmp("env", lang, min(3, len))) {
191 			lang += 3;
192 			lang += strspn(lang, " \t");
193 			if (!*lang)
194 				goto free;
195 		}
196 
197 		/* put a null at the first whitespace */
198 		for (i = 0; lang[i]; i++)
199 			if (lang[i] == ' ' || lang[i] == '\t') {
200 				lang[i] = '\0';
201 				break;
202 			}
203 
204 		syntax = syntax_findlang(lang);
205 free:
206 		free(s);
207 		if (syntax)
208 			return syntax;
209 	}
210 out:
211 
212 	/* emacs-style -*- language -*- on the first line */
213 	syntax = NULL;
214 	if ((lang = strnstr(first->text, "-*-", first->length)) != NULL) {
215 		lang += 3;
216 		len = first->length - (lang - first->text);
217 		s = ckmalloc(len + 1);
218 		memcpy(s, lang, len);
219 		lang = strnstr(s, "-*-", len);
220 		if (!lang)
221 			goto free2;
222 		*lang = '\0';
223 		lang = s + strspn(s, " \t");
224 
225 		/* put a null at the first whitespace */
226 		for (i = 0; lang[i]; i++)
227 			if (lang[i] == ' ' || lang[i] == '\t') {
228 				lang[i] = '\0';
229 				break;
230 			}
231 
232 		syntax = syntax_findlang(lang);
233 free2:
234 		free(s);
235 		if (syntax)
236 			return syntax;
237 	}
238 
239 	return NULL;
240 }
241 
vhighlight_map(vhighlight_t * v)242 static void vhighlight_map(vhighlight_t *v) {
243 	v->syntax = detectlang(v->hdr.buffer);
244 	anchor_emptylist(&v->anchors);
245 	v->valid.line = TAILQ_FIRST(&v->hdr.buffer->lines);
246 	v->valid.linenum = 0;
247 
248 	v->vdef->redraw_header = 1;
249 	v->vdef->redraw_text = 1;
250 }
251 
vhighlight_rmline(vhighlight_t * v,line_t * line,int linenum)252 static void vhighlight_rmline(vhighlight_t *v, line_t *line, int linenum) {
253 	anchor_findlast(&v->anchors, linenum);
254 	if (TAILQ_PREV(line, linelist, l_list))
255 		vhighlight_vstate(v, TAILQ_PREV(line, linelist,
256 			l_list), linenum - 1);
257 	else {
258 		/*
259 		 * line being removed is the first line in the buffer,
260 		 * so set state to valid on the next line (which will
261 		 * become the first line) with number at 0.
262 		 */
263 		v->valid.line = TAILQ_NEXT(line, l_list);
264 		v->valid.linenum = 0;
265 	}
266 	VIEWCALL(v->hdr.under, rmline, line, linenum);
267 }
268 
vhighlight_rmstr(vhighlight_t * v,line_t * line,int linenum,int loc,int slen)269 static void vhighlight_rmstr(vhighlight_t *v, line_t *line, int linenum,
270 		int loc, int slen) {
271 	anchor_findlast(&v->anchors, linenum);
272 	vhighlight_vstate(v, line, linenum);
273 	VIEWCALL(v->hdr.under, rmstr, line, linenum, loc, slen);
274 }
275 
vhighlight_addline(vhighlight_t * v,line_t * line,int linenum)276 static void vhighlight_addline(vhighlight_t *v, line_t *line, int linenum) {
277 	anchor_findlast(&v->anchors, linenum);
278 	vhighlight_vstate(v, line, linenum);
279 	VIEWCALL(v->hdr.under, addline, line, linenum);
280 }
281 
vhighlight_stradd(vhighlight_t * v,line_t * line,int linenum,int loc,int slen)282 static void vhighlight_stradd(vhighlight_t *v, line_t *line, int linenum,
283 		int loc, int slen) {
284 	anchor_findlast(&v->anchors, linenum);
285 	vhighlight_vstate(v, line, linenum);
286 	VIEWCALL(v->hdr.under, stradd, line, linenum, loc, slen);
287 }
288 
vhighlight_destroy(vhighlight_t * v)289 static void vhighlight_destroy(vhighlight_t *v) {
290 	anchor_emptylist(&v->anchors);
291 	free(v);
292 }
293 
vhighlight_create(viewhdr_t * under)294 static viewhdr_t *vhighlight_create(viewhdr_t *under) {
295 	vhighlight_t *vhighlight;
296 
297 	/* create the vhighlight structure */
298 	vhighlight = VIEW_CREATE(vhighlight_t, "vhighlight", under);
299 	vhighlight->vdef = view_findvdef((viewhdr_t *) vhighlight);
300 	TAILQ_INIT(&vhighlight->anchors);
301 
302 	/* set view operations */
303 	vhighlight->hdr.op.draw		= NULL;
304 	vhighlight->hdr.op.drawpart	= VIEWOP(vhighlight_drawpart);
305 	vhighlight->hdr.op.viswid	= NULL;
306 	vhighlight->hdr.op.drawhdr	= NULL;
307 	vhighlight->hdr.op.input	= VIEWOP(vhighlight_input);
308 	vhighlight->hdr.op.map		= VIEWOP(vhighlight_map);
309 	vhighlight->hdr.op.geomchange	= NULL;
310 	vhighlight->hdr.op.activate	= NULL;
311 	vhighlight->hdr.op.rmline	= VIEWOP(vhighlight_rmline);
312 	vhighlight->hdr.op.rmstr	= VIEWOP(vhighlight_rmstr);
313 	vhighlight->hdr.op.addline	= VIEWOP(vhighlight_addline);
314 	vhighlight->hdr.op.stradd	= VIEWOP(vhighlight_stradd);
315 	vhighlight->hdr.op.destroy	= VIEWOP(vhighlight_destroy);
316 
317 	return (viewhdr_t *) vhighlight;
318 }
319 
320 /* type operations for the highlighter view */
321 static viewtypeops_t vhighlight_typeops = {
322 	vhighlight_create
323 };
324 
325 /* register the view type for the highlighter view */
vhighlight_init()326 int vhighlight_init() {
327 	vhighlight_setlang = input_allocsyms(1);
328 	options_add("vhighlight_numberclr", OPT_COLOR,
329 		&vhighlight_numberclr, 1);
330 	vhighlight_numberclr = color_lookup("number");
331 	if (vhighlight_numberclr == COLOR_NULL)
332 		vhighlight_numberclr = COLOR_DEFAULT;
333 	command_add("vhighlight_setlang", vhighlight_setlang);
334 	VIEW_ADDTYPE(vhighlight_typeops, "vhighlight");
335 	syntax_init();
336 
337 	return 0;
338 }
339 
340 /* free up memory */
vhighlight_shutdown()341 void vhighlight_shutdown() {
342 	syntax_shutdown();
343 	anchor_shutdown();
344 	command_remove("vhighlight_setlang");
345 	view_rmtype("vhighlight");
346 }
347