1 /* Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
2 *
3 * This file is part of ratpoison.
4 *
5 * ratpoison is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * ratpoison is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this software; see the file COPYING. If not, write to
17 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18 * Boston, MA 02111-1307 USA
19 */
20
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <X11/Xlib.h>
27 #include <X11/keysym.h>
28 #include <X11/Xutil.h>
29 #include <X11/Xatom.h>
30
31 #include "ratpoison.h"
32
33 /* bind functions */
34 static edit_status editor_forward_char (rp_input_line *line);
35 static edit_status editor_backward_char (rp_input_line *line);
36 static edit_status editor_forward_word (rp_input_line *line);
37 static edit_status editor_backward_word (rp_input_line *line);
38 static edit_status editor_beginning_of_line (rp_input_line *line);
39 static edit_status editor_end_of_line (rp_input_line *line);
40 static edit_status editor_delete_char (rp_input_line *line);
41 static edit_status editor_backward_delete_char (rp_input_line *line);
42 static edit_status editor_kill_word (rp_input_line *line);
43 static edit_status editor_backward_kill_word (rp_input_line *line);
44 static edit_status editor_kill_line (rp_input_line *line);
45 static edit_status editor_paste_selection (rp_input_line *line);
46 static edit_status editor_abort (rp_input_line *line);
47 static edit_status editor_no_action (rp_input_line *line);
48 static edit_status editor_enter (rp_input_line *line);
49 static edit_status editor_history_previous (rp_input_line *line);
50 static edit_status editor_history_next (rp_input_line *line);
51 static edit_status editor_backward_kill_line (rp_input_line *line);
52 static edit_status editor_complete_prev (rp_input_line *line);
53 static edit_status editor_complete_next (rp_input_line *line);
54
55 /* default edit action */
56 static edit_status editor_insert (rp_input_line *line, char *keysym_buf);
57
58
59 static char *saved_command = NULL;
60
61 typedef struct edit_binding edit_binding;
62
63 struct edit_binding
64 {
65 struct rp_key key;
66 edit_status (*func)(rp_input_line *);
67 };
68
69 static edit_binding edit_bindings[] =
70 { {{XK_g, RP_CONTROL_MASK}, editor_abort},
71 {{XK_Escape, 0}, editor_abort},
72 {{XK_f, RP_CONTROL_MASK}, editor_forward_char},
73 {{XK_Right, 0}, editor_forward_char},
74 {{XK_b, RP_CONTROL_MASK}, editor_backward_char},
75 {{XK_Left, 0}, editor_backward_char},
76 {{XK_f, RP_META_MASK}, editor_forward_word},
77 {{XK_b, RP_META_MASK}, editor_backward_word},
78 {{XK_a, RP_CONTROL_MASK}, editor_beginning_of_line},
79 {{XK_Home, 0}, editor_beginning_of_line},
80 {{XK_e, RP_CONTROL_MASK}, editor_end_of_line},
81 {{XK_End, 0}, editor_end_of_line},
82 {{XK_d, RP_CONTROL_MASK}, editor_delete_char},
83 {{XK_Delete, 0}, editor_delete_char},
84 {{XK_BackSpace, 0}, editor_backward_delete_char},
85 {{XK_h, RP_CONTROL_MASK}, editor_backward_delete_char},
86 {{XK_BackSpace, RP_META_MASK}, editor_backward_kill_word},
87 {{XK_d, RP_META_MASK}, editor_kill_word},
88 {{XK_k, RP_CONTROL_MASK}, editor_kill_line},
89 {{XK_u, RP_CONTROL_MASK}, editor_backward_kill_line},
90 {{XK_y, RP_CONTROL_MASK}, editor_paste_selection},
91 {{XK_p, RP_CONTROL_MASK}, editor_history_previous},
92 {{XK_Up, 0}, editor_history_previous},
93 {{XK_n, RP_CONTROL_MASK}, editor_history_next},
94 {{XK_Down, 0}, editor_history_next},
95 {{XK_Return, 0}, editor_enter},
96 {{XK_m, RP_CONTROL_MASK}, editor_enter},
97 {{XK_KP_Enter, 0}, editor_enter},
98 {{XK_Tab, 0}, editor_complete_next},
99 {{XK_ISO_Left_Tab, 0}, editor_complete_prev},
100 { {0, 0}, 0} };
101
102 rp_input_line *
input_line_new(char * prompt,char * preinput,int history_id,enum completion_styles style,completion_fn fn)103 input_line_new (char *prompt, char *preinput, int history_id,
104 enum completion_styles style, completion_fn fn)
105 {
106 rp_input_line *line;
107 size_t length;
108
109 line = xmalloc (sizeof (rp_input_line));
110 line->prompt = prompt;
111 line->compl = completions_new (fn, style);
112 line->history_id = history_id;
113
114 /* Allocate some memory to start with (100 extra bytes) */
115 length = strlen (preinput);
116 line->size = length + 1 + 100;
117 line->buffer = xmalloc (line->size);
118
119 /* load in the preinput */
120 memcpy (line->buffer, preinput, length);
121 line->buffer[length] = '\0';
122 line->position = line->length = length;
123
124 return line;
125 }
126
127 void
input_line_free(rp_input_line * line)128 input_line_free (rp_input_line *line)
129 {
130 completions_free (line->compl);
131 free (line->buffer);
132 free (line);
133 }
134
135 edit_status
execute_edit_action(rp_input_line * line,KeySym ch,unsigned int modifier,char * keysym_buf)136 execute_edit_action (rp_input_line *line, KeySym ch, unsigned int modifier, char *keysym_buf)
137 {
138 struct edit_binding *binding = NULL;
139 int found_binding = 0;
140 edit_status status;
141
142 for (binding = edit_bindings; binding->func; binding++)
143 {
144 if (ch == binding->key.sym && modifier == binding->key.state)
145 {
146 found_binding = 1;
147 break;
148 }
149 }
150
151 if (found_binding)
152 status = binding->func (line);
153 else if (modifier)
154 status = editor_no_action (line);
155 else
156 status = editor_insert (line, keysym_buf);
157
158 return status;
159 }
160
161 static edit_status
editor_forward_char(rp_input_line * line)162 editor_forward_char (rp_input_line *line)
163 {
164 if (line->position == line->length)
165 return EDIT_NO_OP;
166
167 if (RP_IS_UTF8_START (line->buffer[line->position]))
168 {
169 do
170 line->position++;
171 while (RP_IS_UTF8_CONT (line->buffer[line->position]));
172 }
173 else
174 line->position++;
175
176 return EDIT_MOVE;
177 }
178
179 static edit_status
editor_backward_char(rp_input_line * line)180 editor_backward_char (rp_input_line *line)
181 {
182 if (line->position == 0)
183 return EDIT_NO_OP;
184
185 do
186 line->position--;
187 while (line->position > 0 && RP_IS_UTF8_CONT (line->buffer[line->position]));
188
189 return EDIT_MOVE;
190 }
191
192 static edit_status
editor_forward_word(rp_input_line * line)193 editor_forward_word (rp_input_line *line)
194 {
195 if (line->position == line->length)
196 return EDIT_NO_OP;
197
198 while (line->position < line->length
199 && !isalnum ((unsigned char)line->buffer[line->position]))
200 line->position++;
201
202 while (line->position < line->length
203 && (isalnum ((unsigned char)line->buffer[line->position])
204 || RP_IS_UTF8_CHAR (line->buffer[line->position])))
205 line->position++;
206
207 return EDIT_MOVE;
208 }
209
210 static edit_status
editor_backward_word(rp_input_line * line)211 editor_backward_word (rp_input_line *line)
212 {
213 if (line->position == 0)
214 return EDIT_NO_OP;
215
216 while (line->position > 0 && !isalnum ((unsigned char)line->buffer[line->position]))
217 line->position--;
218
219 while (line->position > 0
220 && (isalnum ((unsigned char)line->buffer[line->position])
221 || RP_IS_UTF8_CHAR (line->buffer[line->position])))
222 line->position--;
223
224 return EDIT_MOVE;
225 }
226
227 static edit_status
editor_beginning_of_line(rp_input_line * line)228 editor_beginning_of_line (rp_input_line *line)
229 {
230 if (line->position == 0)
231 return EDIT_NO_OP;
232 else
233 {
234 line->position = 0;
235 return EDIT_MOVE;
236 }
237 }
238
239 static edit_status
editor_end_of_line(rp_input_line * line)240 editor_end_of_line (rp_input_line *line)
241 {
242 if (line->position == line->length)
243 return EDIT_NO_OP;
244 else
245 {
246 line->position = line->length;
247 return EDIT_MOVE;
248 }
249 }
250
251 static edit_status
editor_delete_char(rp_input_line * line)252 editor_delete_char (rp_input_line *line)
253 {
254 size_t diff = 0;
255
256 if (line->position == line->length)
257 return EDIT_NO_OP;
258
259 if (RP_IS_UTF8_START (line->buffer[line->position]))
260 {
261 do
262 diff++;
263 while (RP_IS_UTF8_CONT (line->buffer[line->position + diff]));
264 }
265 else
266 diff++;
267
268 memmove (&line->buffer[line->position],
269 &line->buffer[line->position + diff],
270 line->length - line->position + diff + 1);
271
272 line->length -= diff;
273
274 return EDIT_DELETE;
275 }
276
277 static edit_status
editor_backward_delete_char(rp_input_line * line)278 editor_backward_delete_char (rp_input_line *line)
279 {
280 size_t diff = 1;
281
282 if (line->position == 0)
283 return EDIT_NO_OP;
284
285 while (line->position - diff > 0
286 && RP_IS_UTF8_CONT (line->buffer[line->position - diff]))
287 diff++;
288
289 memmove (&line->buffer[line->position - diff],
290 &line->buffer[line->position],
291 line->length - line->position + 1);
292
293 line->position -= diff;
294 line->length -= diff;
295
296 return EDIT_DELETE;
297 }
298
299 static edit_status
editor_kill_word(rp_input_line * line)300 editor_kill_word (rp_input_line *line)
301 {
302 size_t diff = 0;
303
304 if (line->position == line->length)
305 return EDIT_NO_OP;
306
307 while (line->position + diff < line->length &&
308 !isalnum ((unsigned char)line->buffer[line->position + diff]))
309 diff++;
310
311 while (line->position + diff < line->length
312 && (isalnum ((unsigned char)line->buffer[line->position + diff])
313 || RP_IS_UTF8_CHAR (line->buffer[line->position + diff])))
314 diff++;
315
316 /* Add the word to the X11 selection. */
317 set_nselection (&line->buffer[line->position], diff);
318
319 memmove (&line->buffer[line->position],
320 &line->buffer[line->position + diff],
321 line->length - line->position + diff + 1);
322
323 line->length -= diff;
324
325 return EDIT_DELETE;
326 }
327
328 static edit_status
editor_backward_kill_word(rp_input_line * line)329 editor_backward_kill_word (rp_input_line *line)
330 {
331 size_t diff = 1;
332
333 if (line->position == 0)
334 return EDIT_NO_OP;
335
336 while (line->position - diff > 0 &&
337 !isalnum ((unsigned char)line->buffer[line->position - diff]))
338 diff++;
339
340 while (line->position - diff > 0
341 && (isalnum ((unsigned char)line->buffer[line->position - diff])
342 || RP_IS_UTF8_CHAR (line->buffer[line->position - diff])))
343 diff++;
344
345 /* Add the word to the X11 selection. */
346 set_nselection (&line->buffer[line->position - diff], diff);
347
348 memmove (&line->buffer[line->position - diff],
349 &line->buffer[line->position],
350 line->length - line->position + 1);
351
352 line->position -= diff;
353 line->length -= diff;
354
355 return EDIT_DELETE;
356 }
357
358 static edit_status
editor_kill_line(rp_input_line * line)359 editor_kill_line (rp_input_line *line)
360 {
361 if (line->position == line->length)
362 return EDIT_NO_OP;
363
364 /* Add the line to the X11 selection. */
365 set_selection (&line->buffer[line->position]);
366
367 line->length = line->position;
368 line->buffer[line->length] = '\0';
369
370 return EDIT_DELETE;
371 }
372
373 /* Do the dirty work of killing a line backwards. */
374 static void
backward_kill_line(rp_input_line * line)375 backward_kill_line (rp_input_line *line)
376 {
377 memmove (&line->buffer[0],
378 &line->buffer[line->position],
379 line->length - line->position + 1);
380
381 line->length -= line->position;
382 line->position = 0;
383 }
384
385 static edit_status
editor_backward_kill_line(rp_input_line * line)386 editor_backward_kill_line (rp_input_line *line)
387 {
388 if (line->position == 0)
389 return EDIT_NO_OP;
390
391 /* Add the line to the X11 selection. */
392 set_nselection (line->buffer, line->position);
393
394 backward_kill_line (line);
395
396 return EDIT_DELETE;
397 }
398
399 static edit_status
editor_history_previous(rp_input_line * line)400 editor_history_previous (rp_input_line *line)
401 {
402 const char *entry = history_previous (line->history_id);
403
404 if (entry)
405 {
406 if (!saved_command)
407 {
408 line->buffer[line->length] = '\0';
409 saved_command = xstrdup (line->buffer);
410 PRINT_DEBUG (("saved current command line: \'%s\'\n", saved_command));
411 }
412
413 free (line->buffer);
414 line->buffer = xstrdup (entry);
415 line->length = strlen (line->buffer);
416 line->size = line->length + 1;
417 line->position = line->length;
418 PRINT_DEBUG (("entry: \'%s\'\n", line->buffer));
419 }
420 else
421 {
422 PRINT_DEBUG (("- do nothing -\n"));
423 return EDIT_NO_OP;
424 }
425
426 return EDIT_INSERT;
427 }
428
429 static edit_status
editor_history_next(rp_input_line * line)430 editor_history_next (rp_input_line *line)
431 {
432 const char *entry = history_next (line->history_id);
433
434 if (entry)
435 {
436 free (line->buffer);
437 line->buffer = xstrdup (entry);
438 PRINT_DEBUG (("entry: \'%s\'\n", line->buffer));
439 }
440 else if (saved_command)
441 {
442 free (line->buffer);
443 line->buffer = saved_command;
444 saved_command = NULL;
445 PRINT_DEBUG (("restored command line: \'%s\'\n", line->buffer));
446 }
447 else
448 {
449 PRINT_DEBUG (("- do nothing -\n"));
450 return EDIT_NO_OP;
451 }
452
453 line->length = strlen (line->buffer);
454 line->size = line->length + 1;
455 line->position = line->length;
456
457 return EDIT_INSERT;
458 }
459
460 static edit_status
editor_abort(rp_input_line * line UNUSED)461 editor_abort (rp_input_line *line UNUSED)
462 {
463 return EDIT_ABORT;
464 }
465
466 static edit_status
editor_no_action(rp_input_line * line UNUSED)467 editor_no_action (rp_input_line *line UNUSED)
468 {
469 return EDIT_NO_OP;
470 }
471
472 static edit_status
editor_insert(rp_input_line * line,char * keysym_buf)473 editor_insert (rp_input_line *line, char *keysym_buf)
474 {
475 size_t nbytes;
476
477 PRINT_DEBUG (("keysym_buf: '%s'\n", keysym_buf));
478
479 nbytes = strlen (keysym_buf);
480 if (line->length + nbytes > line->size - 1)
481 {
482 line->size += nbytes + 100;
483 line->buffer = xrealloc (line->buffer, line->size);
484 }
485
486 memmove (&line->buffer[line->position + nbytes],
487 &line->buffer[line->position],
488 line->length - line->position + 1);
489 memcpy (&line->buffer[line->position], keysym_buf, nbytes);
490
491 line->length += nbytes;
492 line->position += nbytes;
493
494 return EDIT_INSERT;
495 }
496
497 static edit_status
editor_enter(rp_input_line * line)498 editor_enter (rp_input_line *line)
499 {
500 int result;
501 char *expansion;
502
503 line->buffer[line->length] = '\0';
504
505 if (!defaults.history_expansion) {
506 history_add (line->history_id, line->buffer);
507 return EDIT_DONE;
508 }
509
510 result = history_expand_line (line->history_id, line->buffer, &expansion);
511
512 PRINT_DEBUG (("History Expansion - result: %d\n", result));
513 PRINT_DEBUG (("History Expansion - expansion: \'%s\'\n", expansion));
514
515 if (result == -1 || result == 2)
516 {
517 marked_message_printf (0, 0, "%s", expansion);
518 free (expansion);
519 return EDIT_ABORT;
520 }
521 else /* result == 0 || result == 1 */
522 {
523 history_add (line->history_id, expansion);
524 free (line->buffer);
525 line->buffer = expansion;
526 }
527
528 return EDIT_DONE;
529 }
530
531 static edit_status
editor_paste_selection(rp_input_line * line)532 editor_paste_selection (rp_input_line *line)
533 {
534 char *text;
535
536 text = get_selection ();
537 if (text)
538 {
539 editor_insert (line, text);
540 free (text);
541 return EDIT_INSERT;
542 }
543 else
544 return EDIT_NO_OP;
545 }
546
547 static edit_status
editor_complete(rp_input_line * line,int direction)548 editor_complete (rp_input_line *line, int direction)
549 {
550 char *tmp;
551 char *s;
552
553 /* Create our partial string that will be used for completion. It is
554 the characters up to the position of the cursor. */
555 tmp = xmalloc (line->position + 1);
556 memcpy (tmp, line->buffer, line->position);
557 tmp[line->position] = '\0';
558
559 /* We don't need to free s because it's a string from the completion
560 list. */
561 s = completions_complete (line->compl, tmp, direction);
562 free (tmp);
563
564 if (s == NULL)
565 return EDIT_NO_OP;
566
567 /* Insert the completion. */
568 backward_kill_line (line);
569 editor_insert (line, s);
570
571 return EDIT_COMPLETE;
572 }
573
574 static edit_status
editor_complete_next(rp_input_line * line)575 editor_complete_next (rp_input_line *line)
576 {
577 return editor_complete (line, COMPLETION_NEXT);
578 }
579
580 static edit_status
editor_complete_prev(rp_input_line * line)581 editor_complete_prev (rp_input_line *line)
582 {
583 return editor_complete (line, COMPLETION_PREVIOUS);
584 }
585