diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 8 | ||||
-rw-r--r-- | src/actions.c | 176 | ||||
-rw-r--r-- | src/completions.c | 104 | ||||
-rw-r--r-- | src/completions.h | 13 | ||||
-rw-r--r-- | src/conf.h | 23 | ||||
-rw-r--r-- | src/data.h | 40 | ||||
-rw-r--r-- | src/editor.c | 576 | ||||
-rw-r--r-- | src/editor.h | 24 | ||||
-rw-r--r-- | src/globals.c | 1 | ||||
-rw-r--r-- | src/globals.h | 2 | ||||
-rw-r--r-- | src/input.c | 259 | ||||
-rw-r--r-- | src/input.h | 7 | ||||
-rw-r--r-- | src/linkedlist.h | 9 | ||||
-rw-r--r-- | src/main.c | 9 | ||||
-rw-r--r-- | src/ratpoison.h | 3 | ||||
-rw-r--r-- | src/sbuf.h | 3 |
16 files changed, 1096 insertions, 161 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 457acfb..953e256 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -17,7 +17,7 @@ ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## -## $Id: Makefile.am,v 1.17 2003/05/20 07:20:15 sabetts Exp $ +## $Id: Makefile.am,v 1.18 2003/05/27 07:51:06 sabetts Exp $ bin_PROGRAMS = ratpoison @@ -25,10 +25,14 @@ ratpoison_SOURCES = actions.c \ actions.h \ bar.c \ bar.h \ + completions.c \ + completions.h \ communications.c \ communications.h \ conf.h \ data.h \ + editor.c \ + editor.h \ events.c \ events.h \ frame.c \ @@ -40,6 +44,8 @@ ratpoison_SOURCES = actions.c \ globals.c \ group.h \ group.c \ + history.h \ + history.c \ input.c \ input.h \ linkedlist.h \ diff --git a/src/actions.c b/src/actions.c index 0b5a323..8b2a04b 100644 --- a/src/actions.c +++ b/src/actions.c @@ -755,6 +755,42 @@ string_to_window_number (char *str) return *s ? -1 : i; } + +struct list_head * +trivial_completions (char* str) +{ + struct list_head *list; + + /* Initialize our list. */ + list = xmalloc (sizeof (struct list_head)); + INIT_LIST_HEAD (list); + + return list; +} + +struct list_head * +window_completions (char* str) +{ + rp_window_elem *cur; + struct list_head *list; + + /* Initialize our list. */ + list = xmalloc (sizeof (struct list_head)); + INIT_LIST_HEAD (list); + + /* Gather the names of all the windows. */ + list_for_each_entry (cur, &rp_current_group->mapped_windows, node) + { + struct sbuf *name; + + name = sbuf_new (0); + sbuf_copy (name, window_name (cur->win)); + list_add_tail (&name->node, list); + } + + return list; +} + /* switch to window number or name */ char * cmd_select (int interactive, char *data) @@ -763,7 +799,7 @@ cmd_select (int interactive, char *data) int n; if (data == NULL) - str = get_input (MESSAGE_PROMPT_SWITCH_TO_WINDOW); + str = get_input (MESSAGE_PROMPT_SWITCH_TO_WINDOW, window_completions); else str = xstrdup (data); @@ -814,7 +850,7 @@ cmd_rename (int interactive, char *data) if (current_window() == NULL) return NULL; if (data == NULL) - winname = get_input (MESSAGE_PROMPT_NEW_WINDOW_NAME); + winname = get_input (MESSAGE_PROMPT_NEW_WINDOW_NAME, trivial_completions); else winname = xstrdup (data); @@ -959,6 +995,42 @@ command (int interactive, char *data) return result; } +struct list_head * +colon_completions (char* str) +{ + int i; + struct sbuf *s; + struct list_head *list; + + /* Initialize our list. */ + list = xmalloc (sizeof (struct list_head)); + INIT_LIST_HEAD (list); + + /* Put all the aliases in our list. */ + for(i=0; i<alias_list_last; ++i) + { + s = sbuf_new (0); + sbuf_copy (s, alias_list[i].name); + /* The space is so when the user completes a space is + conveniently inserted after the command. */ + sbuf_concat (s, " "); + list_add_tail (&s->node, list); + } + + /* Put all the commands in our list. */ + for(i=0; user_commands[i].name; ++i) + { + s = sbuf_new (0); + sbuf_copy (s, user_commands[i].name); + /* The space is so when the user completes a space is + conveniently inserted after the command. */ + sbuf_concat (s, " "); + list_add_tail (&s->node, list); + } + + return list; +} + char * cmd_colon (int interactive, char *data) { @@ -966,9 +1038,9 @@ cmd_colon (int interactive, char *data) char *input; if (data == NULL) - input = get_input (MESSAGE_PROMPT_COMMAND); + input = get_input (MESSAGE_PROMPT_COMMAND, colon_completions); else - input = get_more_input (MESSAGE_PROMPT_COMMAND, data); + input = get_more_input (MESSAGE_PROMPT_COMMAND, data, colon_completions); /* User aborted. */ if (input == NULL) @@ -985,13 +1057,76 @@ cmd_colon (int interactive, char *data) return NULL; } +struct list_head * +exec_completions (char *str) +{ + size_t n = 256; + char *partial; + struct sbuf *line; + FILE *file; + struct list_head *head; + char *completion_string; + + /* Initialize our list. */ + head = xmalloc (sizeof (struct list_head)); + INIT_LIST_HEAD (head); + + /* FIXME: A Bash dependancy?? */ + completion_string = xsprintf("bash -c \"compgen -ac %s|sort\"", str); + file = popen (completion_string, "r"); + free (completion_string); + if (!file) + { + PRINT_ERROR (("popen failed\n")); + return head; + } + + partial = (char*)xmalloc (n); + + /* Read data from the file, split it into lines and store it in a + list. */ + line = sbuf_new (0); + while (fgets (partial, n, file) != NULL) + { + /* Read a chunk from the file into our line accumulator. */ + sbuf_concat (line, partial); + + if (feof(file) || (*(sbuf_get (line) + strlen(sbuf_get (line)) - 1) == '\n')) + { + char *s; + struct sbuf *elem; + + s = sbuf_get (line); + + /* Frob the newline into */ + if (*(s + strlen(s) - 1) == '\n') + *(s + strlen(s) - 1) = '\0'; + + /* Add our line to the list. */ + elem = sbuf_new (0); + sbuf_copy (elem, s); + /* The space is so when the user completes a space is + conveniently inserted after the command. */ + sbuf_concat (elem, " "); + list_add_tail (&elem->node, head); + + sbuf_clear (line); + } + } + + free (partial); + pclose (file); + + return head; +} + char * cmd_exec (int interactive, char *data) { char *cmd; if (data == NULL) - cmd = get_input (MESSAGE_PROMPT_SHELL_COMMAND); + cmd = get_input (MESSAGE_PROMPT_SHELL_COMMAND, exec_completions); else cmd = xstrdup (data); @@ -1053,7 +1188,7 @@ cmd_newwm(int interactive, char *data) char *prog; if (data == NULL) - prog = get_input (MESSAGE_PROMPT_SWITCH_WM); + prog = get_input (MESSAGE_PROMPT_SWITCH_WM, trivial_completions); else prog = xstrdup (data); @@ -1442,6 +1577,9 @@ cmd_resize (int interactive, char *data) show_frame_message (" Resize frame "); nbytes = read_key (&c, &mod, buffer, sizeof (buffer)); + /* Convert the mask to be compatible with ratpoison. */ + mod = x11_mask_to_rp_mask (mod); + if (c == RESIZE_VGROW_KEY && mod == RESIZE_VGROW_MODIFIER) resize_frame_vertically (current_frame(), defaults.frame_resize_unit); else if (c == RESIZE_VSHRINK_KEY && mod == RESIZE_VSHRINK_MODIFIER) @@ -3193,6 +3331,30 @@ find_group (char *str) return group; } +struct list_head * +group_completions (char *str) +{ + struct list_head *list; + rp_group *cur; + + /* Initialize our list. */ + list = xmalloc (sizeof (struct list_head)); + INIT_LIST_HEAD (list); + + /* Grab all the group names. */ + list_for_each_entry (cur, &rp_groups, node) + { + struct sbuf *s; + + s = sbuf_new (0); + sbuf_copy (s, cur->name); + + list_add_tail (&s->node, list); + } + + return list; +} + char * cmd_gselect (int interactive, char *data) { @@ -3200,7 +3362,7 @@ cmd_gselect (int interactive, char *data) rp_group *g; if (data == NULL) - str = get_input (MESSAGE_PROMPT_SWITCH_TO_GROUP); + str = get_input (MESSAGE_PROMPT_SWITCH_TO_GROUP, group_completions); else str = xstrdup (data); diff --git a/src/completions.c b/src/completions.c new file mode 100644 index 0000000..970897d --- /dev/null +++ b/src/completions.c @@ -0,0 +1,104 @@ +#include <string.h> + +#include "ratpoison.h" +#include "completions.h" + +rp_completions * +completions_new (completion_fn list_fn) +{ + rp_completions *c; + + c = (rp_completions *) xmalloc (sizeof(rp_completions)); + + INIT_LIST_HEAD (&c->completion_list); + c->complete_fn = list_fn; + c->last_match = NULL; + c->partial = NULL; + c->virgin = 1; + + return c; +} + +void +completions_free (rp_completions *c) +{ + struct sbuf *cur; + struct list_head *tmp, *iter; + + /* Clear our list */ + list_for_each_safe_entry (cur, iter, tmp, &c->completion_list, node) + { + list_del (&cur->node); + sbuf_free (cur); + } + + /* Free the partial string. */ + if (c->partial) + free (c->partial); +} + +void +completions_assign (rp_completions *c, struct list_head *new_list) +{ + struct sbuf *cur; + struct list_head *tmp, *iter; + + /* Clear our list */ + list_for_each_safe_entry (cur, iter, tmp, &c->completion_list, node) + { + list_del (&cur->node); + sbuf_free (cur); + } + + /* splice the list into completion_list. Note that we SHOULDN'T free + new_list, because they share the same memory. */ + INIT_LIST_HEAD (&c->completion_list); + list_splice (new_list, &c->completion_list); + + list_first (c->last_match, &c->completion_list, node); +} + +void +completions_update (rp_completions *c, char *partial) +{ + struct list_head *new_list; + + new_list = c->complete_fn (partial); + + c->virgin = 0; + if (c->partial) + free (c->partial); + c->partial = xstrdup (partial); + + completions_assign (c, new_list); +} + +/* Return a completed string that starts with partial. */ +char * +completions_next_completion (rp_completions *c, char *partial) +{ + struct sbuf *cur; + + if (c->virgin) + completions_update (c, partial); + + if (c->last_match == NULL) + return NULL; + + /* search forward from our last match through the list looking for + another match. */ + for (cur = list_next_entry (c->last_match, &c->completion_list, node); + cur != c->last_match; + cur = list_next_entry (cur, &c->completion_list, node)) + { + if (str_comp (sbuf_get (cur), c->partial, strlen (c->partial))) + { + /* We found a match so update our last_match pointer and + return the string. */ + c->last_match = cur; + return sbuf_get (cur); + } + } + + return NULL; +} diff --git a/src/completions.h b/src/completions.h new file mode 100644 index 0000000..a0a262c --- /dev/null +++ b/src/completions.h @@ -0,0 +1,13 @@ +/* Function prototypes. + */ + +#ifndef _RATPOISON_COMPLETIONS_H +#define _RATPOISON_COMPLETIONS_H 1 + +char *completions_next_completion (rp_completions *c, char *partial); +void completions_update (rp_completions *c, char *partial); +void completions_assign (rp_completions *c, struct list_head *new_list); +rp_completions *completions_new (completion_fn list_fn); +void completions_free (rp_completions *c); + +#endif /* ! _RATPOISON_COMPLETIONS_H */ @@ -30,31 +30,31 @@ /* This is the abort key when typing input. */ #define INPUT_ABORT_KEY XK_g -#define INPUT_ABORT_MODIFIER ControlMask +#define INPUT_ABORT_MODIFIER RP_CONTROL_MASK /* This is the previous history entry key when typing input. */ #define INPUT_PREV_HISTORY_KEY XK_p -#define INPUT_PREV_HISTORY_MODIFIER ControlMask +#define INPUT_PREV_HISTORY_MODIFIER RP_CONTROL_MASK /* This is the next history entry key when typing input. */ #define INPUT_NEXT_HISTORY_KEY XK_n -#define INPUT_NEXT_HISTORY_MODIFIER ControlMask +#define INPUT_NEXT_HISTORY_MODIFIER RP_CONTROL_MASK /* Key used to enlarge frame vertically when in resize mode. */ #define RESIZE_VGROW_KEY XK_n -#define RESIZE_VGROW_MODIFIER ControlMask +#define RESIZE_VGROW_MODIFIER RP_CONTROL_MASK /* Key used to shrink frame vertically when in resize mode. */ #define RESIZE_VSHRINK_KEY XK_p -#define RESIZE_VSHRINK_MODIFIER ControlMask +#define RESIZE_VSHRINK_MODIFIER RP_CONTROL_MASK /* Key used to enlarge frame horizontally when in resize mode. */ #define RESIZE_HGROW_KEY XK_f -#define RESIZE_HGROW_MODIFIER ControlMask +#define RESIZE_HGROW_MODIFIER RP_CONTROL_MASK /* Key used to shrink frame horizontally when in resize mode. */ #define RESIZE_HSHRINK_KEY XK_b -#define RESIZE_HSHRINK_MODIFIER ControlMask +#define RESIZE_HSHRINK_MODIFIER RP_CONTROL_MASK /* Key used to shrink frame to fit it's current window. */ #define RESIZE_SHRINK_TO_WINDOW_KEY XK_s @@ -100,4 +100,13 @@ /* This is the name of the first group that is created. */ #define DEFAULT_GROUP_NAME "default" +/* Maximum allowed history size */ +#define MAX_HISTORY_SIZE 100 + +/* The default filename in which to store the history */ +#define HISTORY_FILE ".ratpoison_history" + +/* Use a visual bell in the input window */ +#define VISUAL_BELL 1 + #endif /* !_ _RATPOISON_CONF_H */ @@ -36,6 +36,8 @@ typedef struct rp_frame rp_frame; typedef struct rp_child_info rp_child_info; typedef struct rp_group rp_group; typedef struct rp_window_elem rp_window_elem; +typedef struct rp_completions rp_completions; +typedef struct rp_input_line rp_input_line; struct rp_frame { @@ -212,6 +214,8 @@ struct rp_defaults /* Pointer warping toggle. */ int warp; + + int history_size; }; /* Information about a child process. */ @@ -256,4 +260,40 @@ struct modifier_info unsigned int scroll_lock_mask; }; +typedef struct list_head *(*completion_fn)(char *string); + +struct rp_completions +{ + /* A pointer to the partial string that is being completed. We need + to store this so that the user can cycle through all possible + completions. */ + char *partial; + + /* A pointer to the string that was last matched string. Used to + keep track of where we are in the completion list. */ + struct sbuf *last_match; + + /* A list of sbuf's which are possible completions. */ + struct list_head completion_list; + + /* The function that generates the completions. */ + completion_fn complete_fn; + + /* virgin = 1 means no completions have been attempted on the input + string. */ + unsigned short int virgin; +}; + +struct rp_input_line +{ + char *buffer; + char *prompt; + char *saved; + int position; + int length; + int size; + rp_completions *compl; + Atom selection; +}; + #endif /* _RATPOISON_DATA_H */ diff --git a/src/editor.c b/src/editor.c new file mode 100644 index 0000000..f300bb3 --- /dev/null +++ b/src/editor.c @@ -0,0 +1,576 @@ +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> + +#include "ratpoison.h" + +/* bind functions */ +static edit_status editor_forward_char (rp_input_line *line); +static edit_status editor_backward_char (rp_input_line *line); +static edit_status editor_forward_word (rp_input_line *line); +static edit_status editor_backward_word (rp_input_line *line); +static edit_status editor_beginning_of_line (rp_input_line *line); +static edit_status editor_end_of_line (rp_input_line *line); +static edit_status editor_delete_char (rp_input_line *line); +static edit_status editor_backward_delete_char (rp_input_line *line); +static edit_status editor_kill_word (rp_input_line *line); +static edit_status editor_backward_kill_word (rp_input_line *line); +static edit_status editor_kill_line (rp_input_line *line); +static edit_status editor_paste_selection (rp_input_line *line); +static edit_status editor_abort (rp_input_line *line); +static edit_status editor_no_action (rp_input_line *line); +static edit_status editor_enter (rp_input_line *line); +static edit_status editor_history_previous (rp_input_line *line); +static edit_status editor_history_next (rp_input_line *line); +static edit_status editor_complete (rp_input_line *line); +static edit_status editor_backward_kill_line (rp_input_line *line); + +/* default edit action */ +edit_status editor_insert (rp_input_line *line, char *keysym_buf); + + +static char *saved_command = NULL; + +typedef struct edit_binding edit_binding; + +struct edit_binding +{ + struct rp_key key; + edit_status (*func)(rp_input_line *); +}; + +static edit_binding edit_bindings[] = + { {{XK_g, RP_CONTROL_MASK}, editor_abort}, + {{XK_Escape, 0}, editor_abort}, + {{XK_f, RP_CONTROL_MASK}, editor_forward_char}, + {{XK_Right, 0}, editor_forward_char}, + {{XK_b, RP_CONTROL_MASK}, editor_backward_char}, + {{XK_Left, 0}, editor_backward_char}, + {{XK_f, RP_META_MASK}, editor_forward_word}, + {{XK_b, RP_META_MASK}, editor_backward_word}, + {{XK_a, RP_CONTROL_MASK}, editor_beginning_of_line}, + {{XK_Home, 0}, editor_beginning_of_line}, + {{XK_e, RP_CONTROL_MASK}, editor_end_of_line}, + {{XK_End, 0}, editor_end_of_line}, + {{XK_d, RP_CONTROL_MASK}, editor_delete_char}, + {{XK_Delete, 0}, editor_delete_char}, + {{XK_BackSpace, 0}, editor_backward_delete_char}, + {{XK_BackSpace, RP_META_MASK}, editor_backward_kill_word}, + {{XK_d, RP_META_MASK}, editor_kill_word}, + {{XK_k, RP_CONTROL_MASK}, editor_kill_line}, + {{XK_u, RP_CONTROL_MASK}, editor_backward_kill_line}, + {{XK_y, RP_CONTROL_MASK}, editor_paste_selection}, + {{XK_Insert, 0}, editor_paste_selection}, + {{XK_p, RP_CONTROL_MASK}, editor_history_previous}, + {{XK_Up, 0}, editor_history_previous}, + {{XK_n, RP_CONTROL_MASK}, editor_history_next}, + {{XK_Down, 0}, editor_history_next}, + {{XK_Return, 0}, editor_enter}, + {{XK_KP_Enter, 0}, editor_enter}, + {{XK_Tab, 0}, editor_complete}, + { {0, 0}, 0} }; + +rp_input_line * +input_line_new (char *prompt, char *preinput, completion_fn fn) +{ + rp_input_line *line; + + line = xmalloc (sizeof (rp_input_line)); + line->prompt = prompt; + line->compl = completions_new (fn); + + /* Allocate some memory to start with */ + line->size = strlen (preinput) + 100; + line->buffer = (char *) xmalloc (line->size); + + /* load in the preinput */ + strcpy (line->buffer, preinput); + line->position = line->length = strlen (preinput); + + return line; +} + +void +input_line_free (rp_input_line *line) +{ +/* completions_free (line->compl); */ + free (line->buffer); + free (line); +} + +edit_status +execute_edit_action (rp_input_line *line, KeySym ch, unsigned int modifier, char *keysym_buf) +{ + struct edit_binding *binding = NULL; + int found_binding = 0; + edit_status status; + + for (binding = edit_bindings; binding->func; binding++) + { + if (ch == binding->key.sym && modifier == binding->key.state) + { + found_binding = 1; + break; + } + } + + if (found_binding) + status = binding->func (line); + else if (modifier) + status = editor_no_action (line); + else + status = editor_insert (line, keysym_buf); + + return status; +} + +static edit_status +editor_forward_char (rp_input_line *line) +{ + if (line->position < line->length) + { + line->position++; + return EDIT_MOVE; + } + else + return EDIT_NO_OP; + +} + +static edit_status +editor_backward_char (rp_input_line *line) +{ + if (line->position > 0) + { + line->position--; + return EDIT_MOVE; + } + else + return EDIT_NO_OP; +} + +static edit_status +editor_forward_word (rp_input_line *line) +{ + if (line->position < line->length) + { + for (; line->position < line->length && !isalnum (line->buffer[line->position]); line->position++); + for (; line->position < line->length && isalnum (line->buffer[line->position]); line->position++); + } + + return EDIT_MOVE; +} + +static edit_status +editor_backward_word (rp_input_line *line) +{ + if (line->position > 0) + { + for (; line->position > 0 && !isalnum (line->buffer[line->position - 1]); line->position--); + for (; line->position > 0 && isalnum (line->buffer[line->position - 1]); line->position--); + } + + return EDIT_MOVE; +} + +static edit_status +editor_beginning_of_line (rp_input_line *line) +{ + if (line->position > 0) + line->position = 0; + + return EDIT_MOVE; +} + +static edit_status +editor_end_of_line (rp_input_line *line) +{ + if (line->position < line->length) + line->position = line->length; + + return EDIT_MOVE; +} + +static edit_status +editor_delete_char (rp_input_line *line) +{ + int i; + + if (line->position < line->length) + { + for (i = line->position; i < line->length; i++) + line->buffer[i] = line->buffer[i + 1]; + + line->length--; + return EDIT_DELETE; + } + else + return EDIT_NO_OP; +} + +static edit_status +editor_backward_delete_char (rp_input_line *line) +{ + int i; + + if (line->position > 0) + { + for (i = line->position - 1; i < line->length; i++) + line->buffer[i] = line->buffer[i + 1]; + + line->position--; + line->length--; + return EDIT_DELETE; + } + else + return EDIT_NO_OP; +} + +static edit_status +editor_kill_word (rp_input_line *line) +{ + int i, diff; + + if (line->position < line->length) + { + for (i = line->position; i < line->length && !isalnum (line->buffer[i]); i++); + for (; i < line->length && isalnum (line->buffer[i]); i++); + + diff = i - line->position; + + for (i = line->position; i <= line->length - diff; i++) + line->buffer[i] = line->buffer[i + diff]; + + line->length -= diff; + } + + return EDIT_DELETE; +} + +static edit_status +editor_backward_kill_word (rp_input_line *line) +{ + int i, diff; + + if (line->position > 0) + { + for (i = line->position; i > 0 && !isalnum (line->buffer[i - 1]); i--); + for (; i > 0 && isalnum (line->buffer[i - 1]); i--); + + diff = line->position - i; + + line->position = i; + + for (; i <= line->length - diff; i++) + line->buffer[i] = line->buffer[i + diff]; + + line->length -= diff; + } + + return EDIT_DELETE; +} + +static edit_status +editor_kill_line (rp_input_line *line) +{ + if (line->position < line->length) + { + line->length = line->position; + line->buffer[line->length] = 0; + } + + return EDIT_DELETE; +} + +static edit_status +editor_backward_kill_line (rp_input_line *line) +{ + int i; + + if (line->position <= 0) + return EDIT_NO_OP; + + for (i = line->position; i<= line->length; i++) + line->buffer[i - line->position] = line->buffer[i]; + + line->length -= line->position; + line->position = 0; + + return EDIT_DELETE; +} + +static edit_status +editor_history_previous (rp_input_line *line) +{ + char *entry = history_previous (); + + if (entry) + { + if (!saved_command) + { + line->buffer[line->length] = '\0'; + saved_command = xstrdup (line->buffer); + PRINT_DEBUG (("saved current command line: \'%s\'\n", saved_command)); + } + + free (line->buffer); + line->buffer = xstrdup (entry); + line->length = strlen (line->buffer); + line->size = line->length + 1; + line->position = line->length; + PRINT_DEBUG (("entry: \'%s\'\n", line->buffer)); + } + else + { + PRINT_DEBUG (("- do nothing -")); + return EDIT_NO_OP; + } + + return EDIT_INSERT; +} + +static edit_status +editor_history_next (rp_input_line *line) +{ + char *entry = history_next (); + + if (entry) + { + free (line->buffer); + line->buffer = xstrdup (entry); + PRINT_DEBUG (("entry: \'%s\'\n", line->buffer)); + } + else if (saved_command) + { + free (line->buffer); + line->buffer = saved_command; + saved_command = NULL; + PRINT_DEBUG (("restored command line: \'%s\'\n", line->buffer)); + } + else + { + PRINT_DEBUG (("- do nothing -")); + return EDIT_NO_OP; + } + + line->length = strlen (line->buffer); + line->size = line->length + 1; + line->position = line->length; + + return EDIT_INSERT; +} + +static edit_status +editor_abort (rp_input_line *line) +{ + return EDIT_ABORT; +} + +static edit_status +editor_no_action (rp_input_line *line) +{ + return EDIT_NO_OP; +} + +static edit_status +editor_insert (rp_input_line *line, char *keysym_buf) +{ + int nbytes; + int i; + + PRINT_DEBUG (("keysym_buf: '%s'\n", keysym_buf)); + + nbytes = strlen (keysym_buf); + if (line->length + nbytes > line->size - 1) + { + line->size += nbytes + 100; + line->buffer = xrealloc (line->buffer, line->size); + } + + for (i = line->length + nbytes; i > line->position; i--) + line->buffer[i] = line->buffer[i - nbytes]; + + strncpy (&line->buffer[line->position], keysym_buf, nbytes); + + line->length += nbytes; + line->position += nbytes; + + PRINT_DEBUG (("line->buffer: '%s'\n", line->buffer)); + + return EDIT_INSERT; +} + +static edit_status +editor_enter (rp_input_line *line) +{ + int result; + char *expansion; + + line->buffer[line->length] = '\0'; + result = history_expand_line (line->buffer, &expansion); + + PRINT_DEBUG (("History Expansion - result: %d\n", result)); + PRINT_DEBUG (("History Expansion - expansion: \'%s\'\n", expansion)); + + if (result == -1 || result == 2) + { + marked_message_printf (0, 0, " %s ", expansion); + free (expansion); + line->buffer = NULL; + } + else /* result == 0 || result == 1 */ + { + history_add (expansion); + line->buffer = expansion; + } + + return EDIT_DONE; +} + +static edit_status +paste_cut_buffer (rp_input_line *line) +{ + int nbytes; + char *data; + edit_status status; + + PRINT_DEBUG (("trying the cut buffer\n")); + + data = XFetchBytes (dpy, &nbytes); + + if (data) + { +/* status = editor_insert (line, data, nbytes); */ + status = editor_insert (line, data); + XFree (data); + } + else + { + status = EDIT_NO_OP; + } + + return status; +} + +static edit_status +paste_primary_selection (rp_input_line *line) +{ + Atom actual_type; + rp_screen *s = current_screen (); + int actual_format; + unsigned long nitems; + unsigned long offset; + unsigned long bytes_after; + unsigned char *data; + + if (XGetWindowProperty (dpy, s->input_window, rp_selection, 0, 0, False, XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data) == Success) + { + if (data) + { + XFree (data); + data = NULL; + } + + PRINT_DEBUG (("actual_type = %ld, actual_format = %d, bytes_after = %ld\n", actual_type, actual_format, bytes_after)); + + if (actual_type != XA_STRING || actual_format != 8) + { + PRINT_DEBUG (("selection data is invalid\n")); + if (data) + XFree (data); + return EDIT_NO_OP; + } + + offset = 0; + + while (bytes_after > 0) + { + if (XGetWindowProperty (dpy, s->input_window, rp_selection, offset / 4, bytes_after / 4 + 1, False, XA_STRING, &actual_type, &actual_format, &nitems, &bytes_after, &data) != Success) + break; + + PRINT_DEBUG (("bytes_after = %ld, nitems = %ld, data = '%s'\n", bytes_after, nitems, data)); + + nitems *= actual_format / 8; + offset += nitems; + +/* editor_insert (line, data, nitems); */ + editor_insert (line, data); + + PRINT_DEBUG (("bytes_after = %ld, nitems = %ld, data = '%s'\n", bytes_after, nitems, data)); + + XFree (data); + } + } + + /* notify the owner that the data has been transferred */ + XDeleteProperty(dpy, s->input_window, rp_selection); + + return EDIT_INSERT; +} + +static edit_status +editor_paste_selection (rp_input_line *line) +{ + Atom property; + XEvent ev; + rp_screen *s = current_screen (); + int loops = 1000; + + /* be a good icccm citizen */ + XDeleteProperty (dpy, s->input_window, rp_selection); + /* TODO: we shouldn't use CurrentTime here, use the time of the XKeyEvent, should we fake it? */ + XConvertSelection (dpy, XA_PRIMARY, XA_STRING, rp_selection, s->input_window, CurrentTime); + + while (!XCheckTypedWindowEvent (dpy, s->input_window, SelectionNotify, &ev)) + { + if (loops == 0) + { + PRINT_ERROR (("selection request timed out\n")); + return EDIT_NO_OP; + } + usleep (10000); + loops--; + } + + PRINT_DEBUG (("SelectionNotify event\n")); + + property = ev.xselection.property; + + if (property != None) + return paste_primary_selection (line); + else + return paste_cut_buffer (line); +} + +static edit_status +editor_complete (rp_input_line *line) +{ + char *tmp; + char *s; + + /* Create our partial string that will be used for completion. It is + the characters up to the position of the cursor. */ + tmp = xmalloc ((line->position + 1) * sizeof (char)); + strncpy (tmp, line->buffer, line->position); + tmp[line->position] = '\0'; + + /* We don't need to free s because it's a string from the completion + list. */ + s = completions_next_completion (line->compl, tmp); + free (tmp); + + if (s == NULL) + return EDIT_NO_OP; + + /* Insert the completion. */ + editor_backward_kill_line (line); + editor_insert (line, s); + + return EDIT_COMPLETE; +} diff --git a/src/editor.h b/src/editor.h new file mode 100644 index 0000000..fdcc08b --- /dev/null +++ b/src/editor.h @@ -0,0 +1,24 @@ +#ifndef _RATPOISON_EDITOR_H +#define _RATPOISON_EDITOR_H 1 + +typedef enum edit_status edit_status; + +enum +edit_status +{ + EDIT_INSERT, + EDIT_DELETE, + EDIT_MOVE, + EDIT_COMPLETE, + EDIT_ABORT, + EDIT_DONE, + EDIT_NO_OP +}; + +/* Input line functions */ +rp_input_line *input_line_new (char *prompt, char *preinput, completion_fn fn); +void input_line_free (rp_input_line *line); + +edit_status execute_edit_action (rp_input_line *line, KeySym ch, unsigned int modifier, char *keysym_buf); + +#endif /* ! _RATPOISON_EDITOR_H */ diff --git a/src/globals.c b/src/globals.c index 1aef12e..319ac91 100644 --- a/src/globals.c +++ b/src/globals.c @@ -39,6 +39,7 @@ Atom wm_colormaps; Atom rp_command; Atom rp_command_request; Atom rp_command_result; +Atom rp_selection; int rp_current_screen; rp_screen *screens; diff --git a/src/globals.h b/src/globals.h index 4343b80..57b6df3 100644 --- a/src/globals.h +++ b/src/globals.h @@ -24,6 +24,7 @@ #include "data.h" #define FONT_HEIGHT(f) ((f)->max_bounds.ascent + (f)->max_bounds.descent) +#define MAX_FONT_WIDTH(f) ((f)->max_bounds.width) #define WIN_EVENTS (StructureNotifyMask | PropertyChangeMask | ColormapChangeMask | FocusChangeMask) /* EMPTY is used when a frame doesn't contain a window, or a window @@ -75,6 +76,7 @@ extern Display *dpy; extern Atom rp_command; extern Atom rp_command_request; extern Atom rp_command_result; +extern Atom rp_selection; extern Atom wm_name; extern Atom wm_state; diff --git a/src/input.c b/src/input.c index 80d17dc..31c901d 100644 --- a/src/input.c +++ b/src/input.c @@ -22,16 +22,13 @@ #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <unistd.h> #include <X11/Xlib.h> #include <X11/keysym.h> #include <X11/Xutil.h> #include "ratpoison.h" -/* Variables to keep track of input history. */ -static char *input_history[INPUT_MAX_HISTORY]; -static int input_num_history_entries = 0; - /* Convert an X11 modifier mask to the rp modifier mask equivalent, as best it can (the X server may not have a hyper key defined, for instance). */ @@ -275,73 +272,119 @@ read_key (KeySym *keysym, unsigned int *modifiers, char *keysym_name, int len) } static void -update_input_window (rp_screen *s, char *prompt, char *input, int input_len) +update_input_window (rp_screen *s, rp_input_line *line) { - int prompt_width = XTextWidth (defaults.font, prompt, strlen (prompt)); - int input_width = XTextWidth (defaults.font, input, input_len); - int width, height; - - width = defaults.bar_x_padding * 2 + prompt_width + input_width; + int prompt_width = XTextWidth (defaults.font, line->prompt, strlen (line->prompt)); + int input_width = XTextWidth (defaults.font, line->buffer, line->length); + int total_width; + GC lgc; + XGCValues gv; + int height; + + total_width = defaults.bar_x_padding * 2 + prompt_width + input_width + MAX_FONT_WIDTH (defaults.font); height = (FONT_HEIGHT (defaults.font) + defaults.bar_y_padding * 2); - if (width < defaults.input_window_size + prompt_width) + if (total_width < defaults.input_window_size + prompt_width) { - width = defaults.input_window_size + prompt_width; + total_width = defaults.input_window_size + prompt_width; } XMoveResizeWindow (dpy, s->input_window, - bar_x (s, width), bar_y (s, height), width, height); + bar_x (s, total_width), bar_y (s, height), total_width, + (FONT_HEIGHT (defaults.font) + defaults.bar_y_padding * 2)); XClearWindow (dpy, s->input_window); XSync (dpy, False); XDrawString (dpy, s->input_window, s->normal_gc, - defaults.bar_x_padding, - defaults.bar_y_padding + defaults.font->max_bounds.ascent, prompt, - strlen (prompt)); - + defaults.bar_x_padding, + defaults.bar_y_padding + defaults.font->max_bounds.ascent, + line->prompt, + strlen (line->prompt)); + XDrawString (dpy, s->input_window, s->normal_gc, - defaults.bar_x_padding + prompt_width, - defaults.bar_y_padding + defaults.font->max_bounds.ascent, input, - input_len); - - /* Draw a cheap-o cursor. */ - XDrawLine (dpy, s->input_window, s->normal_gc, - defaults.bar_x_padding + prompt_width + input_width + 2, - defaults.bar_y_padding + 1, - defaults.bar_x_padding + prompt_width + input_width + 2, - defaults.bar_y_padding + FONT_HEIGHT (defaults.font) - 1); + defaults.bar_x_padding + prompt_width, + defaults.bar_y_padding + defaults.font->max_bounds.ascent, + line->buffer, + line->length); + + gv.function = GXxor; + gv.foreground = s->fg_color ^ s->bg_color; + lgc = XCreateGC (dpy, s->input_window, GCFunction | GCForeground, &gv); + + /* Draw a cheap-o cursor - MkII */ + XFillRectangle (dpy, s->input_window, lgc, + defaults.bar_x_padding + prompt_width + XTextWidth (defaults.font, line->buffer, line->position), + defaults.bar_y_padding, + XTextWidth (defaults.font, &line->buffer[line->position], 1), + FONT_HEIGHT (defaults.font)); + + XFlush (dpy); + XFreeGC (dpy, lgc); +} + +void +ring_bell () +{ +#ifdef VISUAL_BELL + GC lgc; + XGCValues gv; + XWindowAttributes attr; + rp_screen *s = current_screen (); + + XGetWindowAttributes (dpy, s->input_window, &attr); + + gv.function = GXxor; + gv.foreground = s->fg_color ^ s->bg_color; + lgc = XCreateGC (dpy, s->input_window, GCFunction | GCForeground, &gv); + + XFillRectangle (dpy, s->input_window, lgc, 0, 0, attr.width, attr.height); + XFlush (dpy); + +#ifdef HAVE_USLEEP + usleep (15000); +#else + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 15000; + select (0, NULL, NULL, NULL, &tv); + } +#endif + XFillRectangle (dpy, s->input_window, lgc, 0, 0, attr.width, attr.height); + XFlush (dpy); + XFreeGC (dpy, lgc); +#else + XBell (dpy, 0); +#endif } char * -get_input (char *prompt) +get_input (char *prompt, completion_fn fn) { - return get_more_input (prompt, ""); + return get_more_input (prompt, "", fn); } char * -get_more_input (char *prompt, char *preinput) +get_more_input (char *prompt, char *preinput, + completion_fn compl_fn) { /* Emacs 21 uses a 513 byte string to store the keysym name. */ char keysym_buf[513]; int keysym_bufsize = sizeof (keysym_buf); int nbytes; rp_screen *s = current_screen (); - int cur_len = 0; /* Current length of the string. */ - int allocated_len=100; /* The amount of memory we allocated for str */ KeySym ch; unsigned int modifier; int revert; Window fwin; - char *str; - int history_index = input_num_history_entries; - - /* Allocate some memory to start with. */ - str = (char *) xmalloc ( allocated_len ); + rp_input_line *line; + char *final_input; + edit_status status; - /* load in the preinput */ - strcpy (str, preinput); - cur_len = strlen (preinput); + /* Create our line structure */ + line = input_line_new (prompt, preinput, compl_fn); /* We don't want to draw overtop of the program bar. */ hide_bar (s); @@ -351,117 +394,51 @@ get_more_input (char *prompt, char *preinput) XClearWindow (dpy, s->input_window); XSync (dpy, False); - update_input_window (s, prompt, str, cur_len); + update_input_window (s, line); XGetInputFocus (dpy, &fwin, &revert); XSetInputFocus (dpy, s->input_window, RevertToPointerRoot, CurrentTime); /* XSync (dpy, False); */ - - nbytes = read_key (&ch, &modifier, keysym_buf, keysym_bufsize); - while (ch != XK_Return) + for (;;) { - PRINT_DEBUG (("key %ld\n", ch)); - if (ch == XK_BackSpace) - { - if (cur_len > 0) cur_len--; - update_input_window(s, prompt, str, cur_len); - } - else if (ch == INPUT_PREV_HISTORY_KEY - && modifier == INPUT_PREV_HISTORY_MODIFIER) - { - /* Cycle through the history. */ - if (input_num_history_entries > 0) - { - history_index--; - if (history_index < 0) - { - history_index = input_num_history_entries - 1; - } - - free (str); - str = xstrdup (input_history[history_index]); - allocated_len = strlen (str) + 1; - cur_len = allocated_len - 1; - - update_input_window (s, prompt, str, cur_len); - } - } - else if (ch == INPUT_NEXT_HISTORY_KEY - && modifier == INPUT_NEXT_HISTORY_MODIFIER) - { - /* Cycle through the history. */ - if (input_num_history_entries > 0) - { - history_index++; - if (history_index >= input_num_history_entries) - { - history_index = 0; - } - - free (str); - str = xstrdup (input_history[history_index]); - allocated_len = strlen (str) + 1; - cur_len = allocated_len - 1; - - update_input_window (s, prompt, str, cur_len); - } - } - else if (ch == INPUT_ABORT_KEY && modifier == INPUT_ABORT_MODIFIER) - { - /* User aborted. */ - free (str); - XSetInputFocus (dpy, fwin, RevertToPointerRoot, CurrentTime); - XUnmapWindow (dpy, s->input_window); - return NULL; - } - else - { - if (cur_len + nbytes > allocated_len - 1) - { - allocated_len += nbytes + 100; - str = xrealloc ( str, allocated_len ); - } - - strncpy (&str[cur_len], keysym_buf, nbytes); -/* str[cur_len] = ch; */ - cur_len+=nbytes; - - update_input_window(s, prompt, str, cur_len); - } - nbytes = read_key (&ch, &modifier, keysym_buf, keysym_bufsize); + modifier = x11_mask_to_rp_mask (modifier); + PRINT_DEBUG (("ch = %ld, modifier = %d, keysym_buf = %s, keysym_bufsize = %d\n", + ch, modifier, keysym_buf, keysym_bufsize)); + status = execute_edit_action (line, ch, modifier, keysym_buf); + + if (status == EDIT_DELETE || status == EDIT_INSERT || status == EDIT_MOVE + || status == EDIT_COMPLETE) + { + /* If the text changed (and we didn't just complete + something) then set the virgin bit. */ + if (status != EDIT_COMPLETE) + line->compl->virgin = 1; + /* In all cases, we need to redisplay the input string. */ + update_input_window (s, line); + } + else if (status == EDIT_NO_OP) + { + ring_bell (); + } + else if (status == EDIT_ABORT) + { + final_input = NULL; + break; + } + else if (status == EDIT_DONE) + { + final_input = xstrdup (line->buffer); + break; + } } - str[cur_len] = 0; - - /* Push the history entries down. */ - if (input_num_history_entries >= INPUT_MAX_HISTORY) - { - int i; - free (input_history[0]); - for (i=0; i<INPUT_MAX_HISTORY-1; i++) - { - input_history[i] = input_history[i+1]; - } - - input_num_history_entries--; - } - - /* Store the string in the history. */ - input_history[input_num_history_entries] = xstrdup (str); - input_num_history_entries++; + /* Clean up our line structure */ + input_line_free (line); XSetInputFocus (dpy, fwin, RevertToPointerRoot, CurrentTime); XUnmapWindow (dpy, s->input_window); - return str; -} -void -free_history () -{ - int i; - - for (i=0; i<input_num_history_entries; i++) - free (input_history[i]); -} + return final_input; +} diff --git a/src/input.h b/src/input.h index bc074a9..f8703e3 100644 --- a/src/input.h +++ b/src/input.h @@ -24,13 +24,14 @@ char *keysym_to_string (KeySym keysym, unsigned int modifier); int cook_keycode (XKeyEvent *ev, KeySym *keysym, unsigned int *mod, char *keysym_name, int len, int ignore_bad_mods); -char *get_input (char *prompt); -char *get_more_input (char *prompt, char *preinput); +char *get_input (char *prompt, completion_fn fn); +char *get_more_input (char *prompt, char *preinput, completion_fn fn); int read_key (KeySym *keysym, unsigned int *modifiers, char *keysym_name, int len); unsigned int x11_mask_to_rp_mask (unsigned int mask); unsigned int rp_mask_to_x11_mask (unsigned int mask); void update_modifier_map (); void grab_key (int keycode, unsigned int modifiers, Window grab_window); -void free_history (); + +void ring_bell (); #endif /* ! _RATPOISON_INPUT_H */ diff --git a/src/linkedlist.h b/src/linkedlist.h index 1314f69..fc1b8db 100644 --- a/src/linkedlist.h +++ b/src/linkedlist.h @@ -171,3 +171,12 @@ void prefetch(const void *x); prefetch(pos->member.prev)) #endif + + +/* Return the first element in the list. */ +#define list_first(first, head, member) \ +{ \ + first = list_entry((head)->next, typeof(*first), member); \ + if (&first->member == (head)) \ + first = NULL; \ +} @@ -79,7 +79,6 @@ xrealloc (void *ptr, size_t size) register void *value = realloc (ptr, size); if (value == 0) fatal ("Virtual memory exhausted"); - PRINT_DEBUG (("realloc: %d\n", size)); return value; } @@ -468,6 +467,8 @@ init_defaults () defaults.startup_message = 1; defaults.warp = 1; defaults.window_list_style = STYLE_ROW; + + defaults.history_size = 20; } int @@ -536,6 +537,7 @@ main (int argc, char *argv[]) rp_command = XInternAtom (dpy, "RP_COMMAND", False); rp_command_request = XInternAtom (dpy, "RP_COMMAND_REQUEST", False); rp_command_result = XInternAtom (dpy, "RP_COMMAND_RESULT", False); + rp_selection = XInternAtom (dpy, "RP_SELECTION", False); if (cmd_count > 0) { @@ -613,6 +615,7 @@ main (int argc, char *argv[]) init_frame_lists (); update_modifier_map (); initialize_default_keybindings (); + history_load (); /* Scan for windows */ if (screen_arg) @@ -766,10 +769,12 @@ clean_up () { int i; + history_save (); + free_keybindings (); free_aliases (); free_bar (); - free_history (); +/* free_history (); */ free_window_stuff (); diff --git a/src/ratpoison.h b/src/ratpoison.h index 5646d1f..b643aab 100644 --- a/src/ratpoison.h +++ b/src/ratpoison.h @@ -72,6 +72,9 @@ extern XGCValues gv; #include "frame.h" #include "screen.h" #include "group.h" +#include "editor.h" +#include "history.h" +#include "completions.h" void clean_up (); rp_screen *find_screen (Window w); @@ -30,6 +30,9 @@ sbuf char *data; size_t len; size_t maxsz; + + /* sbuf can exist in a list. */ + struct list_head node; }; struct sbuf *sbuf_new (size_t initsz); |