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