jcs ratpoison hax
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 */
34static edit_status editor_forward_char (rp_input_line *line);
35static edit_status editor_backward_char (rp_input_line *line);
36static edit_status editor_forward_word (rp_input_line *line);
37static edit_status editor_backward_word (rp_input_line *line);
38static edit_status editor_beginning_of_line (rp_input_line *line);
39static edit_status editor_end_of_line (rp_input_line *line);
40static edit_status editor_delete_char (rp_input_line *line);
41static edit_status editor_backward_delete_char (rp_input_line *line);
42static edit_status editor_kill_word (rp_input_line *line);
43static edit_status editor_backward_kill_word (rp_input_line *line);
44static edit_status editor_kill_line (rp_input_line *line);
45static edit_status editor_paste_selection (rp_input_line *line);
46static edit_status editor_abort (rp_input_line *line);
47static edit_status editor_no_action (rp_input_line *line);
48static edit_status editor_enter (rp_input_line *line);
49static edit_status editor_history_previous (rp_input_line *line);
50static edit_status editor_history_next (rp_input_line *line);
51static edit_status editor_backward_kill_line (rp_input_line *line);
52static edit_status editor_complete_prev (rp_input_line *line);
53static edit_status editor_complete_next (rp_input_line *line);
54
55/* default edit action */
56static edit_status editor_insert (rp_input_line *line, char *keysym_buf);
57
58
59static char *saved_command = NULL;
60
61typedef struct edit_binding edit_binding;
62
63struct edit_binding
64{
65 struct rp_key key;
66 edit_status (*func)(rp_input_line *);
67};
68
69static 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
102rp_input_line *
103input_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
127void
128input_line_free (rp_input_line *line)
129{
130 completions_free (line->compl);
131 free (line->buffer);
132 free (line);
133}
134
135edit_status
136execute_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
161static edit_status
162editor_forward_char (rp_input_line *line)
163{
164 if (line->position == line->length)
165 return EDIT_NO_OP;
166
167 if (isu8start (line->buffer[line->position]))
168 {
169 do
170 line->position++;
171 while (isu8cont (line->buffer[line->position]));
172 }
173 else
174 line->position++;
175
176 return EDIT_MOVE;
177}
178
179static edit_status
180editor_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 && isu8cont (line->buffer[line->position]));
188
189 return EDIT_MOVE;
190}
191
192static edit_status
193editor_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 || isu8char (line->buffer[line->position])))
205 line->position++;
206
207 return EDIT_MOVE;
208}
209
210static edit_status
211editor_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 || isu8char (line->buffer[line->position])))
222 line->position--;
223
224 return EDIT_MOVE;
225}
226
227static edit_status
228editor_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
239static edit_status
240editor_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
251static edit_status
252editor_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 (isu8start (line->buffer[line->position]))
260 {
261 do
262 diff++;
263 while (isu8cont (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
277static edit_status
278editor_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 && isu8cont (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
299static edit_status
300editor_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 || isu8char (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
328static edit_status
329editor_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 || isu8char (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
358static edit_status
359editor_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. */
374static void
375backward_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
385static edit_status
386editor_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
399static edit_status
400editor_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
429static edit_status
430editor_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
460static edit_status
461editor_abort (rp_input_line *line UNUSED)
462{
463 return EDIT_ABORT;
464}
465
466static edit_status
467editor_no_action (rp_input_line *line UNUSED)
468{
469 return EDIT_NO_OP;
470}
471
472static edit_status
473editor_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
497static edit_status
498editor_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
531static edit_status
532editor_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
547static edit_status
548editor_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
574static edit_status
575editor_complete_next (rp_input_line *line)
576{
577 return editor_complete (line, COMPLETION_NEXT);
578}
579
580static edit_status
581editor_complete_prev (rp_input_line *line)
582{
583 return editor_complete (line, COMPLETION_PREVIOUS);
584}