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