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