aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2012-11-10 20:08:51 +0100
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2012-11-10 20:08:51 +0100
commitf120e3e367f01f006bd5ddfd9f1ac018273e75e3 (patch)
tree65e51cfbc6bc05eb25d331bcb86ed642a1af6bad
parent5aa548cd63edb8ba12c656300a2a5f0ff62a7b36 (diff)
downloadsciteco-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--.gitignore3
-rw-r--r--Makefile12
-rw-r--r--cmdline.cpp140
-rw-r--r--gtk-info-popup.gob135
-rw-r--r--main.cpp5
-rw-r--r--parser.cpp5
-rw-r--r--parser.h3
-rw-r--r--qbuffers.cpp9
-rw-r--r--qbuffers.h10
-rw-r--r--sciteco.h4
10 files changed, 313 insertions, 13 deletions
diff --git a/.gitignore b/.gitignore
index da4c764..13f51b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# Generated code
+gtk-info-popup*.[ch]
+
*.o
sciteco
diff --git a/Makefile b/Makefile
index 49cf315..5d7787a 100644
--- a/Makefile
+++ b/Makefile
@@ -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);
+ }
+}
diff --git a/main.cpp b/main.cpp
index 761fa96..0f62dc5 100644
--- a/main.cpp
+++ b/main.cpp
@@ -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");
diff --git a/parser.cpp b/parser.cpp
index f4abac4..0c1a697 100644
--- a/parser.cpp
+++ b/parser.cpp
@@ -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)
diff --git a/parser.h b/parser.h
index 61eb433..e655e34 100644
--- a/parser.h
+++ b/parser.h
@@ -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
*/
diff --git a/qbuffers.h b/qbuffers.h
index 0f1c16e..eb4c2d4 100644
--- a/qbuffers.h
+++ b/qbuffers.h
@@ -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
diff --git a/sciteco.h b/sciteco.h
index e35ce92..8659890 100644
--- a/sciteco.h
+++ b/sciteco.h
@@ -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