diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2012-11-10 20:08:51 +0100 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2012-11-10 20:08:51 +0100 |
commit | f120e3e367f01f006bd5ddfd9f1ac018273e75e3 (patch) | |
tree | 65e51cfbc6bc05eb25d331bcb86ed642a1af6bad | |
parent | 5aa548cd63edb8ba12c656300a2a5f0ff62a7b36 (diff) | |
download | sciteco-f120e3e367f01f006bd5ddfd9f1ac018273e75e3.tar.gz |
filename autocompletion using <CTRL/T> and <TAB>
* <TAB> autocompletion only in specified states
* GtkInfoPopup widget to display possible completions, written using Gob2
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Makefile | 12 | ||||
-rw-r--r-- | cmdline.cpp | 140 | ||||
-rw-r--r-- | gtk-info-popup.gob | 135 | ||||
-rw-r--r-- | main.cpp | 5 | ||||
-rw-r--r-- | parser.cpp | 5 | ||||
-rw-r--r-- | parser.h | 3 | ||||
-rw-r--r-- | qbuffers.cpp | 9 | ||||
-rw-r--r-- | qbuffers.h | 10 | ||||
-rw-r--r-- | sciteco.h | 4 |
10 files changed, 313 insertions, 13 deletions
@@ -1,3 +1,6 @@ +# Generated code +gtk-info-popup*.[ch] + *.o sciteco @@ -1,3 +1,5 @@ +GOB2:=gob2 + GTK_CFLAGS:=$(shell pkg-config --cflags gtk+-2.0) GTK_LDFLAGS:=$(shell pkg-config --libs gtk+-2.0) @@ -5,14 +7,22 @@ SCI_CFLAGS:=-I../scintilla/include -DGTK -DSCI_LEXER SCI_LDFLAGS:=../scintilla/bin/scintilla.a CPPFLAGS:= +CFLAGS:=-Wall -std=c99 -g -O0 $(GTK_CFLAGS) $(SCI_CFLAGS) CXXFLAGS:=-Wall -g -O0 $(GTK_CFLAGS) $(SCI_CFLAGS) LDFLAGS:=$(GTK_LDFLAGS) $(SCI_LDFLAGS) all : sciteco sciteco : main.o cmdline.o undo.o expressions.o qbuffers.o \ - parser.o goto.o + parser.o goto.o \ + gtk-info-popup.o $(CXX) -o $@ $^ $(LDFLAGS) +main.o : gtk-info-popup.h + +%.c %.h %-private.h : %.gob + $(GOB2) $< + clean: $(RM) sciteco *.o + $(RM) gtk-info-popup*.[ch] diff --git a/cmdline.cpp b/cmdline.cpp index 152adc5..9f3a438 100644 --- a/cmdline.cpp +++ b/cmdline.cpp @@ -3,6 +3,7 @@ #include <glib.h> #include <glib/gprintf.h> +#include <glib/gstdio.h> #include "sciteco.h" #include "parser.h" @@ -11,6 +12,11 @@ static inline const gchar *process_edit_cmd(gchar key); static gchar *macro_echo(const gchar *macro, const gchar *prefix = ""); +static gchar *filename_complete(const gchar *filename, gchar completed = ' '); + +static const gchar *last_occurrence(const gchar *str, + const gchar *chars = " \t\v\r\n\f<>,;@"); +static inline gboolean filename_is_dir(const gchar *filename); gchar *cmdline = NULL; @@ -24,6 +30,14 @@ cmdline_keypress(gchar key) gchar *echo; /* + * Cleanup messages, popups, etc... + */ + if (gtk_widget_get_visible(GTK_WIDGET(filename_popup))) { + gtk_widget_hide(GTK_WIDGET(filename_popup)); + gtk_info_popup_clear(filename_popup); + } + + /* * Process immediate editing commands */ insert = process_edit_cmd(key); @@ -74,6 +88,28 @@ process_edit_cmd(gchar key) } break; + case CTL_KEY('T'): { + const gchar *filename = cmdline ? last_occurrence(cmdline) + 1 + : NULL; + gchar *new_chars = filename_complete(filename); + if (new_chars) + g_stpcpy(insert, new_chars); + g_free(new_chars); + break; + } + + case '\t': + if (current_state == &states.file) { + gchar *new_chars = filename_complete(strings[0], escape_char); + if (new_chars) + g_stpcpy(insert, new_chars); + g_free(new_chars); + break; + } + insert[0] = key; + insert[1] = '\0'; + break; + case '\x1B': if (cmdline && cmdline[cmdline_len - 1] == '\x1B') { if (quit_requested) { @@ -135,3 +171,107 @@ macro_echo(const gchar *macro, const gchar *prefix) return result; } + +static gchar * +filename_complete(const gchar *filename, gchar completed) +{ + gchar *dirname, *basename; + GDir *dir; + GList *files = NULL, *matching; + GCompletion *completion; + gchar *new_prefix; + gchar *insert = NULL; + + if (!filename) + filename = ""; + + if (is_glob_pattern(filename)) + return NULL; + + dirname = g_path_get_dirname(filename); + dir = g_dir_open(dirname, 0, NULL); + if (!dir) { + g_free(dirname); + return NULL; + } + if (*dirname != *filename) + *dirname = '\0'; + + while ((basename = (gchar *)g_dir_read_name(dir))) { + gchar *filename = g_build_filename(dirname, basename, NULL); + + if (g_file_test(filename, G_FILE_TEST_IS_DIR)) { + gchar *new_filename; + new_filename = g_strconcat(filename, + G_DIR_SEPARATOR_S, NULL); + g_free(filename); + filename = new_filename; + } + + files = g_list_prepend(files, filename); + } + + g_free(dirname); + g_dir_close(dir); + + completion = g_completion_new(NULL); + g_completion_add_items(completion, files); + + matching = g_completion_complete(completion, filename, &new_prefix); + if (new_prefix && strlen(new_prefix) > strlen(filename)) + insert = g_strdup(new_prefix + strlen(filename)); + g_free(new_prefix); + + if (!insert && g_list_length(matching) > 1) { + matching = g_list_sort(matching, (GCompareFunc)g_strcmp0); + + for (GList *file = g_list_first(matching); + file != NULL; + file = g_list_next(file)) { + GtkInfoPopupFileType type; + + type = filename_is_dir((gchar *)file->data) + ? GTK_INFO_POPUP_DIRECTORY + : GTK_INFO_POPUP_FILE; + gtk_info_popup_add_filename(filename_popup, + type, (gchar *)file->data); + } + + gtk_widget_show(GTK_WIDGET(filename_popup)); + } else if (g_list_length(matching) == 1 && + !filename_is_dir((gchar *)g_list_first(matching)->data)) { + gchar *new_insert; + + new_insert = g_strconcat(insert ? : "", + (gchar []){completed, '\0'}, NULL); + g_free(insert); + insert = new_insert; + } + + g_completion_free(completion); + + for (GList *file = g_list_first(files); file; file = g_list_next(file)) + g_free(file->data); + g_list_free(files); + + return insert; +} + +/* + * Auxiliary functions + */ + +static const gchar * +last_occurrence(const gchar *str, const gchar *chars) +{ + while (*chars) + str = strrchr(str, *chars++) ? : str; + + return str; +} + +static inline gboolean +filename_is_dir(const gchar *filename) +{ + return g_str_has_suffix(filename, G_DIR_SEPARATOR_S); +} diff --git a/gtk-info-popup.gob b/gtk-info-popup.gob new file mode 100644 index 0000000..65d13e8 --- /dev/null +++ b/gtk-info-popup.gob @@ -0,0 +1,135 @@ +requires 2.0.16 + +%privateheader{ +#include <gdk/gdk.h> +%} + +%h{ +#include <gtk/gtk.h> +%} + +enum GTK_INFO_POPUP { + FILE, + DIRECTORY +} Gtk:Info:Popup:File:Type; + +class Gtk:Info:Popup from Gtk:Window { + private GtkWidget *parent; + + init(self) + { + GtkWidget *vbox; + + //gtk_window_set_gravity(GTK_WINDOW(self), GDK_GRAVITY_SOUTH_WEST); + gtk_container_set_border_width(GTK_CONTAINER(self), 10); + + vbox = gtk_vbox_new(FALSE, 5); + gtk_container_set_resize_mode(GTK_CONTAINER(vbox), GTK_RESIZE_PARENT); + gtk_container_add(GTK_CONTAINER(self), vbox); + gtk_widget_show(vbox); + } + + public GtkWidget * + new(Gtk:Widget *parent) + { + Self *widget; + GtkWidget *toplevel; + + widget = GET_NEW_VARG("type", GTK_WINDOW_POPUP, NULL); + widget->_priv->parent = parent; + + g_signal_connect(parent, "size-allocate", + G_CALLBACK(self_size_allocate_cb), widget); + toplevel = gtk_widget_get_toplevel(parent); + g_signal_connect(toplevel, "configure-event", + G_CALLBACK(self_configure_cb), widget); + + return GTK_WIDGET(widget); + } + + private void + position(self) + { + GdkWindow *window = gtk_widget_get_window(self->_priv->parent); + gint x, y; + gint w, h; + + if (!window) + return; + + gdk_window_get_origin(window, &x, &y); + gtk_window_get_size(GTK_WINDOW(self), &w, &h); + + gtk_window_move(GTK_WINDOW(self), x, y - h); + } + + private void + size_allocate_cb(Gtk:Widget *parent, + Gtk:Allocation *alloc, gpointer user_data) + { + self_position(SELF(user_data)); + } + + private gboolean + configure_cb(Gtk:Widget *toplevel, + Gdk:Event:Configure *event, gpointer user_data) + { + self_position(SELF(user_data)); + return FALSE; + } + + override (Gtk:Widget) void + show(Gtk:Widget *self) + { + GtkRequisition req; + + gtk_widget_size_request(self, &req); + gtk_window_resize(GTK_WINDOW(self), req.width, req.height); + self_position(SELF(self)); + + PARENT_HANDLER(self); + } + + public void + add_filename(self, Gtk:Info:Popup:File:Type type, const gchar *filename) + { + GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); + + const gchar *stock_id; + GtkWidget *hbox; + GtkWidget *image, *label; + + switch (type) { + case GTK_INFO_POPUP_FILE: + stock_id = GTK_STOCK_FILE; + break; + case GTK_INFO_POPUP_DIRECTORY: + stock_id = GTK_STOCK_DIRECTORY; + break; + } + + hbox = gtk_hbox_new(FALSE, 5); + image = gtk_image_new_from_stock(stock_id, GTK_ICON_SIZE_MENU); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + label = gtk_label_new(filename); + gtk_misc_set_alignment(GTK_MISC(label), 0., 0.5); + gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); + + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_widget_show_all(hbox); + } + + public void + clear(self) + { + GtkWidget *vbox = gtk_bin_get_child(GTK_BIN(self)); + GList *children; + + children = gtk_container_get_children(GTK_CONTAINER(vbox)); + for (GList *cur = g_list_first(children); + cur != NULL; + cur = g_list_next(cur)) + gtk_widget_destroy(GTK_WIDGET(cur->data)); + g_list_free(children); + } +} @@ -7,6 +7,7 @@ #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> +#include "gtk-info-popup.h" #include <Scintilla.h> #include <SciLexer.h> @@ -21,6 +22,8 @@ static GtkWidget *editor_widget; static GtkWidget *cmdline_widget; static GtkWidget *info_widget, *message_widget; +GtkInfoPopup *filename_popup; + void cmdline_display(const gchar *cmdline_str) { @@ -134,6 +137,8 @@ main(int argc, char **argv) gtk_container_add(GTK_CONTAINER(window), vbox); + filename_popup = GTK_INFO_POPUP(gtk_info_popup_new(cmdline_widget)); + editor_msg(SCI_SETFOCUS, 1); editor_msg(SCI_SETCARETSTYLE, 2); editor_msg(SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Courier"); @@ -12,7 +12,7 @@ gint macro_pc = 0; States states; -static State *current_state = &states.start; +State *current_state = &states.start; static struct { bool colon; @@ -27,8 +27,7 @@ static bool skip_else = false; static gint nest_level = 0; gchar *strings[2] = {NULL, NULL}; - -static gchar escape_char = '\x1B'; +gchar escape_char = '\x1B'; bool macro_execute(const gchar *macro) @@ -105,6 +105,8 @@ extern struct States { StateInsert insert; } states; +extern State *current_state; + extern enum Mode { MODE_NORMAL = 0, MODE_PARSE_ONLY @@ -116,6 +118,7 @@ extern enum Mode { } G_STMT_END extern gchar *strings[2]; +extern gchar escape_char; bool macro_execute(const gchar *macro); diff --git a/qbuffers.cpp b/qbuffers.cpp index 5f33417..61eab15 100644 --- a/qbuffers.cpp +++ b/qbuffers.cpp @@ -126,15 +126,6 @@ Ring::~Ring() } /* - * Auxiliary functions - */ -static inline bool -is_glob_pattern(const gchar *str) -{ - return strchr(str, '*') || strchr(str, '?'); -} - -/* * Command states */ @@ -1,6 +1,7 @@ #ifndef __QBUFFERS_H #define __QBUFFERS_H +#include <string.h> #include <bsd/sys/queue.h> #include <glib.h> @@ -121,4 +122,13 @@ private: State *done(const gchar *str); }; +/* + * Auxiliary functions + */ +static inline bool +is_glob_pattern(const gchar *str) +{ + return strchr(str, '*') || strchr(str, '?'); +} + #endif @@ -5,12 +5,15 @@ #include <glib.h> #include <gtk/gtk.h> +#include "gtk-info-popup.h" #include <Scintilla.h> extern gchar *cmdline; extern bool quit_requested; +extern GtkInfoPopup *filename_popup; + void message_display(GtkMessageType type, const gchar *fmt, ...) G_GNUC_PRINTF(2, 3); @@ -21,6 +24,7 @@ sptr_t editor_msg(unsigned int iMessage, uptr_t wParam = 0, sptr_t lParam = 0); #define IS_CTL(C) ((C) < ' ') #define CTL_ECHO(C) ((C) | 0x40) +#define CTL_KEY(C) ((C) & ~0x40) /* TECO uses only lower 7 bits for commands */ #define MAX_TRANSITIONS 127 |