diff options
| author | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2025-12-23 13:54:17 +0100 |
|---|---|---|
| committer | Robin Haberkorn <rhaberkorn@fmsbw.de> | 2025-12-23 13:54:17 +0100 |
| commit | 0c89fb700957e411885e7e7835e15f441e8b5e84 (patch) | |
| tree | 56d756d6fa1923a5198504a322ec9c67cf274922 | |
| parent | 2592ef74ab2eba57c32fe21993ce01e9698b106f (diff) | |
fixed clicking the "(Unnamed)" buffer in 0EB popupsHEADmaster-fmsbw-cimaster
* When constructing the list of popup items, the unnamed buffer is stored as the empty string
instead of a prerendered "(Unnamed)".
Using the empty string simplifies autocompletions, which will actually have to insert nothing
at all (in addition to terminating the string).
* Since unnamed buffers are now special in the popup list, we can render them with special
icons as well.
Currently, only on Curses we use a file symbol with a question mark.
There doesn't appear to be a fitting standard Freedesktop icon to use on GTK and there
isn't even any fitting standard emblem to lay over the default file icon.
| -rw-r--r-- | src/interface-curses/curses-icons.c | 4 | ||||
| -rw-r--r-- | src/interface-curses/curses-info-popup.c | 20 | ||||
| -rw-r--r-- | src/interface-curses/curses-utils.h | 3 | ||||
| -rw-r--r-- | src/interface-curses/interface.c | 38 | ||||
| -rw-r--r-- | src/interface-gtk/gtk-info-popup.c | 15 | ||||
| -rw-r--r-- | src/interface-gtk/gtk-label.c | 28 | ||||
| -rw-r--r-- | src/interface-gtk/interface.c | 18 | ||||
| -rw-r--r-- | src/interface.h | 12 | ||||
| -rw-r--r-- | src/ring.c | 9 | ||||
| -rw-r--r-- | src/string-utils.h | 2 |
10 files changed, 93 insertions, 56 deletions
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c index 7c021d6..d932104 100644 --- a/src/interface-curses/curses-icons.c +++ b/src/interface-curses/curses-icons.c @@ -362,6 +362,10 @@ teco_curses_icon_cmp(const void *a, const void *b) gunichar teco_curses_icons_lookup_file(const gchar *filename) { + if (!filename || !*filename) + /* "(Unnamed)" file */ + return 0xf1036; /* */ + g_autofree gchar *basename = g_path_get_basename(filename); const teco_curses_icon_t *icon; diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c index c51a99b..8da6fbb 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.c @@ -19,6 +19,8 @@ #include "config.h" #endif +#include <string.h> + #include <glib.h> #include <curses.h> @@ -38,6 +40,7 @@ typedef struct { teco_stailq_entry_t entry; teco_popup_entry_type_t type; + /** entry name or empty string for the "(Unnamed)" buffer */ teco_string_t name; gboolean highlight; } teco_popup_entry_t; @@ -122,25 +125,32 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) if (entry->highlight) wattron(ctx->pad, A_BOLD); + teco_string_t name = entry->name; + if (!name.len) { + name.data = TECO_UNNAMED_FILE; + name.len = strlen(name.data); + } + switch (entry->type) { case TECO_POPUP_FILE: - g_assert(!teco_string_contains(&entry->name, '\0')); + g_assert(!teco_string_contains(&name, '\0')); if (teco_ed & TECO_ED_ICONS) { + /* "(Unnamed)" buffer is looked up as "" */ teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_file(entry->name.data)); waddch(ctx->pad, ' '); } - teco_curses_format_filename(ctx->pad, entry->name.data, -1); + teco_curses_format_filename(ctx->pad, name.data, -1); break; case TECO_POPUP_DIRECTORY: - g_assert(!teco_string_contains(&entry->name, '\0')); + g_assert(!teco_string_contains(&name, '\0')); if (teco_ed & TECO_ED_ICONS) { teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_dir(entry->name.data)); waddch(ctx->pad, ' '); } - teco_curses_format_filename(ctx->pad, entry->name.data, -1); + teco_curses_format_filename(ctx->pad, name.data, -1); break; default: - teco_curses_format_str(ctx->pad, entry->name.data, entry->name.len, -1); + teco_curses_format_str(ctx->pad, name.data, name.len, -1); break; } diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h index 18cdd3d..c63c747 100644 --- a/src/interface-curses/curses-utils.h +++ b/src/interface-curses/curses-utils.h @@ -20,6 +20,9 @@ #include <curses.h> +/** what is displayed for unnamed buffers in the info line and popups */ +#define TECO_UNNAMED_FILE "(Unnamed)" + guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width); guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width); diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 073ff86..833b564 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -138,8 +138,6 @@ static gint teco_xterm_version(void) G_GNUC_UNUSED; static gint teco_interface_blocking_getch(void); -#define UNNAMED_FILE "(Unnamed)" - /** * Get bright variant of one of the 8 standard * curses colors. @@ -202,6 +200,7 @@ static struct { TECO_INFO_TYPE_BUFFER = 0, TECO_INFO_TYPE_QREG } info_type; + /* current document's name or empty string for "(Unnamed)" buffer */ teco_string_t info_current; gboolean info_dirty; @@ -437,12 +436,6 @@ teco_interface_init(void) teco_curses_info_popup_init(&teco_interface.popup); - /* - * Make sure we have a string for the info line - * even if teco_interface_info_update() is never called. - */ - teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME)); - teco_cmdline_init(); /* * The default INDIC_STRIKE wouldn't be visible. @@ -1160,26 +1153,30 @@ teco_interface_draw_info(void) waddstr(teco_interface.info_window, PACKAGE_NAME " "); + teco_string_t info_current = teco_interface.info_current; + if (!info_current.len) { + info_current.data = TECO_UNNAMED_FILE; + info_current.len = strlen(info_current.data); + } + switch (teco_interface.info_type) { case TECO_INFO_TYPE_QREG: info_type_str = PACKAGE_NAME " - <QRegister> "; teco_curses_add_wc(teco_interface.info_window, teco_ed & TECO_ED_ICONS ? TECO_CURSES_ICONS_QREG : '-'); waddstr(teco_interface.info_window, " <QRegister> "); - /* same formatting as in command lines */ teco_curses_format_str(teco_interface.info_window, - teco_interface.info_current.data, - teco_interface.info_current.len, -1); + info_current.data, info_current.len, -1); break; case TECO_INFO_TYPE_BUFFER: info_type_str = PACKAGE_NAME " - <Buffer> "; - g_assert(!teco_string_contains(&teco_interface.info_current, '\0')); + g_assert(!teco_string_contains(&info_current, '\0')); + /* "(Unnamed)" buffer has to be looked up as "" */ teco_curses_add_wc(teco_interface.info_window, teco_ed & TECO_ED_ICONS ? teco_curses_icons_lookup_file(teco_interface.info_current.data) : '-'); waddstr(teco_interface.info_window, " <Buffer> "); - teco_curses_format_filename(teco_interface.info_window, - teco_interface.info_current.data, + teco_curses_format_filename(teco_interface.info_window, info_current.data, getmaxx(teco_interface.info_window) - getcurx(teco_interface.info_window) - 1); waddch(teco_interface.info_window, teco_interface.info_dirty ? '*' : ' '); @@ -1195,8 +1192,7 @@ teco_interface_draw_info(void) * Make sure the title will consist only of printable characters. */ g_autofree gchar *info_current_printable; - info_current_printable = teco_string_echo(teco_interface.info_current.data, - teco_interface.info_current.len); + info_current_printable = teco_string_echo(info_current.data, info_current.len); g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable, teco_interface.info_dirty ? "*" : "", NULL); teco_interface_set_window_title(title); @@ -1216,10 +1212,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg) void teco_interface_info_update_buffer(const teco_buffer_t *buffer) { - const gchar *filename = buffer->filename ? : UNNAMED_FILE; - teco_string_clear(&teco_interface.info_current); - teco_string_init(&teco_interface.info_current, filename, strlen(filename)); + teco_string_init(&teco_interface.info_current, buffer->filename, + buffer->filename ? strlen(buffer->filename) : 0); teco_interface.info_dirty = buffer->state > TECO_BUFFER_CLEAN; teco_interface.info_type = TECO_INFO_TYPE_BUFFER; /* NOTE: drawn in teco_interface_event_loop_iter() */ @@ -1865,7 +1860,10 @@ teco_interface_process_mevent(MEVENT *event, GError **error) event->y, event->x); if (insert && machine->current->insert_completion_cb) { - /* successfully clicked popup item */ + /* + * Successfully clicked popup item. + * `insert` is the empty string for the "(Unnamed)" buffer. + */ const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len, insert->len - teco_interface.popup_prefix_len}; if (!machine->current->insert_completion_cb(machine, &insert_suffix, error)) diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c index aaa0a65..3ddd10a 100644 --- a/src/interface-gtk/gtk-info-popup.c +++ b/src/interface-gtk/gtk-info-popup.c @@ -37,6 +37,7 @@ typedef struct { teco_stailq_entry_t entry; teco_popup_entry_type_t type; + /** entry name or empty string for the "(Unnamed)" buffer */ teco_string_t name; gboolean highlight; } teco_popup_entry_t; @@ -249,6 +250,10 @@ teco_gtk_info_popup_new(void) GIcon * teco_gtk_info_popup_get_icon_for_path(const gchar *path, const gchar *fallback_name) { + if (!path || !*path) + /* "(Unnamed)" file */ + return g_icon_new_for_string(fallback_name, NULL); + GIcon *icon = NULL; g_autoptr(GFile) file = g_file_new_for_path(path); @@ -299,7 +304,7 @@ teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, static void teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type, - const gchar *name, gssize len, gboolean highlight) + const gchar *name, gsize len, gboolean highlight) { g_return_if_fail(self != NULL); g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self)); @@ -318,12 +323,8 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic" : "folder"; - /* - * `name` is not guaranteed to be null-terminated. - */ - g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len); - - g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(path, fallback); + /* name comes from a teco_string_t and is guaranteed to be null-terminated */ + g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(name, fallback); if (icon) { gint width, height; gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height); diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c index e4b2823..e148652 100644 --- a/src/interface-gtk/gtk-label.c +++ b/src/interface-gtk/gtk-label.c @@ -32,7 +32,9 @@ #include "gtk-label.h" -#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) +#define TECO_UNNAMED_FILE "(Unnamed)" + +#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) struct _TecoGtkLabel { GtkLabel parent_instance; @@ -40,6 +42,7 @@ struct _TecoGtkLabel { PangoColor fg, bg; guint16 fg_alpha, bg_alpha; + /** text backing the label or empty string for "(Unnamed)" buffer */ teco_string_t string; }; @@ -251,19 +254,22 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len) teco_string_clear(&self->string); teco_string_init(&self->string, str, len < 0 ? strlen(str) : len); + teco_string_t string = self->string; + if (!string.len) { + string.data = TECO_UNNAMED_FILE; + string.len = strlen(string.data); + } + g_autofree gchar *plaintext = NULL; + PangoAttrList *attribs = NULL; - if (self->string.len > 0) { - PangoAttrList *attribs = NULL; + teco_gtk_label_parse_string(string.data, string.len, + &self->fg, self->fg_alpha, + &self->bg, self->bg_alpha, + &attribs, &plaintext); - teco_gtk_label_parse_string(self->string.data, self->string.len, - &self->fg, self->fg_alpha, - &self->bg, self->bg_alpha, - &attribs, &plaintext); - - gtk_label_set_attributes(GTK_LABEL(self), attribs); - pango_attr_list_unref(attribs); - } + gtk_label_set_attributes(GTK_LABEL(self), attribs); + pango_attr_list_unref(attribs); gtk_label_set_text(GTK_LABEL(self), plaintext); } diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index d7fc4c4..2b292a3 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -78,7 +78,7 @@ static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny * static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED; static gchar teco_interface_get_ansi_key(GdkEventKey *event); -#define UNNAMED_FILE "(Unnamed)" +#define TECO_UNNAMED_FILE "(Unnamed)" #define USER_CSS_FILE ".teco_css" @@ -102,6 +102,7 @@ static struct { TECO_INFO_TYPE_BUFFER_DIRTY, TECO_INFO_TYPE_QREG } info_type; + /* current document's name or empty string for "(Unnamed)" buffer */ teco_string_t info_current; gboolean no_csd; @@ -501,8 +502,13 @@ teco_interface_refresh_info(void) gtk_style_context_remove_class(style, "dirty"); g_auto(teco_string_t) info_current_temp; - teco_string_init(&info_current_temp, - teco_interface.info_current.data, teco_interface.info_current.len); + + if (!teco_interface.info_current.len) + teco_string_init(&info_current_temp, TECO_UNNAMED_FILE, strlen(TECO_UNNAMED_FILE)); + else + teco_string_init(&info_current_temp, + teco_interface.info_current.data, teco_interface.info_current.len); + if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY) teco_string_append_c(&info_current_temp, '*'); teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget), @@ -570,10 +576,9 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg) void teco_interface_info_update_buffer(const teco_buffer_t *buffer) { - const gchar *filename = buffer->filename ? : UNNAMED_FILE; - teco_string_clear(&teco_interface.info_current); - teco_string_init(&teco_interface.info_current, filename, strlen(filename)); + teco_string_init(&teco_interface.info_current, buffer->filename, + buffer->filename ? strlen(buffer->filename) : 0); teco_interface.info_type = buffer->state > TECO_BUFFER_CLEAN ? TECO_INFO_TYPE_BUFFER_DIRTY : TECO_INFO_TYPE_BUFFER; } @@ -1472,6 +1477,7 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data) { g_assert(len >= teco_interface.popup_prefix_len); + /* str is an empty string for the "(Unnamed)" buffer */ const teco_string_t insert = {str+teco_interface.popup_prefix_len, len-teco_interface.popup_prefix_len}; teco_machine_t *machine = &teco_cmdline.machine.parent; diff --git a/src/interface.h b/src/interface.h index 8ab66ef..24df14c 100644 --- a/src/interface.h +++ b/src/interface.h @@ -135,7 +135,17 @@ typedef enum { TECO_POPUP_DIRECTORY } teco_popup_entry_type_t; -/** @pure */ +/** + * Add entry to popup. + * + * @param type Entry type + * @param name + * Name string of the entry or NULL for "(Unnamed)". + * It is not necessarily null-terminated. + * @param name_len Length of string in name + * @param highlight Whether to highlight the entry + * @pure + */ void teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len, gboolean highlight); /** @pure */ @@ -517,11 +517,10 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error) ctx->flags.allow_filename = TRUE; if (id == 0) { - for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur)) { - const gchar *filename = cur->filename ? : "(Unnamed)"; - teco_interface_popup_add(TECO_POPUP_FILE, filename, - strlen(filename), cur == teco_ring_current); - } + for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur)) + teco_interface_popup_add(TECO_POPUP_FILE, cur->filename, + cur->filename ? strlen(cur->filename) : 0, + cur == teco_ring_current); teco_interface_popup_show(0); } else if (id > 0) { diff --git a/src/string-utils.h b/src/string-utils.h index 2491d07..af2c8a1 100644 --- a/src/string-utils.h +++ b/src/string-utils.h @@ -102,7 +102,7 @@ teco_string_init(teco_string_t *target, const gchar *str, gsize len) static inline void teco_string_init_chunk(teco_string_t *target, const gchar *str, gssize len, GStringChunk *chunk) { - target->data = g_string_chunk_insert_len(chunk, str, len); + target->data = g_string_chunk_insert_len(chunk, str ? : "", len); target->len = len; } |
