1 /*
2 * (c) 2004 by Kalle Wallin <kaw@linux.se>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 */
19 #include "wreadln.h"
20 #include "charset.h"
21 #include "screen_utils.h"
22 #include "config.h"
24 #include <assert.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <glib.h>
29 #ifdef ENABLE_WIDE
30 #include <sys/poll.h>
31 #endif
33 #define KEY_CTRL_A 1
34 #define KEY_CTRL_B 2
35 #define KEY_CTRL_C 3
36 #define KEY_CTRL_D 4
37 #define KEY_CTRL_E 5
38 #define KEY_CTRL_F 6
39 #define KEY_CTRL_G 7
40 #define KEY_CTRL_K 11
41 #define KEY_CTRL_N 14
42 #define KEY_CTRL_P 16
43 #define KEY_CTRL_U 21
44 #define KEY_CTRL_Z 26
45 #define KEY_BCKSPC 8
46 #define TAB 9
48 struct wreadln {
49 /** the ncurses window where this field is displayed */
50 WINDOW *const w;
52 /** the origin coordinates in the window */
53 unsigned x, y;
55 /** the screen width of the input field */
56 unsigned width;
58 /** is the input masked, i.e. characters displayed as '*'? */
59 const gboolean masked;
61 /** the byte position of the cursor */
62 size_t cursor;
64 /** the byte position displayed at the origin (for horizontal
65 scrolling) */
66 size_t start;
68 /** the current value */
69 gchar line[1024];
70 };
72 /** max items stored in the history list */
73 static const guint wrln_max_history_length = 32;
75 void *wrln_completion_callback_data = NULL;
76 wrln_gcmp_pre_cb_t wrln_pre_completion_callback = NULL;
77 wrln_gcmp_post_cb_t wrln_post_completion_callback = NULL;
79 /* move the cursor one step to the right */
80 static inline void cursor_move_right(struct wreadln *wr)
81 {
82 if (wr->line[wr->cursor] == 0)
83 return;
85 ++wr->cursor;
86 if (wr->cursor >= (size_t)wr->width &&
87 wr->start < wr->cursor - wr->width + 1)
88 ++wr->start;
89 }
91 /* move the cursor one step to the left */
92 static inline void cursor_move_left(struct wreadln *wr)
93 {
94 if (wr->cursor == 0)
95 return;
97 if (wr->cursor == wr->start && wr->start > 0)
98 --wr->start;
99 --wr->cursor;
100 }
102 /* move the cursor to the end of the line */
103 static inline void cursor_move_to_eol(struct wreadln *wr)
104 {
105 wr->cursor = strlen(wr->line);
106 if (wr->cursor >= wr->width)
107 wr->start = wr->cursor - wr->width + 1;
108 }
110 /* draw line buffer and update cursor position */
111 static inline void drawline(const struct wreadln *wr)
112 {
113 wmove(wr->w, wr->y, wr->x);
114 /* clear input area */
115 whline(wr->w, ' ', wr->width);
116 /* print visible part of the line buffer */
117 if (wr->masked)
118 whline(wr->w, '*', utf8_width(wr->line) - wr->start);
119 else
120 waddnstr(wr->w, wr->line + wr->start, wr->width);
121 /* move the cursor to the correct position */
122 wmove(wr->w, wr->y, wr->x + wr->cursor - wr->start);
123 /* tell ncurses to redraw the screen */
124 doupdate();
125 }
127 #ifdef ENABLE_WIDE
128 static bool
129 multibyte_is_complete(const char *p, size_t length)
130 {
131 GError *error = NULL;
132 gchar *q = g_locale_to_utf8(p, length,
133 NULL, NULL, &error);
134 if (q != NULL) {
135 g_free(q);
136 return true;
137 } else {
138 g_error_free(error);
139 return false;
140 }
141 }
142 #endif
144 static void
145 wreadln_insert_byte(struct wreadln *wr, gint key)
146 {
147 size_t rest = strlen(wr->line + wr->cursor) + 1;
148 #ifdef ENABLE_WIDE
149 char buffer[32] = { key };
150 size_t length = 1;
151 struct pollfd pfd = {
152 .fd = 0,
153 .events = POLLIN,
154 };
155 int ret;
157 /* wide version: try to complete the multibyte sequence */
159 while (length < sizeof(buffer)) {
160 if (multibyte_is_complete(buffer, length))
161 /* sequence is complete */
162 break;
164 /* poll for more bytes on stdin, without timeout */
166 ret = poll(&pfd, 1, 0);
167 if (ret <= 0)
168 /* no more input from keyboard */
169 break;
171 buffer[length++] = wgetch(wr->w);
172 }
174 memmove(wr->line + wr->cursor + length,
175 wr->line + wr->cursor, rest);
176 memcpy(wr->line + wr->cursor, buffer, length);
178 #else
179 const size_t length = 1;
181 memmove(wr->line + wr->cursor + length,
182 wr->line + wr->cursor, rest);
183 wr->line[wr->cursor] = key;
185 #endif
187 wr->cursor += length;
188 if (wr->cursor >= (size_t)wr->width &&
189 wr->start < wr->cursor - wr->width + 1)
190 wr->start += length;
191 }
193 static void
194 wreadln_delete_char(struct wreadln *wr, size_t x)
195 {
196 size_t rest;
197 const size_t length = 1;
199 assert(x < strlen(wr->line));
201 rest = strlen(&wr->line[x + length]) + 1;
202 memmove(&wr->line[x], &wr->line[x + length], rest);
203 }
205 /* libcurses version */
207 static gchar *
208 _wreadln(WINDOW *w,
209 const gchar *prompt,
210 const gchar *initial_value,
211 unsigned x1,
212 GList **history,
213 GCompletion *gcmp,
214 gboolean masked)
215 {
216 struct wreadln wr = {
217 .w = w,
218 .masked = masked,
219 .cursor = 0,
220 .start = 0,
221 };
222 GList *hlist = NULL, *hcurrent = NULL;
223 gint key = 0;
224 size_t i;
226 /* turn off echo */
227 noecho();
228 /* make shure the cursor is visible */
229 curs_set(1);
230 /* print prompt string */
231 if (prompt)
232 waddstr(w, prompt);
233 /* retrive y and x0 position */
234 getyx(w, wr.y, wr.x);
235 /* check the x1 value */
236 if (x1 <= wr.x || x1 > (unsigned)COLS)
237 x1 = COLS;
238 wr.width = x1 - wr.x;
239 /* clear input area */
240 mvwhline(w, wr.y, wr.x, ' ', wr.width);
242 if (history) {
243 /* append the a new line to our history list */
244 *history = g_list_append(*history, g_malloc0(sizeof(wr.line)));
245 /* hlist points to the current item in the history list */
246 hlist = g_list_last(*history);
247 hcurrent = hlist;
248 }
250 if (initial_value == (char *)-1) {
251 /* get previous history entry */
252 if (history && hlist->prev) {
253 if (hlist == hcurrent)
254 /* save the current line */
255 g_strlcpy(hlist->data, wr.line, sizeof(wr.line));
257 /* get previous line */
258 hlist = hlist->prev;
259 g_strlcpy(wr.line, hlist->data, sizeof(wr.line));
260 }
261 cursor_move_to_eol(&wr);
262 drawline(&wr);
263 } else if (initial_value) {
264 /* copy the initial value to the line buffer */
265 g_strlcpy(wr.line, initial_value, sizeof(wr.line));
266 cursor_move_to_eol(&wr);
267 drawline(&wr);
268 }
270 while (key != 13 && key != '\n') {
271 key = wgetch(w);
273 /* check if key is a function key */
274 for (i = 0; i < 63; i++)
275 if (key == (int)KEY_F(i)) {
276 key = KEY_F(1);
277 i = 64;
278 }
280 switch (key) {
281 #ifdef HAVE_GETMOUSE
282 case KEY_MOUSE: /* ignore mouse events */
283 #endif
284 case ERR: /* ingnore errors */
285 break;
287 case TAB:
288 if (gcmp) {
289 char *prefix = NULL;
290 GList *list;
292 if (wrln_pre_completion_callback)
293 wrln_pre_completion_callback(gcmp, wr.line,
294 wrln_completion_callback_data);
295 list = g_completion_complete(gcmp, wr.line, &prefix);
296 if (prefix) {
297 g_strlcpy(wr.line, prefix, sizeof(wr.line));
298 cursor_move_to_eol(&wr);
299 g_free(prefix);
300 } else
301 screen_bell();
303 if (wrln_post_completion_callback)
304 wrln_post_completion_callback(gcmp, wr.line, list,
305 wrln_completion_callback_data);
306 }
307 break;
309 case KEY_CTRL_G:
310 screen_bell();
311 if (history) {
312 g_free(hcurrent->data);
313 hcurrent->data = NULL;
314 *history = g_list_delete_link(*history, hcurrent);
315 }
316 return NULL;
318 case KEY_LEFT:
319 case KEY_CTRL_B:
320 cursor_move_left(&wr);
321 break;
322 case KEY_RIGHT:
323 case KEY_CTRL_F:
324 cursor_move_right(&wr);
325 break;
326 case KEY_HOME:
327 case KEY_CTRL_A:
328 wr.cursor = 0;
329 wr.start = 0;
330 break;
331 case KEY_END:
332 case KEY_CTRL_E:
333 cursor_move_to_eol(&wr);
334 break;
335 case KEY_CTRL_K:
336 wr.line[wr.cursor] = 0;
337 break;
338 case KEY_CTRL_U:
339 wr.cursor = utf8_width(wr.line);
340 for (i = 0; i < wr.cursor; i++)
341 wr.line[i] = '\0';
342 wr.cursor = 0;
343 break;
344 case 127:
345 case KEY_BCKSPC: /* handle backspace: copy all */
346 case KEY_BACKSPACE: /* chars starting from curpos */
347 if (wr.cursor > 0) { /* - 1 from buf[n+1] to buf */
348 cursor_move_left(&wr);
349 wreadln_delete_char(&wr, wr.cursor);
350 }
351 break;
352 case KEY_DC: /* handle delete key. As above */
353 case KEY_CTRL_D:
354 if (wr.line[wr.cursor] != 0)
355 wreadln_delete_char(&wr, wr.cursor);
356 break;
357 case KEY_UP:
358 case KEY_CTRL_P:
359 /* get previous history entry */
360 if (history && hlist->prev) {
361 if (hlist == hcurrent)
362 /* save the current line */
363 g_strlcpy(hlist->data, wr.line,
364 sizeof(wr.line));
366 /* get previous line */
367 hlist = hlist->prev;
368 g_strlcpy(wr.line, hlist->data,
369 sizeof(wr.line));
370 }
371 cursor_move_to_eol(&wr);
372 break;
373 case KEY_DOWN:
374 case KEY_CTRL_N:
375 /* get next history entry */
376 if (history && hlist->next) {
377 /* get next line */
378 hlist = hlist->next;
379 g_strlcpy(wr.line, hlist->data,
380 sizeof(wr.line));
381 }
382 cursor_move_to_eol(&wr);
383 break;
385 case '\n':
386 case 13:
387 case KEY_IC:
388 case KEY_PPAGE:
389 case KEY_NPAGE:
390 case KEY_F(1):
391 /* ignore char */
392 break;
393 default:
394 if (key >= 32)
395 wreadln_insert_byte(&wr, key);
396 }
398 drawline(&wr);
399 }
401 /* update history */
402 if (history) {
403 if (strlen(wr.line)) {
404 /* update the current history entry */
405 size_t size = strlen(wr.line) + 1;
406 hcurrent->data = g_realloc(hcurrent->data, size);
407 g_strlcpy(hcurrent->data, wr.line, size);
408 } else {
409 /* the line was empty - remove the current history entry */
410 g_free(hcurrent->data);
411 hcurrent->data = NULL;
412 *history = g_list_delete_link(*history, hcurrent);
413 }
415 while (g_list_length(*history) > wrln_max_history_length) {
416 GList *first = g_list_first(*history);
418 /* remove the oldest history entry */
419 g_free(first->data);
420 first->data = NULL;
421 *history = g_list_delete_link(*history, first);
422 }
423 }
425 return g_strdup(wr.line);
426 }
428 gchar *
429 wreadln(WINDOW *w,
430 const gchar *prompt,
431 const gchar *initial_value,
432 unsigned x1,
433 GList **history,
434 GCompletion *gcmp)
435 {
436 return _wreadln(w, prompt, initial_value, x1, history, gcmp, FALSE);
437 }
439 gchar *
440 wreadln_masked(WINDOW *w,
441 const gchar *prompt,
442 const gchar *initial_value,
443 unsigned x1,
444 GList **history,
445 GCompletion *gcmp)
446 {
447 return _wreadln(w, prompt, initial_value, x1, history, gcmp, TRUE);
448 }