aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmdline.c151
-rw-r--r--src/cmdline.h2
-rw-r--r--src/core-commands.c195
-rw-r--r--src/core-commands.h2
-rw-r--r--src/doc.c2
-rw-r--r--src/doc.h2
-rw-r--r--src/eol.c2
-rw-r--r--src/eol.h2
-rw-r--r--src/error.c2
-rw-r--r--src/error.h2
-rw-r--r--src/expressions.c2
-rw-r--r--src/expressions.h2
-rw-r--r--src/file-utils.c80
-rw-r--r--src/file-utils.h2
-rw-r--r--src/glob.c2
-rw-r--r--src/glob.h5
-rw-r--r--src/goto-commands.c28
-rw-r--r--src/goto-commands.h2
-rw-r--r--src/goto.c121
-rw-r--r--src/goto.h8
-rw-r--r--src/help.c12
-rw-r--r--src/help.h2
-rw-r--r--src/interface-curses/curses-icons.c2
-rw-r--r--src/interface-curses/curses-icons.h2
-rw-r--r--src/interface-curses/curses-info-popup.c74
-rw-r--r--src/interface-curses/curses-info-popup.h20
-rw-r--r--src/interface-curses/curses-utils.c2
-rw-r--r--src/interface-curses/curses-utils.h2
-rw-r--r--src/interface-curses/interface.c437
-rw-r--r--src/interface-gtk/Makefile.am2
-rw-r--r--src/interface-gtk/gtk-info-popup.c57
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c8
-rw-r--r--src/interface-gtk/gtk-label.h3
-rw-r--r--src/interface-gtk/interface.c329
-rw-r--r--src/interface-gtk/view.c126
-rw-r--r--src/interface.c5
-rw-r--r--src/interface.h28
-rw-r--r--src/lexer.c2
-rw-r--r--src/lexer.h2
-rw-r--r--src/list.h2
-rw-r--r--src/main.c47
-rw-r--r--src/memory.c2
-rw-r--r--src/memory.h2
-rw-r--r--src/parser.c7
-rw-r--r--src/parser.h34
-rw-r--r--src/qreg-commands.c2
-rw-r--r--src/qreg-commands.h9
-rw-r--r--src/qreg.c15
-rw-r--r--src/qreg.h2
-rw-r--r--src/rb3str.c4
-rw-r--r--src/rb3str.h2
-rw-r--r--src/ring.c66
-rw-r--r--src/ring.h4
-rw-r--r--src/sciteco.h4
-rw-r--r--src/search.c2
-rw-r--r--src/search.h2
-rw-r--r--src/spawn.c18
-rw-r--r--src/spawn.h2
-rw-r--r--src/string-utils.c2
-rw-r--r--src/string-utils.h2
-rw-r--r--src/symbols.c10
-rw-r--r--src/symbols.h2
-rw-r--r--src/undo.c2
-rw-r--r--src/undo.h2
-rw-r--r--src/view.c2
-rw-r--r--src/view.h2
67 files changed, 1537 insertions, 444 deletions
diff --git a/src/cmdline.c b/src/cmdline.c
index 816816c..dde096d 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -614,7 +614,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
*/
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -645,6 +645,16 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
}
gboolean
+teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error)
+{
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(ctx, str->data, str->len, &str_escaped);
+ if (!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1]))
+ teco_string_append_c(&str_escaped, ' ');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_stringbuilding_escaped_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error)
{
@@ -684,6 +694,14 @@ teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_
}
gboolean
+teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+ teco_state_t *stringbuilding_current = stringbuilding_ctx->parent.current;
+ return stringbuilding_current->insert_completion_cb(&stringbuilding_ctx->parent, str, error);
+}
+
+gboolean
teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -778,7 +796,7 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -801,6 +819,20 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) &&
+ ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -820,7 +852,7 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -855,6 +887,21 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_autofree gchar *pattern_escaped = teco_globber_escape_pattern(str->data);
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, pattern_escaped, strlen(pattern_escaped), &str_escaped);
+ if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) &&
+ ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -874,7 +921,7 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -898,6 +945,19 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *
}
gboolean
+teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ /*
+ * FIXME: We might terminate the command in case of leaf directories.
+ */
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
g_assert(ctx->expectqreg != NULL);
@@ -910,6 +970,18 @@ teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ g_assert(ctx->expectqreg != NULL);
+ /*
+ * NOTE: teco_machine_qregspec_t is private, so we downcast to teco_machine_t.
+ * Otherwise, we'd have to move this callback into qreg.c.
+ */
+ teco_state_t *expectqreg_current = ((teco_machine_t *)ctx->expectqreg)->current;
+ return expectqreg_current->insert_completion_cb((teco_machine_t *)ctx->expectqreg, str, error);
+}
+
+gboolean
teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
switch (key) {
@@ -919,7 +991,7 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -945,6 +1017,12 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_
}
gboolean
+teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error)
+{
+ return teco_cmdline_insert(str->data, str->len, error);
+}
+
+gboolean
teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = teco_machine_qregspec_get_stringbuilding(ctx);
@@ -967,7 +1045,7 @@ teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_m
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -985,6 +1063,17 @@ teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_m
}
gboolean
+teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = teco_machine_qregspec_get_stringbuilding(ctx);
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ teco_string_append_c(&str_escaped, ']');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1003,14 +1092,14 @@ teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
break;
/*
- * In the EC command, <TAB> completes files just like ^T
+ * In the EC command, <TAB> completes files just like ^G<TAB>.
*
* TODO: Implement shell-command completion by iterating
* executables in $PATH
*/
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1049,7 +1138,7 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1075,6 +1164,17 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac
}
gboolean
+teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ teco_string_append_c(&str_escaped, ',');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1094,7 +1194,7 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1122,6 +1222,20 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
}
gboolean
+teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ /*
+ * FIXME: This does not escape `,`. Cannot be escaped via ^Q currently?
+ */
+ teco_string_append_c(&str_escaped, ',');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1141,7 +1255,7 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1163,6 +1277,19 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
return stringbuilding_current->process_edit_cmd_cb(&stringbuilding_ctx->parent, &ctx->parent, key, error);
}
+gboolean
+teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ if (ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
/*
* Command states
*/
diff --git a/src/cmdline.h b/src/cmdline.h
index f4b84e4..ebdf1e1 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/core-commands.c b/src/core-commands.c
index 4ee0c5c..0d23adb 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -2202,51 +2202,6 @@ TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_escape,
.style = SCE_SCITECO_COMMAND
);
-/*$ EF close
- * [bool]EF -- Remove buffer from ring
- * -EF
- *
- * Removes buffer from buffer ring, effectively
- * closing it.
- * If the buffer is dirty (modified), EF will yield
- * an error.
- * <bool> may be a specified to enforce closing dirty
- * buffers.
- * If it is a Failure condition boolean (negative),
- * the buffer will be closed unconditionally.
- * If <bool> is absent, the sign prefix (1 or -1) will
- * be implied, so \(lq-EF\(rq will always close the buffer.
- *
- * It is noteworthy that EF will be executed immediately in
- * interactive mode but can be rubbed out at a later time
- * to reopen the file.
- * Closed files are kept in memory until the command line
- * is terminated.
- */
-static void
-teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
-{
- if (teco_qreg_current) {
- const teco_string_t *name = &teco_qreg_current->head.name;
- g_autofree gchar *name_printable = teco_string_echo(name->data, name->len);
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Q-Register \"%s\" currently edited", name_printable);
- return;
- }
-
- teco_int_t v;
- if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
- return;
- if (teco_is_failure(v) && teco_ring_current->dirty) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Buffer \"%s\" is dirty",
- teco_ring_current->filename ? : "(Unnamed)");
- return;
- }
-
- teco_ring_close(error);
-}
-
/*$ ED flags
* flags ED -- Set and get ED-flags
* [off,]on ED
@@ -2266,37 +2221,50 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * - 4: If enabled, prefer raw single-byte ANSI encoding
- * for all new buffers and registers.
- * This does not change the encoding of any existing
- * buffers and any initialized default register when set via
- * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
- * - 8: Enable/disable automatic folding of case-insensitive
- * command characters during interactive key translation.
- * The case of letter keys is inverted, so one or two
- * character commands will typically be inserted upper-case,
- * but you can still press Shift to insert lower-case letters.
- * Case-insensitive Q-Register specifications are not
- * case folded.
- * This is thought to improve the readability of the command
- * line macro.
- * - 16: Enable/disable automatic translation of end of
- * line sequences to and from line feed.
- * Disabling this flag allows 8-bit clean loading and saving
- * of files.
- * - 32: Enable/Disable buffer editing hooks
- * (via execution of macro in global Q-Register \(lqED\(rq)
- * - 128: Enable/Disable enforcement of UNIX98
- * \(lq/bin/sh\(rq emulation for operating system command
- * executions
- * - 256: Enable/Disable OSC-52 clipboard support.
- * Must only be enabled if the terminal emulator is configured
- * properly.
- * - 512: Enable/Disable Unicode icons in the Curses UI.
- * This requires a capable font, like the ones provided
- * by the \(lqNerd Fonts\(rq project.
- * Changes to this flag in interactive mode may not become
- * effective immediately.
+ * .IP 4: 5
+ * If enabled, prefer raw single-byte ANSI encoding
+ * for all new buffers and registers.
+ * This does not change the encoding of any existing
+ * buffers and any initialized default register when set via
+ * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
+ * .IP 8:
+ * Enable/disable automatic folding of case-insensitive
+ * command characters during interactive key translation.
+ * The case of letter keys is inverted, so one or two
+ * character commands will typically be inserted upper-case,
+ * but you can still press Shift to insert lower-case letters.
+ * Case-insensitive Q-Register specifications are not
+ * case folded.
+ * This is thought to improve the readability of the command
+ * line macro.
+ * .IP 16:
+ * Enable/disable automatic translation of end of
+ * line sequences to and from line feed.
+ * Disabling this flag allows 8-bit clean loading and saving
+ * of files.
+ * .IP 32:
+ * Enable/Disable buffer editing hooks
+ * (via execution of macro in global Q-Register \(lqED\(rq)
+ * .IP 64:
+ * .SCITECO_TOPIC mouse
+ * Enable/Disable processing and delivery of mouse events in
+ * the Curses UI.
+ * If enabled, the terminal emulator's default mouse behavior
+ * may be inhibited.
+ * .IP 128:
+ * Enable/Disable enforcement of UNIX98
+ * \(lq/bin/sh\(rq emulation for operating system command
+ * executions
+ * .IP 256:
+ * Enable/Disable OSC-52 clipboard support.
+ * Must only be enabled if the terminal emulator is configured
+ * properly.
+ * .IP 512:
+ * Enable/Disable Unicode icons in the Curses UI.
+ * This requires a capable font, like the ones provided
+ * by the \(lqNerd Fonts\(rq project.
+ * Changes to this flag in interactive mode may not become
+ * effective immediately.
*
* The features controlled thus are discribed in other sections
* of this manual.
@@ -2322,9 +2290,10 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
/*$ EJ properties
* [key]EJ -> value -- Get and set system properties
- * -EJ -> value
* value,keyEJ
* rgb,color,3EJ
+ * -EJ -> event
+ * -2EJ -> y, x
*
* This command may be used to get and set system
* properties.
@@ -2338,16 +2307,16 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* write-only or read-only.
*
* The following property keys are defined:
- * .IP 0 4
+ * .IP 0: 4
* The current user interface: 1 for Curses, 2 for GTK
* (\fBread-only\fP)
- * .IP 1
+ * .IP 1:
* The current numbfer of buffers: Also the numeric id
* of the last buffer in the ring. This is implied if
* no argument is given, so \(lqEJ\(rq returns the number
* of buffers in the ring.
* (\fBread-only\fP)
- * .IP 2
+ * .IP 2:
* The current memory limit in bytes.
* This limit helps to prevent dangerous out-of-memory
* conditions (e.g. resulting from infinite loops) by
@@ -2373,7 +2342,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* this happens you may have to clear your command-line
* first.
* Memory limiting is enabled by default.
- * .IP 3
+ * .IP 3:
* This \fBwrite-only\fP property allows redefining the
* first 16 entries of the terminal color palette \(em a
* feature required by some
@@ -2414,17 +2383,60 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* on exit the author is aware of is \fBxterm\fP(1) and
* the Linux console driver.
* You have been warned. Good luck.
- * .IP 4
+ * .IP 4:
* The column after the last horizontal movement.
* This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal
* setting \fBSCI_CHOOSECARETX\fP.
* Unless most other settings, this is on purpose not restored on rubout,
* so it "survives" command line replacements.
+ * .
+ * .IP -1:
+ * Type of the last mouse event (\fBread-only\fP).
+ * One of the following values will be returned:
+ * .RS
+ * . IP 1: 4
+ * Some button has been pressed
+ * . IP 2:
+ * Some button has been released
+ * . IP 3:
+ * Scroll up
+ * . IP 4:
+ * Scroll down
+ * .RE
+ * .IP -2:
+ * Coordinates of the mouse pointer relative to the Scintilla view
+ * at the time of the last mouse event.
+ * This is in pixels or cells depending on the UI.
+ * First the Y coordinate is pushed, followed by the X coordinate,
+ * allowing you to pass them on directly to the \fBSCI_POSITIONFROMPOINT\fP
+ * and similar Scintilla messages using the \fBES\fP command.
+ * (\fBread-only\fP)
+ * .IP -3:
+ * Number of the mouse button involved in the last mouse event, beginning with 1.
+ * Can be -1 if the button cannot be determined or is irrelevant.
+ * (\fBread-only\fP)
+ * .IP -4:
+ * Bit mask describing the key modifiers at the time of the last
+ * mouse event (\fBread-only\fP).
+ * Currently, the following flags are used:
+ * .RS
+ * . IP 1: 4
+ * Shift key
+ * . IP 2:
+ * Control key (CTRL)
+ * . IP 4:
+ * Alt key
+ * .RE
*/
static void
teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
{
enum {
+ EJ_MOUSE_MODS = -4,
+ EJ_MOUSE_BUTTON,
+ EJ_MOUSE_COORD,
+ EJ_MOUSE_TYPE,
+
EJ_USER_INTERFACE = 0,
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
@@ -2487,6 +2499,21 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
* Get property
*/
switch (property) {
+ case EJ_MOUSE_TYPE:
+ teco_expressions_push(teco_mouse.type);
+ break;
+ case EJ_MOUSE_COORD:
+ /* can be passed down to @ES/POSITIONFROMPOINT// */
+ teco_expressions_push(teco_mouse.y);
+ teco_expressions_push(teco_mouse.x);
+ break;
+ case EJ_MOUSE_BUTTON:
+ teco_expressions_push(teco_mouse.button);
+ break;
+ case EJ_MOUSE_MODS:
+ teco_expressions_push(teco_mouse.mods);
+ break;
+
case EJ_USER_INTERFACE:
/*
* FIXME: Replace INTERFACE_* macros with
@@ -2767,7 +2794,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error)
}
}
- teco_int_t dot_glyphs;
+ teco_int_t dot_glyphs = 0;
if (colon_modified) {
sptr_t dot_bytes = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
dot_glyphs = teco_interface_bytes2glyphs(dot_bytes);
diff --git a/src/core-commands.h b/src/core-commands.h
index fbb67fa..8ce7be7 100644
--- a/src/core-commands.h
+++ b/src/core-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/doc.c b/src/doc.c
index 019603a..4ac96c8 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/doc.h b/src/doc.h
index 1218c35..4ecfd21 100644
--- a/src/doc.h
+++ b/src/doc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/eol.c b/src/eol.c
index 9e80b96..e5cab2c 100644
--- a/src/eol.c
+++ b/src/eol.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/eol.h b/src/eol.h
index 26418e5..0bcb28c 100644
--- a/src/eol.h
+++ b/src/eol.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/error.c b/src/error.c
index afa2ac1..6326984 100644
--- a/src/error.c
+++ b/src/error.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/error.h b/src/error.h
index 021f759..b672024 100644
--- a/src/error.h
+++ b/src/error.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/expressions.c b/src/expressions.c
index 63d3b2f..c48e7b0 100644
--- a/src/expressions.c
+++ b/src/expressions.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/expressions.h b/src/expressions.h
index caea1d7..631c867 100644
--- a/src/expressions.h
+++ b/src/expressions.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/file-utils.c b/src/file-utils.c
index 7839a38..75bcb48 100644
--- a/src/file-utils.c
+++ b/src/file-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -241,6 +241,60 @@ teco_file_get_program_path(void)
#endif
+#ifdef G_OS_WIN32
+
+/*
+ * Definitions from the DDK's ntifs.h.
+ */
+#define FileCaseSensitiveInformation 71
+
+static gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ g_autofree gunichar2 *path_utf16 = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
+ HANDLE hnd = CreateFileW(path_utf16, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (hnd == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ /*
+ * NOTE: This requires Windows 10, version 1803 or later.
+ * FIXME: But even then, this is relying on undocumented behavior!
+ * If unavailable we just assume the platform-default case-insensitivity.
+ */
+ FILE_CASE_SENSITIVE_INFORMATION info = {0};
+ GetFileInformationByHandleEx(hnd, FileCaseSensitiveInformation, &info, sizeof(info));
+ CloseHandle(hnd);
+ return info.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR;
+}
+
+#elif defined(G_OS_UNIX) && defined(_PC_CASE_SENSITIVE)
+
+/*
+ * This is supported at least on Mac OS.
+ *
+ * NOTE: If the selector is not supported, -1 is returned and we also assume case-sensitivity.
+ */
+static inline gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ return pathconf(path, _PC_CASE_SENSITIVE);
+}
+
+#else /* !G_OS_WIN32 && (!G_OS_UNIX || !_PC_CASE_SENSITIVE) */
+
+/*
+ * FIXME: The only way to query this on Linux and FreeBSD would be to
+ * hardcode "case-insensitive" file systems.
+ */
+static inline gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ return TRUE;
+}
+
+#endif
+
/**
* Get the datadir.
*
@@ -334,11 +388,16 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
gsize dirname_len = teco_file_get_dirname_len(filename_expanded);
g_autofree gchar *dirname = g_strndup(filename_expanded, dirname_len);
gchar *basename = filename_expanded + dirname_len;
+ gsize basename_len = strlen(basename);
g_autoptr(GDir) dir = g_dir_open(dirname_len ? dirname : ".", 0, NULL);
if (!dir)
return FALSE;
+ /* Whether the directory has case-sensitive entries */
+ gboolean case_sensitive = teco_file_is_case_sensitive(dirname_len ? dirname : ".");
+ teco_string_diff_t string_diff = case_sensitive ? teco_string_diff : teco_string_casediff;
+
/*
* On Windows, both forward and backslash
* directory separators are allowed in directory
@@ -356,9 +415,12 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
guint files_len = 0;
gsize prefix_len = 0;
- const gchar *cur_basename;
- while ((cur_basename = g_dir_read_name(dir))) {
- if (!g_str_has_prefix(cur_basename, basename))
+ teco_string_t cur_basename;
+ while ((cur_basename.data = (gchar *)g_dir_read_name(dir))) {
+ cur_basename.len = strlen(cur_basename.data);
+
+ if (string_diff(&cur_basename, basename, basename_len) != basename_len)
+ /* basename is not a prefix of cur_basename */
continue;
/*
@@ -366,8 +428,8 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
* Reserving one byte at the end of the filename ensures we can easily
* append the directory separator without reallocations.
*/
- gchar *cur_filename = g_malloc(strlen(dirname)+strlen(cur_basename)+2);
- strcat(strcpy(cur_filename, dirname), cur_basename);
+ gchar *cur_filename = g_malloc(strlen(dirname)+cur_basename.len+2);
+ strcat(strcpy(cur_filename, dirname), cur_basename.data);
/*
* NOTE: This avoids g_file_test() for G_FILE_TEST_EXISTS
@@ -391,8 +453,8 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
other_file.data = (gchar *)g_slist_next(files)->data + filename_len;
other_file.len = strlen(other_file.data);
- gsize len = teco_string_diff(&other_file, cur_filename + filename_len,
- strlen(cur_filename) - filename_len);
+ gsize len = string_diff(&other_file, cur_filename + filename_len,
+ strlen(cur_filename) - filename_len);
if (len < prefix_len)
prefix_len = len;
} else {
@@ -421,7 +483,7 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
strlen((gchar *)file->data), is_buffer);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(filename_len);
}
/*
diff --git a/src/file-utils.h b/src/file-utils.h
index e974e2f..12a9b83 100644
--- a/src/file-utils.h
+++ b/src/file-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/glob.c b/src/glob.c
index e338975..fe73065 100644
--- a/src/glob.c
+++ b/src/glob.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/glob.h b/src/glob.h
index 8f03d38..d11fbce 100644
--- a/src/glob.h
+++ b/src/glob.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,6 +48,7 @@ GRegex *teco_globber_compile_pattern(const gchar *pattern);
/* in cmdline.c */
gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTGLOB
@@ -58,6 +59,8 @@ gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_m
TECO_DEFINE_STATE_EXPECTFILE(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectglob_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectglob_insert_completion, \
##__VA_ARGS__ \
)
diff --git a/src/goto-commands.c b/src/goto-commands.c
index 2035277..99288c1 100644
--- a/src/goto-commands.c
+++ b/src/goto-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@
#include "lexer.h"
#include "core-commands.h"
#include "undo.h"
+#include "interface.h"
#include "goto.h"
#include "goto-commands.h"
@@ -62,15 +63,20 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
}
if (chr == '!') {
- /*
- * NOTE: If the label already existed, its PC will be restored
- * on rubout.
- * Otherwise, the label will be removed (PC == -1).
- */
gssize existing_pc = teco_goto_table_set(&ctx->goto_table, ctx->goto_label.data,
ctx->goto_label.len, ctx->macro_pc);
+ if (existing_pc == ctx->macro_pc)
+ /* encountered the same label again */
+ return &teco_state_start;
+ if (existing_pc >= 0) {
+ g_autofree gchar *label_printable = teco_string_echo(ctx->goto_label.data,
+ ctx->goto_label.len);
+ teco_interface_msg(TECO_MSG_WARNING, "Ignoring goto label \"%s\" redefinition",
+ label_printable);
+ return &teco_state_start;
+ }
if (ctx->parent.must_undo)
- teco_goto_table_undo_set(&ctx->goto_table, ctx->goto_label.data, ctx->goto_label.len, existing_pc);
+ teco_goto_table_undo_remove(&ctx->goto_table, ctx->goto_label.data, ctx->goto_label.len);
if (teco_goto_skip_label.len > 0 &&
!teco_string_cmp(&ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) {
@@ -151,7 +157,10 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
}
/* in cmdline.c */
-gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error);
+gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar chr, GError **error);
+gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ O
* Olabel$ -- Go to label
@@ -180,7 +189,8 @@ gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine
* terminate the command-line.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_goto_insert_completion
);
/*
diff --git a/src/goto-commands.h b/src/goto-commands.h
index 03773c0..f4f52d5 100644
--- a/src/goto-commands.h
+++ b/src/goto-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/goto.c b/src/goto.c
index 65ee3ca..855c9f9 100644
--- a/src/goto.c
+++ b/src/goto.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -78,53 +78,26 @@ teco_goto_table_dump(teco_goto_table_t *ctx)
}
#endif
-/** @memberof teco_goto_table_t */
-gssize
+/**
+ * Remove label from goto table.
+ *
+ * @param ctx Goto table
+ * @param name Label name
+ * @param len Length of label name
+ * @return TRUE if the label existed and was removed
+ *
+ * @memberof teco_goto_table_t
+ */
+gboolean
teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len)
{
- gssize existing_pc = -1;
-
teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- if (label) {
- existing_pc = label->pc;
- rb3_unlink_and_rebalance(&label->head.head);
- teco_goto_label_free(label);
- }
+ if (!label)
+ return FALSE;
- return existing_pc;
-}
-
-/** @memberof teco_goto_table_t */
-gssize
-teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len)
-{
- teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- return label ? label->pc : -1;
-}
-
-/** @memberof teco_goto_table_t */
-gssize
-teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc)
-{
- if (pc < 0)
- return teco_goto_table_remove(ctx, name, len);
-
- gssize existing_pc = -1;
-
- teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- if (label) {
- existing_pc = label->pc;
- label->pc = pc;
- } else {
- label = teco_goto_label_new(name, len, pc);
- teco_rb3str_insert(&ctx->tree, TRUE, &label->head);
- }
-
-#ifdef DEBUG
- teco_goto_table_dump(ctx);
-#endif
-
- return existing_pc;
+ rb3_unlink_and_rebalance(&label->head.head);
+ teco_goto_label_free(label);
+ return TRUE;
}
/*
@@ -135,35 +108,35 @@ teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize
*/
typedef struct {
teco_goto_table_t *table;
- gssize pc;
gsize len;
gchar name[];
-} teco_goto_table_undo_set_t;
+} teco_goto_table_undo_remove_t;
static void
-teco_goto_table_undo_set_action(teco_goto_table_undo_set_t *ctx, gboolean run)
+teco_goto_table_undo_remove_action(teco_goto_table_undo_remove_t *ctx, gboolean run)
{
- if (run) {
- teco_goto_table_set(ctx->table, ctx->name, ctx->len, ctx->pc);
+ if (!run)
+ return;
+
+ G_GNUC_UNUSED gboolean removed = teco_goto_table_remove(ctx->table, ctx->name, ctx->len);
+ g_assert(removed == TRUE);
#ifdef DEBUG
- teco_goto_table_dump(ctx->table);
+ teco_goto_table_dump(ctx->table);
#endif
- }
}
/** @memberof teco_goto_table_t */
void
-teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc)
+teco_goto_table_undo_remove(teco_goto_table_t *ctx, const gchar *name, gsize len)
{
if (!ctx->must_undo)
return;
- teco_goto_table_undo_set_t *token;
- token = teco_undo_push_size((teco_undo_action_t)teco_goto_table_undo_set_action,
+ teco_goto_table_undo_remove_t *token;
+ token = teco_undo_push_size((teco_undo_action_t)teco_goto_table_undo_remove_action,
sizeof(*token) + len);
if (token) {
token->table = ctx;
- token->pc = pc;
token->len = len;
if (name)
memcpy(token->name, name, len);
@@ -171,6 +144,44 @@ teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, g
}
/** @memberof teco_goto_table_t */
+gssize
+teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len)
+{
+ teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
+ return label ? label->pc : -1;
+}
+
+/**
+ * Insert label into goto table.
+ *
+ * @param ctx Goto table
+ * @param name Label name
+ * @param len Length of label name
+ * @param pc Program counter of the new label
+ * @return The program counter of any label of the same name
+ * or -1. The label is inserted only if there is no label in the
+ * table already.
+ *
+ * @memberof teco_goto_table_t
+ */
+gssize
+teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gsize pc)
+{
+ gssize existing_pc = teco_goto_table_find(ctx, name, len);
+ if (existing_pc >= 0)
+ return existing_pc;
+
+ teco_goto_label_t *label = teco_goto_label_new(name, len, pc);
+ teco_rb3str_insert(&ctx->tree, TRUE, &label->head);
+
+#ifdef DEBUG
+ teco_goto_table_dump(ctx);
+#endif
+
+ return -1;
+}
+
+/** @memberof teco_goto_table_t */
void
teco_goto_table_clear(teco_goto_table_t *ctx)
{
diff --git a/src/goto.h b/src/goto.h
index 01f55ac..eda823f 100644
--- a/src/goto.h
+++ b/src/goto.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,12 +40,12 @@ teco_goto_table_init(teco_goto_table_t *ctx, gboolean must_undo)
ctx->must_undo = must_undo;
}
-gssize teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
+gboolean teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
+void teco_goto_table_undo_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
gssize teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len);
-gssize teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc);
-void teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc);
+gssize teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gsize pc);
/** @memberof teco_goto_table_t */
static inline gboolean
diff --git a/src/help.c b/src/help.c
index 0f88646..07acb86 100644
--- a/src/help.c
+++ b/src/help.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -289,13 +289,13 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
return NULL;
}
- teco_ring_undo_edit();
/*
* ED hooks with the default lexer framework
* will usually load the styling SciTECO script
* when editing the buffer for the first time.
*/
- if (!teco_ring_edit(topic->filename, error))
+ if (!teco_current_doc_undo_edit(error) ||
+ !teco_ring_edit(topic->filename, error))
return NULL;
/*
@@ -314,7 +314,10 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
}
/* in cmdline.c */
-gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error);
+gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar chr, GError **error);
+gboolean teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ "?" help
* ?[topic]$ -- Get help for topic
@@ -384,5 +387,6 @@ gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_help,
.initial_cb = (teco_state_initial_cb_t)teco_state_help_initial,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_help_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_help_insert_completion,
.expectstring.string_building = FALSE
);
diff --git a/src/help.h b/src/help.h
index 6400399..3148e01 100644
--- a/src/help.h
+++ b/src/help.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c
index e2e4256..3e63d02 100644
--- a/src/interface-curses/curses-icons.c
+++ b/src/interface-curses/curses-icons.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-icons.h b/src/interface-curses/curses-icons.h
index c1be06f..933241d 100644
--- a/src/interface-curses/curses-icons.h
+++ b/src/interface-curses/curses-icons.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c
index e6e1549..dffbcf8 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -200,6 +200,65 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
wmove(ctx->window, bar_y, cols-1);
wattron(ctx->window, A_REVERSE);
wvline(ctx->window, ' ', bar_height);
+}
+
+/**
+ * Find the entry at the given character coordinates.
+ *
+ * @param ctx The popup widget to look up
+ * @param y The pointer's Y position, relative to the popup's window
+ * @param x The pointer's X position, relative to the popup's window
+ * @return Pointer to the entry's string under the pointer or NULL.
+ * This string is owned by the popup and is only valid until the
+ * popup is cleared.
+ *
+ * @note This must match the calculations in teco_curses_info_popup_init_pad().
+ * But we could perhaps also cache these values.
+ */
+const teco_string_t *
+teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x)
+{
+ int cols = getmaxx(stdscr); /**! screen width */
+ gint pad_cols; /**! entry columns */
+ gint pad_colwidth; /**! width per entry column */
+
+ if (y == 0)
+ return NULL;
+
+ /*
+ * With Unicode icons enabled, we reserve 2 characters at the beginning and one
+ * after the filename/directory.
+ * Otherwise 2 characters after the entry.
+ */
+ gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2;
+ pad_colwidth = MIN(ctx->longest + reserve, cols - 2);
+
+ /* pad_cols = floor((cols - 2) / pad_colwidth) */
+ pad_cols = (cols - 2) / pad_colwidth;
+
+ gint cur_col = 0;
+ for (teco_stailq_entry_t *cur = ctx->list.first; cur != NULL; cur = cur->next) {
+ teco_popup_entry_t *entry = (teco_popup_entry_t *)cur;
+ gint cur_line = cur_col/pad_cols + 1;
+
+ if (cur_line > ctx->pad_first_line+y)
+ break;
+ if (cur_line == ctx->pad_first_line+y &&
+ x > (cur_col % pad_cols)*pad_colwidth && x <= ((cur_col % pad_cols)+1)*pad_colwidth)
+ return &entry->name;
+
+ cur_col++;
+ }
+
+ return NULL;
+}
+
+void
+teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx)
+{
+ gint lines = getmaxy(stdscr);
+ gint pad_lines = getmaxy(ctx->pad);
+ gint popup_lines = MIN(pad_lines + 1, lines - 1);
/* progress scroll position */
ctx->pad_first_line += popup_lines - 1;
@@ -211,6 +270,19 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
}
void
+teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta)
+{
+ gint lines = getmaxy(stdscr);
+ gint pad_lines = getmaxy(ctx->pad);
+ gint popup_lines = MIN(pad_lines + 1, lines - 1);
+
+ ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0);
+ if (pad_lines - ctx->pad_first_line < popup_lines - 1)
+ /* show last page */
+ ctx->pad_first_line = pad_lines - (popup_lines - 1);
+}
+
+void
teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx)
{
if (ctx->window)
diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h
index a6c28a5..d845b29 100644
--- a/src/interface-curses/curses-info-popup.h
+++ b/src/interface-curses/curses-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
#include <curses.h>
#include "list.h"
+#include "string-utils.h"
#include "interface.h"
typedef struct {
@@ -49,6 +50,10 @@ void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_
const gchar *name, gsize name_len, gboolean highlight);
void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr);
+const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x);
+void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx);
+void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta);
+
static inline bool
teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx)
{
@@ -58,8 +63,17 @@ teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx)
static inline void
teco_curses_info_popup_noutrefresh(teco_curses_info_popup_t *ctx)
{
- if (ctx->window)
- wnoutrefresh(ctx->window);
+ if (!ctx->window)
+ return;
+ /*
+ * NOTE: Scinterm always redraws its window, which is
+ * equivalent to touching it, even if it didn't change.
+ * Consequently, wnoutrefresh() will always copy it to newscr.
+ * We must therefore always redraw the popup as well, so it
+ * will still overlap the Scintilla view.
+ */
+ touchwin(ctx->window);
+ wnoutrefresh(ctx->window);
}
void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx);
diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c
index c751afd..f362424 100644
--- a/src/interface-curses/curses-utils.c
+++ b/src/interface-curses/curses-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h
index 2c819ee..9f2e8f3 100644
--- a/src/interface-curses/curses-utils.h
+++ b/src/interface-curses/curses-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index f713bc1..42ffdc6 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,6 +44,10 @@
#include <glib/gprintf.h>
#include <glib/gstdio.h>
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
#include <curses.h>
#ifdef HAVE_TIGETSTR
@@ -53,6 +57,7 @@
* Some macros in term.h interfere with our code.
*/
#undef lines
+#undef buttons
#endif
#include <Scintilla.h>
@@ -355,6 +360,7 @@ static struct {
GQueue *input_queue;
teco_curses_info_popup_t popup;
+ gsize popup_prefix_len;
/**
* GError "thrown" by teco_interface_event_loop_iter().
@@ -688,6 +694,15 @@ teco_interface_init_interactive(GError **error)
#endif
/*
+ * Disables click-detection.
+ * If we'd want to discern PRESSED and CLICKED events,
+ * we'd have to emulate the same feature on GTK.
+ */
+#if NCURSES_MOUSE_VERSION >= 2
+ mouseinterval(0);
+#endif
+
+ /*
* We always have a CTRL handler on Windows, but doing it
* here again, ensures that we have a higher precedence
* than the one installed by PDCurses.
@@ -700,6 +715,11 @@ teco_interface_init_interactive(GError **error)
noecho();
/* Scintilla draws its own cursor */
curs_set(0);
+ /*
+ * This has also been observed to reduce flickering
+ * in teco_interface_refresh().
+ */
+ leaveok(stdscr, TRUE);
teco_interface.info_window = newwin(1, 0, 0, 0);
teco_interface.msg_window = newwin(1, 0, LINES - 2, 0);
@@ -781,11 +801,11 @@ teco_interface_restore_batch(void)
*/
#ifdef CURSES_TTY
if (teco_interface.stdout_orig >= 0) {
- int fd = dup2(teco_interface.stdout_orig, 1);
+ G_GNUC_UNUSED int fd = dup2(teco_interface.stdout_orig, 1);
g_assert(fd == 1);
}
if (teco_interface.stderr_orig >= 0) {
- int fd = dup2(teco_interface.stderr_orig, 2);
+ G_GNUC_UNUSED int fd = dup2(teco_interface.stderr_orig, 2);
g_assert(fd == 2);
}
#endif
@@ -1247,39 +1267,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
return TRUE;
}
-#elif defined(CURSES_TTY)
-
-static void
-teco_interface_init_clipboard(void)
-{
- /*
- * At least on XTerm, there are escape sequences
- * for modifying the clipboard (OSC-52).
- * This is not standardized in terminfo, so we add special
- * XTerm support here. Unfortunately, it is pretty hard to find out
- * whether clipboard operations will actually work.
- * XTerm must be at least at v203 and the corresponding window operations
- * must be enabled.
- * There is no way to find out if they are but we must
- * not register the clipboard registers if they aren't.
- * Still, XTerm clipboards are broken with Unicode characters.
- * Also, there are other terminal emulators supporting OSC-52,
- * so the XTerm version is only checked if the terminal identifies as XTerm.
- * Also, a special clipboard ED flag must be set by the user.
- *
- * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
- * not worth using it since it won't always be set and even if set, does not
- * tell you whether the terminal will actually answer to the escape sequence or not.
- */
- if (!(teco_ed & TECO_ED_OSC52) ||
- (teco_xterm_version() >= 0 && teco_xterm_version() < 203))
- return;
-
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
-}
+#elif defined(G_OS_UNIX) && defined(CURSES_TTY)
static inline gchar
get_selection_by_name(const gchar *name)
@@ -1294,9 +1282,48 @@ get_selection_by_name(const gchar *name)
return g_ascii_tolower(*name) ? : 'c';
}
-gboolean
-teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
- GError **error)
+/*
+ * OSC-52 clipboard implementation.
+ *
+ * At least on XTerm, there are escape sequences
+ * for modifying the clipboard (OSC-52).
+ * This is not standardized in terminfo, so we add special
+ * XTerm support here. Unfortunately, it is pretty hard to find out
+ * whether clipboard operations will actually work.
+ * XTerm must be at least at v203 and the corresponding window operations
+ * must be enabled.
+ * There is no way to find out if they are but we must
+ * not register the clipboard registers if they aren't.
+ * Still, XTerm clipboards are broken with Unicode characters.
+ * Also, there are other terminal emulators supporting OSC-52,
+ * so the XTerm version is only checked if the terminal identifies as XTerm.
+ * Also, a special clipboard ED flag must be set by the user.
+ *
+ * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
+ * not worth using it since it won't always be set and even if set, does not
+ * tell you whether the terminal will actually answer to the escape sequence or not.
+ *
+ * This is a rarely used feature and could theoretically also be handled
+ * by the $SCITECO_CLIPBOARD_SET/GET feature.
+ * Unfortunately, there is no readily available command-line utility allowing both
+ * copying and pasting via OSC-52.
+ * That's really the only reason we keep built-in OSC-52 clipboard support.
+ *
+ * FIXME: This is the only thing here requiring CURSES_TTY.
+ * On the other hand, there is hardly any non-PDCurses on UNIX, which is not
+ * on a TTY, so we shouldn't be loosing much by requiring both.
+ */
+
+static inline gboolean
+teco_interface_osc52_is_enabled(void)
+{
+ return teco_ed & TECO_ED_OSC52 &&
+ (teco_xterm_version() < 0 || teco_xterm_version() >= 203);
+}
+
+static gboolean
+teco_interface_osc52_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
{
fputs("\e]52;", teco_interface.screen_tty);
fputc(get_selection_by_name(name), teco_interface.screen_tty);
@@ -1335,8 +1362,8 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
return TRUE;
}
-gboolean
-teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+static gboolean
+teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
{
gboolean ret = TRUE;
@@ -1430,7 +1457,167 @@ cleanup:
return ret;
}
-#else /* !PDCURSES && !CURSES_TTY */
+/*
+ * Implementation using external processes.
+ *
+ * NOTE: This could be done with the portable GSpawn API as well,
+ * but this implementation is much simpler.
+ * We don't really need it on Windows anyway as long as we are using
+ * only PDCurses.
+ * This might only be of interest on Windows if building for the Win32 version
+ * of ncurses.
+ * As a downside, compared to GSpawn, this cannot inherit the environment
+ * variables from the global Q-Register table.
+ */
+
+static void
+teco_interface_init_clipboard(void)
+{
+ if (!teco_interface_osc52_is_enabled() &&
+ (!teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_SET", 22) ||
+ !teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_GET", 22)))
+ return;
+
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
+}
+
+gboolean
+teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_set_clipboard(name, str, str_len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_SET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot set clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "w");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ size_t len = fwrite(str, 1, str_len, pipe);
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ if (len < str_len) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error writing to process from %s", reg_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_get_clipboard(name, str, len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_GET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot get clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "r");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ gchar buffer[1024];
+ size_t read_len;
+
+ g_auto(teco_string_t) ret = {NULL, 0};
+
+ do {
+ read_len = fread(buffer, 1, sizeof(buffer), pipe);
+ teco_string_append(&ret, buffer, read_len);
+ } while (read_len == sizeof(buffer));
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ /*
+ * You may have to add a `|| true` for instance to xclip if it
+ * could fail for empty selections.
+ */
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ *str = ret.data;
+ *len = ret.len;
+ memset(&ret, 0, sizeof(ret));
+
+ return TRUE;
+}
+
+#else /* !PDCURSES && !G_OS_UNIX && !CURSES_TTY */
static void
teco_interface_init_clipboard(void)
@@ -1470,7 +1657,7 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
}
void
-teco_interface_popup_show(void)
+teco_interface_popup_show(gsize prefix_len)
{
if (!teco_interface.cmdline_window)
/* batch mode */
@@ -1479,9 +1666,21 @@ teco_interface_popup_show(void)
short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ teco_interface.popup_prefix_len = prefix_len;
teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg));
}
+void
+teco_interface_popup_scroll(void)
+{
+ if (!teco_interface.cmdline_window)
+ /* batch mode */
+ return;
+
+ teco_curses_info_popup_scroll_page(&teco_interface.popup);
+ teco_interface_popup_show(teco_interface.popup_prefix_len);
+}
+
gboolean
teco_interface_popup_is_shown(void)
{
@@ -1496,8 +1695,7 @@ teco_interface_popup_clear(void)
* PDCurses will not redraw all windows that may be
* overlapped by the popup window correctly - at least
* not the info window.
- * The Scintilla window is apparently always touched by
- * scintilla_noutrefresh().
+ * The Scintilla window is always touched by scintilla_noutrefresh().
* Actually we would expect this to be necessary on any curses,
* but ncurses doesn't require this.
*/
@@ -1563,13 +1761,6 @@ static void
teco_interface_refresh(void)
{
/*
- * Scintilla has been patched to avoid any automatic scrolling since that
- * has been benchmarked to be a very costly operation.
- * Instead we do it only once after every keypress.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
-
- /*
* Info window is updated very often which is very
* costly, especially when using PDC_set_title(),
* so we redraw it here, where the overhead does
@@ -1584,9 +1775,131 @@ teco_interface_refresh(void)
doupdate();
}
+#if NCURSES_MOUSE_VERSION >= 2
+
+#define BUTTON_NUM(X) \
+ (BUTTON##X##_PRESSED | BUTTON##X##_RELEASED | \
+ BUTTON##X##_CLICKED | BUTTON##X##_DOUBLE_CLICKED | BUTTON##X##_TRIPLE_CLICKED)
+#define BUTTON_EVENT(X) \
+ (BUTTON1_##X | BUTTON2_##X | BUTTON3_##X | BUTTON4_##X | BUTTON5_##X)
+
+static gboolean
+teco_interface_getmouse(GError **error)
+{
+ MEVENT event;
+
+ if (getmouse(&event) != OK)
+ return TRUE;
+
+ if (teco_curses_info_popup_is_shown(&teco_interface.popup) &&
+ wmouse_trafo(teco_interface.popup.window, &event.y, &event.x, FALSE)) {
+ /*
+ * NOTE: Not all curses variants report the RELEASED event,
+ * but may also return REPORT_MOUSE_POSITION.
+ * So we might react to all button presses as well.
+ */
+ if (event.bstate & (BUTTON1_RELEASED | REPORT_MOUSE_POSITION)) {
+ teco_machine_t *machine = &teco_cmdline.machine.parent;
+ const teco_string_t *insert = teco_curses_info_popup_getentry(&teco_interface.popup, event.y, event.x);
+
+ if (insert && machine->current->insert_completion_cb) {
+ /* successfully clicked popup item */
+ 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))
+ return FALSE;
+
+ teco_interface_popup_clear();
+ teco_interface_msg_clear();
+ teco_interface_cmdline_update(&teco_cmdline);
+ }
+
+ return TRUE;
+ }
+ if (event.bstate & BUTTON_NUM(4))
+ teco_curses_info_popup_scroll(&teco_interface.popup, -1);
+ else if (event.bstate & BUTTON_NUM(5))
+ teco_curses_info_popup_scroll(&teco_interface.popup, +1);
+
+ short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
+ short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg));
+
+ return TRUE;
+ }
+
+ /*
+ * Return mouse coordinates relative to the view.
+ * They will be in characters, but that's what SCI_POSITIONFROMPOINT
+ * expects on Scinterm anyway.
+ */
+ WINDOW *current = teco_view_get_window(teco_interface_current_view);
+ if (!wmouse_trafo(current, &event.y, &event.x, FALSE))
+ /* no event inside of current view */
+ return TRUE;
+
+ /*
+ * NOTE: There will only be one of the button bits
+ * set in bstate, so we don't loose information translating
+ * them to enums.
+ *
+ * At least on ncurses, we don't always get a RELEASED event.
+ * It instead sends only REPORT_MOUSE_POSITION,
+ * so make sure not to overwrite teco_mouse.button in this case.
+ */
+ if (event.bstate & BUTTON_NUM(4))
+ /* scroll up - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLUP;
+ else if (event.bstate & BUTTON_NUM(5))
+ /* scroll down - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ else if (event.bstate & BUTTON_EVENT(RELEASED))
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ else if (event.bstate & BUTTON_EVENT(PRESSED))
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ else
+ /* can also be REPORT_MOUSE_POSITION */
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+
+ teco_mouse.x = event.x;
+ teco_mouse.y = event.y;
+
+ if (event.bstate & BUTTON_NUM(1))
+ teco_mouse.button = 1;
+ else if (event.bstate & BUTTON_NUM(2))
+ teco_mouse.button = 2;
+ else if (event.bstate & BUTTON_NUM(3))
+ teco_mouse.button = 3;
+ else if (!(event.bstate & REPORT_MOUSE_POSITION))
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event.bstate & BUTTON_SHIFT)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event.bstate & BUTTON_CTRL)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ if (event.bstate & BUTTON_ALT)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
+#endif /* NCURSES_MOUSE_VERSION >= 2 */
+
static gint
teco_interface_blocking_getch(void)
{
+#if NCURSES_MOUSE_VERSION >= 2
+ /*
+ * FIXME: REPORT_MOUSE_POSITION is necessary at least on
+ * ncurses, so that BUTTONX_RELEASED events are reported.
+ * It does NOT report every cursor movement, though.
+ * What does PDCurses do?
+ */
+ mousemask(teco_ed & TECO_ED_MOUSEKEY
+ ? ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION : 0, NULL);
+#endif
+
/* no special <CTRL/C> handling */
raw();
nodelay(teco_interface.input_pad, FALSE);
@@ -1630,6 +1943,9 @@ teco_interface_event_loop_iter(void)
? teco_interface_blocking_getch()
: GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue));
+ const teco_view_t *last_view = teco_interface_current_view;
+ sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+
switch (key) {
case ERR:
/* shouldn't really happen */
@@ -1697,6 +2013,14 @@ teco_interface_event_loop_iter(void)
#undef FNS
#undef FN
+#if NCURSES_MOUSE_VERSION >= 2
+ case KEY_MOUSE:
+ /* ANY of the mouse events */
+ if (!teco_interface_getmouse(error))
+ return;
+ break;
+#endif
+
/*
* Control keys and keys with printable representation
*/
@@ -1740,6 +2064,14 @@ teco_interface_event_loop_iter(void)
}
}
+ /*
+ * Scintilla has been patched to avoid any automatic scrolling since that
+ * has been benchmarked to be a very costly operation.
+ * Instead we do it only once after every keypress.
+ */
+ if (teco_interface_current_view != last_view ||
+ last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0))
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
teco_interface_refresh();
}
@@ -1755,6 +2087,7 @@ teco_interface_event_loop(GError **error)
static const teco_cmdline_t empty_cmdline; // FIXME
teco_interface_cmdline_update(&empty_cmdline);
teco_interface_msg_clear();
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
teco_interface_refresh();
#ifdef EMCURSES
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index 50e5311..e731a21 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -4,7 +4,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \
AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value
noinst_LTLIBRARIES = libsciteco-interface.la
-libsciteco_interface_la_SOURCES = interface.c \
+libsciteco_interface_la_SOURCES = view.c interface.c \
gtk-info-popup.c gtk-info-popup.h \
gtk-label.c gtk-label.h
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index 4e25224..aaa0a65 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -47,12 +47,15 @@ struct _TecoGtkInfoPopup {
GtkAdjustment *hadjustment, *vadjustment;
GtkWidget *flow_box;
+ GdkCursor *cursor; /*< pointer/hand cursor */
GStringChunk *chunk;
teco_stailq_head_t list;
guint idle_id;
gboolean frozen;
};
+static guint teco_gtk_info_popup_clicked_signal;
+
static gboolean teco_gtk_info_popup_scroll_event(GtkWidget *widget, GdkEventScroll *event);
static void teco_gtk_info_popup_show(GtkWidget *widget);
static void teco_gtk_info_popup_vadjustment_changed(GtkAdjustment *vadjustment, GtkWidget *scrollbar);
@@ -72,6 +75,9 @@ teco_gtk_info_popup_finalize(GObject *obj_self)
while ((entry = teco_stailq_remove_head(&self->list)))
g_free(entry);
+ if (self->cursor)
+ g_object_unref(self->cursor);
+
/* chain up to parent class */
G_OBJECT_CLASS(teco_gtk_info_popup_parent_class)->finalize(obj_self);
}
@@ -82,6 +88,31 @@ teco_gtk_info_popup_class_init(TecoGtkInfoPopupClass *klass)
GTK_WIDGET_CLASS(klass)->scroll_event = teco_gtk_info_popup_scroll_event;
GTK_WIDGET_CLASS(klass)->show = teco_gtk_info_popup_show;
G_OBJECT_CLASS(klass)->finalize = teco_gtk_info_popup_finalize;
+
+ teco_gtk_info_popup_clicked_signal =
+ g_signal_new("clicked", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_ULONG);
+}
+
+static void
+teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpointer user_data)
+{
+ TecoGtkInfoPopup *popup = TECO_GTK_INFO_POPUP(user_data);
+
+ /*
+ * Find the TecoGtkLabel in the flow box child.
+ */
+ GtkWidget *hbox = gtk_bin_get_child(GTK_BIN(child));
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(hbox));
+ GList *entry;
+ for (entry = child_list; entry != NULL && !TECO_IS_GTK_LABEL(entry->data); entry = g_list_next(entry));
+ g_assert(entry != NULL);
+ const teco_string_t *str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data));
+
+ g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0,
+ str->data, (gulong)str->len);
}
static void
@@ -106,6 +137,8 @@ teco_gtk_info_popup_init(TecoGtkInfoPopup *self)
G_CALLBACK(teco_gtk_info_popup_vadjustment_changed), scrollbar);
self->flow_box = gtk_flow_box_new();
+ g_signal_connect(self->flow_box, "child-activated",
+ G_CALLBACK(teco_gtk_info_popup_activated_cb), self);
/* take as little height as necessary */
gtk_orientable_set_orientation(GTK_ORIENTABLE(self->flow_box),
GTK_ORIENTATION_HORIZONTAL);
@@ -311,12 +344,6 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_set_halign(label, GTK_ALIGN_START);
gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
- /*
- * FIXME: This makes little sense once we've got mouse support.
- * But for the time being, it's a useful setting.
- */
- gtk_label_set_selectable(GTK_LABEL(label), TRUE);
-
switch (type) {
case TECO_POPUP_PLAIN:
gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
@@ -331,6 +358,16 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_show_all(hbox);
gtk_container_add(GTK_CONTAINER(self->flow_box), hbox);
+
+ GtkWidget *flow_box_child = gtk_widget_get_parent(hbox);
+ g_assert(GTK_IS_FLOW_BOX_CHILD(flow_box_child));
+ GdkWindow *window = gtk_widget_get_window(flow_box_child);
+ g_assert(window != NULL);
+
+ if (G_UNLIKELY(!self->cursor))
+ /* we only initialize it now after guaranteed widget realization */
+ self->cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), "pointer");
+ gdk_window_set_cursor(window, self->cursor);
}
static gboolean
@@ -417,12 +454,10 @@ teco_gtk_info_popup_scroll_page(TecoGtkInfoPopup *self)
* Adjust this so only complete entries are shown.
* Effectively, this rounds down to the line height.
*/
- GList *child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
- if (child_list) {
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
+ if (child_list)
new_value -= (gint)new_value %
gtk_widget_get_allocated_height(GTK_WIDGET(child_list->data));
- g_list_free(child_list);
- }
/* clip to the maximum possible value */
new_value = MIN(new_value, gtk_adjustment_get_upper(adj));
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index c3a62ec..ad79b84 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
index 50cd345..ef370a2 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -269,3 +269,9 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
gtk_label_set_text(GTK_LABEL(self), plaintext);
}
+
+const teco_string_t *
+teco_gtk_label_get_text(TecoGtkLabel *self)
+{
+ return &self->string;
+}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index bed6642..c52d073 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@ G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
+const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self);
void teco_gtk_label_parse_string(const gchar *str, gssize len,
PangoColor *fg, guint16 fg_alpha,
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 0dbd2ba..7f58c45 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -60,16 +60,17 @@
//#define DEBUG
+static gboolean teco_interface_busy_timeout_cb(gpointer user_data);
+static void teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data);
static void teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation,
gpointer user_data);
static void teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str,
gpointer user_data);
-static void teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation,
+static gboolean teco_interface_input_cb(GtkWidget *widget, GdkEvent *event,
+ gpointer user_data);
+static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len,
gpointer user_data);
-static gboolean teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data);
static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event,
gpointer user_data);
static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
@@ -102,73 +103,6 @@ teco_bgr2rgb(guint32 bgr)
return GUINT32_SWAP_LE_BE(bgr) >> 8;
}
-/*
- * NOTE: The teco_view_t pointer is reused to directly
- * point to the ScintillaObject.
- * This saves one heap object per view.
- */
-
-static void
-teco_view_scintilla_notify(ScintillaObject *sci, gint iMessage,
- SCNotification *notify, gpointer user_data)
-{
- teco_view_process_notify((teco_view_t *)sci, notify);
-}
-
-teco_view_t *
-teco_view_new(void)
-{
- ScintillaObject *sci = SCINTILLA(scintilla_new());
- /*
- * We don't want the object to be destroyed
- * when it is removed from the vbox.
- */
- g_object_ref_sink(sci);
-
- scintilla_set_id(sci, 0);
-
- gtk_widget_set_size_request(GTK_WIDGET(sci), 500, 300);
-
- /*
- * This disables mouse and key events on this view.
- * For some strange reason, masking events on
- * the event box does NOT work.
- *
- * NOTE: Scroll events are still allowed - scrolling
- * is currently not under direct control of SciTECO
- * (i.e. it is OK the side effects of scrolling are not
- * tracked).
- */
- gtk_widget_set_can_focus(GTK_WIDGET(sci), FALSE);
- gint events = gtk_widget_get_events(GTK_WIDGET(sci));
- events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
- events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
- gtk_widget_set_events(GTK_WIDGET(sci), events);
-
- g_signal_connect(sci, SCINTILLA_NOTIFY,
- G_CALLBACK(teco_view_scintilla_notify), NULL);
-
- return (teco_view_t *)sci;
-}
-
-static inline GtkWidget *
-teco_view_get_widget(teco_view_t *ctx)
-{
- return GTK_WIDGET(ctx);
-}
-
-sptr_t
-teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
-{
- return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
-}
-
-void
-teco_view_free(teco_view_t *ctx)
-{
- g_object_unref(teco_view_get_widget(ctx));
-}
-
static struct {
GtkCssProvider *css_var_provider;
@@ -198,6 +132,7 @@ static struct {
GtkIMContext *input_method;
GtkWidget *popup_widget;
+ gsize popup_prefix_len;
GtkWidget *current_view_widget;
@@ -240,7 +175,7 @@ teco_interface_init(void)
G_CALLBACK(teco_interface_window_delete_cb), NULL);
g_signal_connect(teco_interface.window, "key-press-event",
- G_CALLBACK(teco_interface_key_pressed_cb), NULL);
+ G_CALLBACK(teco_interface_input_cb), NULL);
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
@@ -313,8 +248,20 @@ teco_interface_init(void)
gtk_box_pack_start(GTK_BOX(overlay_vbox), teco_interface.event_box_widget,
TRUE, TRUE, 0);
- g_signal_connect(teco_interface.event_box_widget, "size-allocate",
- G_CALLBACK(teco_interface_size_allocate_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "realize",
+ G_CALLBACK(teco_interface_event_box_realized_cb), NULL);
+
+ gint events = gtk_widget_get_events(teco_interface.event_box_widget);
+ events |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK;
+ gtk_widget_set_events(teco_interface.event_box_widget, events);
+
+ g_signal_connect(teco_interface.event_box_widget, "button-press-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "button-release-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "scroll-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
teco_interface.message_bar_widget = gtk_info_bar_new();
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
@@ -350,7 +297,7 @@ teco_interface_init(void)
/* we will forward key events, so the view should only react to text insertion */
teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALLCMDKEYS, 0, 0);
- GtkWidget *cmdline_widget = teco_view_get_widget(teco_interface.cmdline_view);
+ GtkWidget *cmdline_widget = GTK_WIDGET(teco_interface.cmdline_view);
gtk_widget_set_name(cmdline_widget, "sciteco-cmdline");
g_signal_connect(cmdline_widget, "size-allocate",
G_CALLBACK(teco_interface_cmdline_size_allocate_cb), NULL);
@@ -373,6 +320,8 @@ teco_interface_init(void)
*/
teco_interface.popup_widget = teco_gtk_info_popup_new();
gtk_widget_set_name(teco_interface.popup_widget, "sciteco-info-popup");
+ g_signal_connect(teco_interface.popup_widget, "clicked",
+ G_CALLBACK(teco_interface_popup_clicked_cb), NULL);
gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), teco_interface.popup_widget);
g_signal_connect(overlay_widget, "get-child-position",
G_CALLBACK(teco_gtk_info_popup_get_position_in_overlay), NULL);
@@ -389,6 +338,17 @@ teco_interface_init(void)
teco_interface_cmdline_update(&empty_cmdline);
}
+static void
+teco_interface_set_cursor(GtkWidget *widget, const gchar *name)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ g_assert(window != NULL);
+ GdkDisplay *display = gdk_window_get_display(window);
+
+ g_autoptr(GdkCursor) cursor = name ? gdk_cursor_new_from_name(display, name) : NULL;
+ gdk_window_set_cursor(window, cursor);
+}
+
GOptionGroup *
teco_interface_get_options(void)
{
@@ -746,12 +706,16 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
}
void
-teco_interface_popup_show(void)
+teco_interface_popup_show(gsize prefix_len)
{
- if (gtk_widget_get_visible(teco_interface.popup_widget))
- teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
- else
- gtk_widget_show(teco_interface.popup_widget);
+ teco_interface.popup_prefix_len = prefix_len;
+ gtk_widget_show(teco_interface.popup_widget);
+}
+
+void
+teco_interface_popup_scroll(void)
+{
+ teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
}
gboolean
@@ -876,7 +840,7 @@ teco_interface_set_css_variables(teco_view_t *view)
* This cannot be done via CSS or Scintilla messages.
* Currently, it is always exactly one line high in order to mimic the Curses UI.
*/
- gtk_widget_set_size_request(teco_view_get_widget(teco_interface.cmdline_view), -1, text_height);
+ gtk_widget_set_size_request(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height);
}
static void
@@ -910,19 +874,12 @@ teco_interface_refresh(gboolean current_view_changed)
gtk_container_remove(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
- teco_interface.current_view_widget = teco_view_get_widget(teco_interface_current_view);
+ teco_interface.current_view_widget = GTK_WIDGET(teco_interface_current_view);
gtk_container_add(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
gtk_widget_show(teco_interface.current_view_widget);
}
-
- /*
- * Scintilla has been patched to avoid any automatic scrolling since that
- * has been benchmarked to be a very costly operation.
- * Instead we do it only once after every keypress.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
}
static void
@@ -977,7 +934,7 @@ teco_interface_get_ansi_key(GdkEventKey *event)
static gboolean
teco_interface_handle_key_press(GdkEventKey *event, GError **error)
{
- const teco_view_t *last_view = teco_interface_current_view;
+ g_assert(event->type == GDK_KEY_PRESS);
switch (event->keyval) {
case GDK_KEY_Escape:
@@ -1105,10 +1062,81 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
gtk_im_context_filter_keypress(teco_interface.input_method, event);
}
- teco_interface_refresh(teco_interface_current_view != last_view);
return TRUE;
}
+static gboolean
+teco_interface_handle_mouse_button(GdkEventButton *event, GError **error)
+{
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ break;
+ case GDK_BUTTON_RELEASE:
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ break;
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ default:
+ /* delivered in addition to GDK_BUTTON_PRESS */
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = event->button;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
+static gboolean
+teco_interface_handle_scroll(GdkEventScroll *event, GError **error)
+{
+ g_assert(event->type == GDK_SCROLL);
+
+ /*
+ * FIXME: Do we have to support GDK_SCROLL_SMOOTH?
+ */
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ teco_mouse.type = TECO_MOUSE_SCROLLUP;
+ break;
+ case GDK_SCROLL_DOWN:
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ break;
+ default:
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
gboolean
teco_interface_event_loop(GError **error)
{
@@ -1251,6 +1279,29 @@ teco_interface_cleanup(void)
*/
/**
+ * Called some time after processing an input event in order to show
+ * business.
+ *
+ * The delay avoids cursor flickering during normal typing.
+ *
+ * @fixme It would be nicer to set the cursor for the entire window,
+ * but that would apparently require another GtkEventBox, spanning everything.
+ */
+static gboolean
+teco_interface_busy_timeout_cb(gpointer user_data)
+{
+ teco_interface_set_cursor(teco_interface.event_box_widget, "wait");
+ return G_SOURCE_REMOVE;
+}
+
+static void
+teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
+{
+ /* It's only now safe to get the GdkWindow. */
+ teco_interface_set_cursor(widget, "text");
+}
+
+/**
* Called when the commandline widget is resized.
* This should ensure that the caret jumps to the middle of the command line,
* imitating the behaviour of the current Curses command line.
@@ -1263,26 +1314,16 @@ teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
CARET_SLOP | CARET_EVEN, allocation->width/2);
}
-static void
-teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation, gpointer user_data)
-{
- /*
- * This especially ensures that the caret is visible after startup.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
-}
-
static gboolean
-teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
static gboolean recursed = FALSE;
- g_autoptr(GError) error = NULL;
#ifdef DEBUG
- g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
- event->string, *event->string,
- event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK);
+ if (event->type == GDK_KEY_PRESS)
+ g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
+ event->key.string, *event->key.string,
+ event->key.state & GDK_SHIFT_MASK, event->key.state & GDK_CONTROL_MASK);
#endif
if (recursed) {
@@ -1295,8 +1336,9 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
* during execution, but the current implementation is
* probably easier.
*/
- if (event->state & GDK_CONTROL_MASK &&
- gdk_keyval_to_upper(event->keyval) == GDK_KEY_C)
+ if (event->type == GDK_KEY_PRESS &&
+ event->key.state & GDK_CONTROL_MASK &&
+ gdk_keyval_to_upper(event->key.keyval) == GDK_KEY_C)
/*
* Handle asynchronous interruptions if CTRL+C is pressed.
* If the execution thread is currently blocking,
@@ -1305,20 +1347,26 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_interrupted = TRUE;
else
g_queue_push_tail(teco_interface.event_queue,
- gdk_event_copy((GdkEvent *)event));
+ gdk_event_copy(event));
return TRUE;
}
recursed = TRUE;
+ GSource *busy_timeout = g_timeout_source_new(500); /* ms */
+ g_source_set_callback(busy_timeout, teco_interface_busy_timeout_cb, NULL, NULL);
+ g_source_attach(busy_timeout, NULL);
+
teco_memory_start_limiting();
- g_queue_push_tail(teco_interface.event_queue, gdk_event_copy((GdkEvent *)event));
+ g_queue_push_tail(teco_interface.event_queue, gdk_event_copy(event));
GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
do {
+ g_autoptr(GError) error = NULL;
+
/*
* The event queue might be filled when pressing keys when SciTECO
* is busy executing code.
@@ -1334,10 +1382,37 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
*/
gdk_window_freeze_updates(top_window);
+ const teco_view_t *last_view = teco_interface_current_view;
+ sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+
teco_interrupted = FALSE;
- teco_interface_handle_key_press(&event->key, &error);
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ teco_interface_handle_key_press(&event->key, &error);
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ teco_interface_handle_mouse_button(&event->button, &error);
+ break;
+ case GDK_SCROLL:
+ teco_interface_handle_scroll(&event->scroll, &error);
+ break;
+ default:
+ g_assert_not_reached();
+ }
teco_interrupted = FALSE;
+ teco_interface_refresh(teco_interface_current_view != last_view);
+ /*
+ * Scintilla has been patched to avoid any automatic scrolling since that
+ * has been benchmarked to be a very costly operation.
+ * Instead we do it only once after every keypress.
+ */
+ if (last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0))
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
+
gdk_window_thaw_updates(top_window);
if (g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) {
@@ -1355,10 +1430,36 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_memory_stop_limiting();
+ g_source_destroy(busy_timeout);
+ g_source_unref(busy_timeout);
+ teco_interface_set_cursor(teco_interface.event_box_widget, "text");
+
recursed = FALSE;
return TRUE;
}
+static void
+teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data)
+{
+ g_assert(len >= teco_interface.popup_prefix_len);
+ 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;
+
+ const teco_view_t *last_view = teco_interface_current_view;
+
+ /*
+ * NOTE: It shouldn't really be necessary to catch TECO_ERROR_QUIT here.
+ * A auto completion should never result in program termination.
+ */
+ if (machine->current->insert_completion_cb &&
+ !machine->current->insert_completion_cb(machine, &insert, NULL))
+ return;
+ teco_interface_popup_clear();
+ teco_interface_cmdline_update(&teco_cmdline);
+
+ teco_interface_refresh(teco_interface_current_view != last_view);
+}
+
static gboolean
teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer user_data)
{
@@ -1374,7 +1475,7 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer
close_event->key.window = gtk_widget_get_parent_window(widget);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(widget, &close_event->key, NULL);
+ return teco_interface_input_cb(widget, close_event, NULL);
}
static gboolean
@@ -1386,5 +1487,5 @@ teco_interface_sigterm_handler(gpointer user_data)
g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(teco_interface.window, &close_event->key, NULL);
+ return teco_interface_input_cb(teco_interface.window, close_event, NULL);
}
diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c
new file mode 100644
index 0000000..ef839d6
--- /dev/null
+++ b/src/interface-gtk/view.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012-2025 Robin Haberkorn
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "view.h"
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(ScintillaObject, g_object_unref)
+
+#define TECO_TYPE_VIEW teco_view_get_type()
+G_DECLARE_FINAL_TYPE(TecoView, teco_view, TECO, VIEW, ScintillaObject)
+
+struct _TecoView {
+ ScintillaObject parent_instance;
+ /** current size allocation */
+ GdkRectangle allocation;
+};
+
+G_DEFINE_TYPE(TecoView, teco_view, SCINTILLA_TYPE_OBJECT)
+
+static void
+teco_view_scintilla_notify_cb(ScintillaObject *sci, gint iMessage, SCNotification *notify)
+{
+ teco_view_process_notify((teco_view_t *)TECO_VIEW(sci), notify);
+}
+
+/**
+ * Called when the view is size allocated.
+ *
+ * This especially ensures that the caret is visible after startup and when
+ * opening files on specific lines.
+ * It's important to scroll the caret only when the size actually changes,
+ * so we do not interfere with mouse scrolling.
+ * That callback is invoked even if the size does not change, so that's why
+ * we have to store the current allocation in teco_view_t.
+ * Calling it once is unfortunately not sufficient since the window size
+ * can change during startup.
+ */
+static void
+teco_view_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation)
+{
+ /* chain to parent class */
+ GTK_WIDGET_CLASS(teco_view_parent_class)->size_allocate(widget, allocation);
+
+ TecoView *view = TECO_VIEW(widget);
+
+ if (allocation->width == view->allocation.width && allocation->height == view->allocation.height)
+ return;
+ teco_view_ssm((teco_view_t *)view, SCI_SCROLLCARET, 0, 0);
+ memcpy(&view->allocation, allocation, sizeof(view->allocation));
+}
+
+teco_view_t *
+teco_view_new(void)
+{
+ TecoView *ctx = TECO_VIEW(g_object_new(TECO_TYPE_VIEW, NULL));
+ /*
+ * We don't want the object to be destroyed
+ * when it is removed from the vbox.
+ */
+ g_object_ref_sink(ctx);
+
+ scintilla_set_id(SCINTILLA(ctx), 0);
+
+ gtk_widget_set_size_request(GTK_WIDGET(ctx), 500, 300);
+
+ /*
+ * This disables mouse and key events on this view.
+ * For some strange reason, masking events on
+ * the event box does NOT work.
+ */
+ gtk_widget_set_can_focus(GTK_WIDGET(ctx), FALSE);
+ gint events = gtk_widget_get_events(GTK_WIDGET(ctx));
+ events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK |
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ gtk_widget_set_events(GTK_WIDGET(ctx), events);
+
+ return (teco_view_t *)ctx;
+}
+
+static void
+teco_view_class_init(TecoViewClass *klass)
+{
+ SCINTILLA_CLASS(klass)->notify = teco_view_scintilla_notify_cb;
+ GTK_WIDGET_CLASS(klass)->size_allocate = teco_view_size_allocate_cb;
+}
+
+static void teco_view_init(TecoView *self) {}
+
+sptr_t
+teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
+}
+
+void
+teco_view_free(teco_view_t *ctx)
+{
+ g_object_unref(ctx);
+}
diff --git a/src/interface.c b/src/interface.c
index 2973dd2..9ec1bed 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,6 +43,9 @@ TECO_DEFINE_UNDO_CALL(teco_interface_ssm, unsigned int, uptr_t, sptr_t);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_qreg, const teco_qreg_t *);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_buffer, const teco_buffer_t *);
+/** Last mouse event */
+teco_mouse_t teco_mouse = {0};
+
typedef struct {
teco_string_t str;
gchar name[];
diff --git a/src/interface.h b/src/interface.h
index 80da8d9..33b094b 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -132,7 +132,9 @@ typedef enum {
void teco_interface_popup_add(teco_popup_entry_type_t type,
const gchar *name, gsize name_len, gboolean highlight);
/** @pure */
-void teco_interface_popup_show(void);
+void teco_interface_popup_show(gsize prefix_len);
+/** @pure */
+void teco_interface_popup_scroll(void);
/** @pure */
gboolean teco_interface_popup_is_shown(void);
/** @pure */
@@ -141,6 +143,28 @@ void teco_interface_popup_clear(void);
/** @pure */
gboolean teco_interface_is_interrupted(void);
+typedef struct {
+ enum {
+ TECO_MOUSE_PRESSED = 1,
+ TECO_MOUSE_RELEASED,
+ TECO_MOUSE_SCROLLUP,
+ TECO_MOUSE_SCROLLDOWN
+ } type;
+
+ guint x; /*< X-coordinate relative to view */
+ guint y; /*< Y-coordinate relative to view */
+
+ gint button; /*< number of pressed mouse button or -1 */
+
+ enum {
+ TECO_MOUSE_SHIFT = (1 << 0),
+ TECO_MOUSE_CTRL = (1 << 1),
+ TECO_MOUSE_ALT = (1 << 2)
+ } mods;
+} teco_mouse_t;
+
+extern teco_mouse_t teco_mouse;
+
/** @pure main entry point */
gboolean teco_interface_event_loop(GError **error);
diff --git a/src/lexer.c b/src/lexer.c
index c0c7847..1124b99 100644
--- a/src/lexer.c
+++ b/src/lexer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/lexer.h b/src/lexer.h
index 87b0d0f..2b011be 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/list.h b/src/list.h
index 156d4a7..2a7ab91 100644
--- a/src/list.h
+++ b/src/list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/main.c b/src/main.c
index e93a3de..7849798 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -141,7 +141,7 @@ teco_process_options(gchar ***argv)
g_autoptr(GError) error = NULL;
- g_autoptr(GOptionContext) options = g_option_context_new("[--] [SCRIPT] [ARGUMENT...]");
+ g_autoptr(GOptionContext) options = g_option_context_new("[--|-S] [SCRIPT] [ARGUMENT...]");
g_option_context_set_summary(
options,
@@ -168,27 +168,48 @@ teco_process_options(gchar ***argv)
* in many situations.
* It is also strictly required to make hash-bang lines like
* #!/usr/bin/sciteco -m
- * work.
+ * work (without additional --).
*/
g_option_context_set_strict_posix(options, TRUE);
+ /*
+ * The first unknown parameter will be left in argv and
+ * terminates option parsing (see above).
+ * This means we can use "-S" as an alternative to "--",
+ * that is always preserved and passed down to the macro.
+ */
+ g_option_context_set_ignore_unknown_options(options, TRUE);
+
if (!g_option_context_parse_strv(options, argv, &error)) {
g_fprintf(stderr, "Option parsing failed: %s\n",
error->message);
exit(EXIT_FAILURE);
}
- /*
- * GOption will NOT remove "--" if followed by an
- * option-argument, which may interfer with scripts
- * doing their own option handling and interpreting "--".
- *
- * NOTE: This is still true if we're parsing in GNU-mode
- * and "--" is not the first non-option argument as in
- * sciteco foo -- -C bar.
- */
- if ((*argv)[0] && !g_strcmp0((*argv)[1], "--"))
+ if ((*argv)[0] && !g_strcmp0((*argv)[1], "-S")) {
+ /* translate -S to --, this is always passed down */
+ (*argv)[1][1] = '-';
+ } else if ((*argv)[0] && !g_strcmp0((*argv)[1], "--")) {
+ /*
+ * GOption will NOT remove "--" if followed by an
+ * option-argument, which may interfer with scripts
+ * doing their own option handling and interpreting "--".
+ * Otherwise, GOption will always remove "--".
+ *
+ * NOTE: This is still true if we're parsing in GNU-mode
+ * and "--" is not the first non-option argument as in
+ * sciteco foo -- -C bar.
+ */
g_free(teco_strv_remove(*argv, 1));
+ } else if ((*argv)[0] && (*argv)[1] && *(*argv)[1] == '-') {
+ /*
+ * GOption does not remove "--" if it is followed by "-",
+ * so if the first parameter starts with "-", we know it's
+ * not a known built-in parameter.
+ */
+ g_fprintf(stderr, "Unknown option \"%s\"\n", (*argv)[1]);
+ exit(EXIT_FAILURE);
+ }
gchar *mung_filename = NULL;
diff --git a/src/memory.c b/src/memory.c
index 653d1ef..ea056bc 100644
--- a/src/memory.c
+++ b/src/memory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/memory.h b/src/memory.h
index 39f8319..ae7b506 100644
--- a/src/memory.h
+++ b/src/memory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/parser.c b/src/parser.c
index 018e35f..295c635 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -447,11 +447,14 @@ teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunich
/* in cmdline.c */
gboolean teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+gboolean teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error);
TECO_DEFINE_STATE(teco_state_stringbuilding_start,
.is_start = TRUE,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)
- teco_state_stringbuilding_start_process_edit_cmd
+ teco_state_stringbuilding_start_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)
+ teco_state_stringbuilding_insert_completion
);
static teco_state_t *
diff --git a/src/parser.h b/src/parser.h
index 7ca5ab3..fe8e764 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -110,6 +110,7 @@ typedef gboolean (*teco_state_refresh_cb_t)(teco_machine_t *ctx, GError **error)
typedef gboolean (*teco_state_end_of_macro_cb_t)(teco_machine_t *ctx, GError **error);
typedef gboolean (*teco_state_process_edit_cmd_cb_t)(teco_machine_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+typedef gboolean (*teco_state_insert_completion_cb_t)(teco_machine_t *ctx, const teco_string_t *str, GError **error);
typedef enum {
TECO_KEYMACRO_MASK_START = (1 << 0),
@@ -187,6 +188,19 @@ struct teco_state_t {
teco_state_process_edit_cmd_cb_t process_edit_cmd_cb;
/**
+ * Insert completion after clicking an entry in the popup
+ * window.
+ *
+ * All implementations of this method are currently
+ * defined in cmdline.c.
+ *
+ * It can be NULL if not required.
+ *
+ * @fixme Perhaps move all implementations to interface.c.
+ */
+ teco_state_insert_completion_cb_t insert_completion_cb;
+
+ /**
* Whether this state is a start state (i.e. not within any
* escape sequence etc.).
* This is separate of TECO_KEYMACRO_MASK_START which is set
@@ -241,11 +255,12 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent
#define TECO_DEFINE_STATE(NAME, ...) \
/** @ingroup states */ \
teco_state_t NAME = { \
- .initial_cb = NULL, /* do nothing */ \
+ .initial_cb = NULL, /* do nothing */ \
.input_cb = (teco_state_input_cb_t)NAME##_input, /* always required */ \
- .refresh_cb = NULL, /* do nothing */ \
+ .refresh_cb = NULL, /* do nothing */ \
.end_of_macro_cb = teco_state_end_of_macro, \
.process_edit_cmd_cb = teco_state_process_edit_cmd, \
+ .insert_completion_cb = NULL, /* do nothing */ \
.is_start = FALSE, \
.keymacro_mask = TECO_KEYMACRO_MASK_DEFAULT, \
.style = SCE_SCITECO_DEFAULT, \
@@ -552,7 +567,10 @@ teco_state_t *teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar c
gboolean teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error);
/* in cmdline.c */
-gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTSTRING
@@ -577,6 +595,8 @@ gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco
.refresh_cb = (teco_state_refresh_cb_t)teco_state_expectstring_refresh, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectstring_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectstring_insert_completion, \
.keymacro_mask = TECO_KEYMACRO_MASK_STRING, \
.style = SCE_SCITECO_STRING, \
.expectstring.string_building = TRUE, \
@@ -591,6 +611,7 @@ gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_stri
/* in cmdline.c */
gboolean teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTFILE
@@ -601,12 +622,15 @@ gboolean teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_m
TECO_DEFINE_STATE_EXPECTSTRING(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectfile_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectfile_insert_completion, \
.expectstring.process_cb = teco_state_expectfile_process, \
##__VA_ARGS__ \
)
/* in cmdline.c */
gboolean teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTDIR
@@ -617,5 +641,7 @@ gboolean teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_ma
TECO_DEFINE_STATE_EXPECTFILE(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectdir_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectdir_insert_completion, \
##__VA_ARGS__ \
)
diff --git a/src/qreg-commands.c b/src/qreg-commands.c
index 0a64b2f..ebf6caa 100644
--- a/src/qreg-commands.c
+++ b/src/qreg-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/qreg-commands.h b/src/qreg-commands.h
index d999587..6dbd1c4 100644
--- a/src/qreg-commands.h
+++ b/src/qreg-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,7 +37,10 @@ gboolean teco_state_expectqreg_initial(teco_machine_main_t *ctx, GError **error)
teco_state_t *teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
/* in cmdline.c */
-gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTQREG
@@ -56,6 +59,8 @@ gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_m
.initial_cb = (teco_state_initial_cb_t)teco_state_expectqreg_initial, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectqreg_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectqreg_insert_completion, \
.style = SCE_SCITECO_QREG, \
.expectqreg.type = TECO_QREG_REQUIRED, \
.expectqreg.got_register_cb = NAME##_got_register, /* always required */ \
diff --git a/src/qreg.c b/src/qreg.c
index 271e7cb..4beea74 100644
--- a/src/qreg.c
+++ b/src/qreg.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1445,11 +1445,15 @@ teco_state_qregspec_start_input(teco_machine_qregspec_t *ctx, gunichar chr, GErr
}
/* in cmdline.c */
-gboolean teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str,
+ GError **error);
TECO_DEFINE_STATE(teco_state_qregspec_start,
.is_start = TRUE,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_insert_completion
);
static teco_state_t *
@@ -1585,9 +1589,12 @@ teco_state_qregspec_string_input(teco_machine_qregspec_t *ctx, gunichar chr, GEr
/* in cmdline.c */
gboolean teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+gboolean teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str,
+ GError **error);
TECO_DEFINE_STATE(teco_state_qregspec_string,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_string_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_string_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_string_insert_completion
);
/** @static @memberof teco_machine_qregspec_t */
diff --git a/src/qreg.h b/src/qreg.h
index a29b6ed..4260202 100644
--- a/src/qreg.h
+++ b/src/qreg.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/rb3str.c b/src/rb3str.c
index d51ac5d..276b624 100644
--- a/src/rb3str.c
+++ b/src/rb3str.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -143,7 +143,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
cur->key.data, cur->key.len, FALSE);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(str_len);
}
return prefixed_entries == 1;
diff --git a/src/rb3str.h b/src/rb3str.h
index adf5f89..466cf90 100644
--- a/src/rb3str.h
+++ b/src/rb3str.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/ring.c b/src/ring.c
index 8ce6948..0f1d67f 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -413,7 +413,7 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error)
strlen(filename), cur == teco_ring_current);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(0);
} else if (id > 0) {
allow_filename = FALSE;
if (!teco_current_doc_undo_edit(error) ||
@@ -480,6 +480,10 @@ teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* area.
* Naturally this only has any effect in interactive
* mode.
+ * Note that you can also click on these entries \(em
+ * if mouse support is enabled \(em to immediately switch
+ * to any file in the buffer ring just like with any
+ * other popup.
*
* <file> may also be a glob pattern, in which case
* all regular files matching the pattern are opened/edited.
@@ -577,3 +581,61 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* characters are enabled by default.
*/
TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file);
+
+/*$ EF close
+ * [bool]EF -- Remove buffer from ring
+ * -EF
+ * :EF
+ *
+ * Removes buffer from buffer ring, effectively
+ * closing it.
+ * If the buffer is dirty (modified), EF will yield
+ * an error.
+ * <bool> may be a specified to enforce closing dirty
+ * buffers.
+ * If it is a Failure condition boolean (negative),
+ * the buffer will be closed unconditionally.
+ * If <bool> is absent, the sign prefix (1 or -1) will
+ * be implied, so \(lq-EF\(rq will always close the buffer.
+ *
+ * When colon-modified, <bool> is ignored and \fBEF\fP
+ * will save the buffer before closing.
+ * The file is always written, unlike \(lq:EX\(rq which
+ * saves only dirty buffers.
+ * This can fail of course, e.g. when called on the unnamed
+ * buffer.
+ *
+ * It is noteworthy that EF will be executed immediately in
+ * interactive mode but can be rubbed out at a later time
+ * to reopen the file.
+ * Closed files are kept in memory until the command line
+ * is terminated.
+ */
+void
+teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
+{
+ if (teco_qreg_current) {
+ const teco_string_t *name = &teco_qreg_current->head.name;
+ g_autofree gchar *name_printable = teco_string_echo(name->data, name->len);
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Q-Register \"%s\" currently edited", name_printable);
+ return;
+ }
+
+ if (teco_machine_main_eval_colon(ctx) > 0) {
+ if (!teco_buffer_save(teco_ring_current, NULL, error))
+ return;
+ } else {
+ teco_int_t v;
+ if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
+ return;
+ if (teco_is_failure(v) && teco_ring_current->dirty) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Buffer \"%s\" is dirty",
+ teco_ring_current->filename ? : "(Unnamed)");
+ return;
+ }
+ }
+
+ teco_ring_close(error);
+}
diff --git a/src/ring.h b/src/ring.h
index 833d052..34293b9 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -100,6 +100,8 @@ void teco_ring_cleanup(void);
TECO_DECLARE_STATE(teco_state_edit_file);
TECO_DECLARE_STATE(teco_state_save_file);
+void teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error);
+
/*
* Helper functions applying to any current
* document (whether a buffer or QRegister).
diff --git a/src/sciteco.h b/src/sciteco.h
index 7fe09d4..4868303 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -89,7 +89,7 @@ enum {
TECO_ED_AUTOCASEFOLD = (1 << 3),
TECO_ED_AUTOEOL = (1 << 4),
TECO_ED_HOOKS = (1 << 5),
- //TECO_ED_MOUSEKEY = (1 << 6),
+ TECO_ED_MOUSEKEY = (1 << 6),
TECO_ED_SHELLEMU = (1 << 7),
TECO_ED_OSC52 = (1 << 8),
TECO_ED_ICONS = (1 << 9)
diff --git a/src/search.c b/src/search.c
index 1945f5c..3b2ebe3 100644
--- a/src/search.c
+++ b/src/search.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/search.h b/src/search.h
index 40ab4d8..621fdd1 100644
--- a/src/search.h
+++ b/src/search.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/spawn.c b/src/spawn.c
index fb7a946..e44ecc4 100644
--- a/src/spawn.c
+++ b/src/spawn.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -129,11 +129,13 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error)
teco_string_t comspec;
if (!reg->vtable->get_string(reg, &comspec.data, &comspec.len, NULL, error))
return NULL;
+ if (teco_string_contains(&comspec, '\0')) {
+ teco_string_clear(&comspec);
+ teco_error_qregcontainsnull_set(error, "$COMSPEC", 8, FALSE);
+ return NULL;
+ }
argv = g_new(gchar *, 5);
- /*
- * FIXME: What if $COMSPEC contains null-bytes?
- */
argv[0] = comspec.data;
argv[1] = g_strdup("/q");
argv[2] = g_strdup("/c");
@@ -148,11 +150,13 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error)
teco_string_t shell;
if (!reg->vtable->get_string(reg, &shell.data, &shell.len, NULL, error))
return NULL;
+ if (teco_string_contains(&shell, '\0')) {
+ teco_string_clear(&shell);
+ teco_error_qregcontainsnull_set(error, "$SHELL", 6, FALSE);
+ return NULL;
+ }
argv = g_new(gchar *, 4);
- /*
- * FIXME: What if $SHELL contains null-bytes?
- */
argv[0] = shell.data;
argv[1] = g_strdup("-c");
argv[2] = g_strdup(cmdline);
diff --git a/src/spawn.h b/src/spawn.h
index 312de6e..ef210e9 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/string-utils.c b/src/string-utils.c
index b284760..10e34a8 100644
--- a/src/string-utils.c
+++ b/src/string-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/string-utils.h b/src/string-utils.h
index ebe25d5..2491d07 100644
--- a/src/string-utils.h
+++ b/src/string-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/symbols.c b/src/symbols.c
index 7198639..7dcc601 100644
--- a/src/symbols.c
+++ b/src/symbols.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -155,7 +155,7 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec
strlen(entry->data), FALSE);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(symbol_len);
}
return glist_len == 1;
@@ -252,7 +252,10 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t
}
/* in cmdline.c */
-gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ ES scintilla message
* -- Send Scintilla message
@@ -332,6 +335,7 @@ gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx,
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_symbols,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_scintilla_symbols_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_scintilla_symbols_insert_completion,
.expectstring.last = FALSE
);
diff --git a/src/symbols.h b/src/symbols.h
index 0325d9d..1d0af12 100644
--- a/src/symbols.h
+++ b/src/symbols.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/undo.c b/src/undo.c
index c49405c..c8b22ab 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/undo.h b/src/undo.h
index 1d1d6fb..d7b7c8e 100644
--- a/src/undo.h
+++ b/src/undo.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/view.c b/src/view.c
index b8c72a5..df09aac 100644
--- a/src/view.c
+++ b/src/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/view.h b/src/view.h
index eebafbf..7776e5b 100644
--- a/src/view.h
+++ b/src/view.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by