1 /*
2  * Copyright (c) 2011 Tim van der Molen <tim@kariliq.nl>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <ctype.h>
18 #include <limits.h>
19 #include <stdarg.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "siren.h"
24 
25 #define PROMPT_LINESIZE 1024
26 
27 enum prompt_mode {
28 	PROMPT_MODE_CHAR,
29 	PROMPT_MODE_LINE
30 };
31 
32 static void		 prompt_line_handle_key(int);
33 static void		 prompt_mode_begin(enum prompt_mode, const char *,
34 			    struct history *, void (*)(char *, void *), void *)
35 			    NONNULL(2, 4);
36 static void		 prompt_mode_end(void);
37 
38 enum prompt_mode	 prompt_mode;
39 
40 static struct history	*prompt_command_history;
41 static struct history	*prompt_history;
42 static struct history	*prompt_search_history;
43 
44 static size_t		 prompt_linelen;
45 static size_t		 prompt_linepos;
46 static size_t		 prompt_linesize;
47 static size_t		 prompt_scroll_offset;
48 static char		*prompt_line;
49 
50 static char		*prompt_prompt;
51 static size_t		 prompt_promptlen;
52 
53 void			 (*prompt_callback)(char *, void *);
54 void			*prompt_callback_data;
55 
56 static void
prompt_adjust_scroll_offset(void)57 prompt_adjust_scroll_offset(void)
58 {
59 	size_t line_ncols, screen_ncols;
60 
61 	screen_ncols = screen_get_ncols();
62 	if (prompt_promptlen < screen_ncols)
63 		line_ncols = screen_ncols - prompt_promptlen;
64 	else
65 		line_ncols = 0;
66 
67 	/* Scroll left if necessary. */
68 	if (prompt_linepos < prompt_scroll_offset || line_ncols == 0)
69 		prompt_scroll_offset = prompt_linepos;
70 
71 	/* Scroll right if necessary. */
72 	else if (prompt_linepos >= prompt_scroll_offset + line_ncols)
73 		prompt_scroll_offset = prompt_linepos - line_ncols + 1;
74 
75 	/*
76 	 * If there is unused space at the end of the line, then use it if
77 	 * possible.
78 	 */
79 	else if (prompt_scroll_offset &&
80 	    prompt_scroll_offset + line_ncols > prompt_linelen) {
81 		if (prompt_linelen <= line_ncols)
82 			prompt_scroll_offset = 0;
83 		else
84 			prompt_scroll_offset = prompt_linelen - line_ncols + 1;
85 	}
86 }
87 
88 void
prompt_end(void)89 prompt_end(void)
90 {
91 	history_free(prompt_command_history);
92 	history_free(prompt_search_history);
93 
94 	/*
95 	 * It is possible that we are quitting while still in prompt mode (for
96 	 * example, because we received SIGTERM in prompt mode). In that case,
97 	 * we have to free the prompt input buffer.
98 	 */
99 	if (input_get_mode() == INPUT_MODE_PROMPT)
100 		free(prompt_line);
101 }
102 
103 void
prompt_get_answer(const char * prompt,void (* callback)(char *,void *),void * callback_data)104 prompt_get_answer(const char *prompt, void (*callback)(char *, void *),
105     void *callback_data)
106 {
107 	prompt_mode_begin(PROMPT_MODE_CHAR, prompt, NULL, callback,
108 	    callback_data);
109 }
110 
111 void
prompt_get_command(const char * prompt,void (* callback)(char *,void *),void * callback_data)112 prompt_get_command(const char *prompt, void (*callback)(char *, void *),
113     void *callback_data)
114 {
115 	prompt_mode_begin(PROMPT_MODE_LINE, prompt, prompt_command_history,
116 	    callback, callback_data);
117 }
118 
119 void
prompt_get_search_query(const char * prompt,void (* callback)(char *,void *),void * callback_data)120 prompt_get_search_query(const char *prompt, void (*callback)(char *, void *),
121     void *callback_data)
122 {
123 	prompt_mode_begin(PROMPT_MODE_LINE, prompt, prompt_search_history,
124 	    callback, callback_data);
125 }
126 
127 static void
prompt_char_handle_key(int key)128 prompt_char_handle_key(int key)
129 {
130 	switch (key) {
131 	case 'N':
132 	case 'n':
133 	case K_CTRL('G'):
134 		prompt_line[0] = 'n';
135 		prompt_mode_end();
136 		break;
137 	case 'Y':
138 	case 'y':
139 	case K_ENTER:
140 		prompt_line[0] = 'y';
141 		prompt_mode_end();
142 		break;
143 	}
144 }
145 
146 void
prompt_handle_key(int key)147 prompt_handle_key(int key)
148 {
149 	if (prompt_mode == PROMPT_MODE_CHAR)
150 		prompt_char_handle_key(key);
151 	else
152 		prompt_line_handle_key(key);
153 }
154 
155 static void
prompt_line_handle_key(int key)156 prompt_line_handle_key(int key)
157 {
158 	size_t		 i, j;
159 	int		 done;
160 	const char	*line;
161 
162 	done = 0;
163 	switch (key) {
164 	case K_CTRL('A'):
165 	case K_HOME:
166 		prompt_linepos = 0;
167 		break;
168 	case K_CTRL('B'):
169 	case K_LEFT:
170 		if (prompt_linepos > 0)
171 			prompt_linepos--;
172 		break;
173 	case K_CTRL('D'):
174 	case K_DELETE:
175 		if (prompt_linepos == prompt_linelen)
176 			break;
177 
178 		for (i = prompt_linepos; i < prompt_linelen; i++)
179 			prompt_line[i] = prompt_line[i + 1];
180 		prompt_linelen--;
181 		break;
182 	case K_CTRL('E'):
183 	case K_END:
184 		prompt_linepos = prompt_linelen;
185 		break;
186 	case K_CTRL('F'):
187 	case K_RIGHT:
188 		if (prompt_linepos < prompt_linelen)
189 			prompt_linepos++;
190 		break;
191 	case K_CTRL('G'):
192 	case K_ESCAPE:
193 		free(prompt_line);
194 		prompt_line = NULL;
195 		done = 1;
196 		break;
197 	case K_CTRL('H'):
198 	case K_BACKSPACE:
199 		if (prompt_linepos == 0)
200 			break;
201 
202 		prompt_linepos--;
203 		for (i = prompt_linepos; i < prompt_linelen; i++)
204 			prompt_line[i] = prompt_line[i + 1];
205 		prompt_linelen--;
206 		break;
207 	case K_CTRL('K'):
208 		prompt_linelen = prompt_linepos;
209 		prompt_line[prompt_linelen] = '\0';
210 		break;
211 	case K_CTRL('U'):
212 		prompt_linelen = 0;
213 		prompt_linepos = 0;
214 		prompt_scroll_offset = 0;
215 		prompt_line[0] = '\0';
216 		break;
217 	case K_CTRL('W'):
218 		i = 0;
219 		while (i < prompt_linepos && !isalnum(
220 		    (unsigned char)prompt_line[prompt_linepos - i - 1]))
221 			i++;
222 
223 		while (i < prompt_linepos && isalnum(
224 		    (unsigned char)prompt_line[prompt_linepos - i - 1]))
225 			i++;
226 
227 		prompt_linepos -= i;
228 		prompt_linelen -= i;
229 		for (j = prompt_linepos; j <= prompt_linelen; j++)
230 			prompt_line[j] = prompt_line[j + i];
231 		break;
232 	case K_DOWN:
233 		if (prompt_history == NULL)
234 			break;
235 
236 		if ((line = history_get_prev(prompt_history)) == NULL) {
237 			prompt_linelen = 0;
238 			prompt_linepos = 0;
239 			prompt_scroll_offset = 0;
240 			prompt_line[0] = '\0';
241 		} else {
242 			free(prompt_line);
243 			prompt_line = xstrdup(line);
244 			prompt_linelen = strlen(prompt_line);
245 			prompt_linesize = prompt_linelen + 1;
246 			prompt_linepos = prompt_linelen;
247 		}
248 		break;
249 	case K_ENTER:
250 		if (prompt_history != NULL && prompt_linelen > 0)
251 			history_add(prompt_history, prompt_line);
252 		else {
253 			free(prompt_line);
254 			prompt_line = NULL;
255 		}
256 		done = 1;
257 		break;
258 	case K_UP:
259 		if (prompt_history == NULL)
260 			break;
261 
262 		if ((line = history_get_next(prompt_history)) != NULL) {
263 			free(prompt_line);
264 			prompt_line = xstrdup(line);
265 			prompt_linelen = strlen(prompt_line);
266 			prompt_linesize = prompt_linelen + 1;
267 			prompt_linepos = prompt_linelen;
268 		}
269 		break;
270 	default:
271 		/*
272 		 * Ignore control characters and function keys not handled
273 		 * above.
274 		 */
275 		if (iscntrl((unsigned char)key) || key > 127)
276 			break;
277 
278 		if (++prompt_linelen == prompt_linesize) {
279 			prompt_linesize += PROMPT_LINESIZE;
280 			prompt_line = xrealloc(prompt_line, prompt_linesize);
281 		}
282 
283 		for (i = prompt_linelen; i > prompt_linepos; i--)
284 			prompt_line[i] = prompt_line[i - 1];
285 
286 		prompt_line[prompt_linepos++] = key;
287 		break;
288 	}
289 
290 	if (done)
291 		prompt_mode_end();
292 	else
293 		prompt_print();
294 }
295 
296 void
prompt_init(void)297 prompt_init(void)
298 {
299 	prompt_command_history = history_init();
300 	prompt_search_history = history_init();
301 }
302 
303 static void
prompt_mode_begin(enum prompt_mode mode,const char * prompt,struct history * history,void (* callback)(char *,void *),void * callback_data)304 prompt_mode_begin(enum prompt_mode mode, const char *prompt,
305     struct history *history, void (*callback)(char *, void *),
306     void *callback_data)
307 {
308 	prompt_history = history;
309 	if (prompt_history != NULL)
310 		history_rewind(prompt_history);
311 
312 	prompt_mode = mode;
313 	if (prompt_mode == PROMPT_MODE_CHAR) {
314 		xasprintf(&prompt_prompt, "%s? ([y]/n): ", prompt);
315 		prompt_linesize = 1;
316 	} else {
317 		prompt_prompt = xstrdup(prompt);
318 		prompt_linesize = PROMPT_LINESIZE;
319 	}
320 
321 	prompt_promptlen = strlen(prompt_prompt);
322 	prompt_callback = callback;
323 	prompt_callback_data = callback_data;
324 	prompt_line = xmalloc(prompt_linesize);
325 	prompt_linelen = 0;
326 	prompt_linepos = 0;
327 	prompt_line[0] = '\0';
328 	prompt_scroll_offset = 0;
329 
330 	input_set_mode(INPUT_MODE_PROMPT);
331 	screen_prompt_begin();
332 	prompt_print();
333 }
334 
335 static void
prompt_mode_end(void)336 prompt_mode_end(void)
337 {
338 	screen_prompt_end();
339 	input_set_mode(INPUT_MODE_VIEW);
340 	free(prompt_prompt);
341 	prompt_callback(prompt_line, prompt_callback_data);
342 }
343 
344 void
prompt_print(void)345 prompt_print(void)
346 {
347 	size_t cursorpos;
348 
349 	prompt_adjust_scroll_offset();
350 	cursorpos = prompt_promptlen + prompt_linepos - prompt_scroll_offset;
351 
352 	screen_prompt_printf(cursorpos, "%s%s", prompt_prompt,
353 	    prompt_line + prompt_scroll_offset);
354 }
355