aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ChangeLog137
-rw-r--r--INSTALL9
-rw-r--r--NEWS21
-rw-r--r--README6
-rw-r--r--TODO96
-rw-r--r--configure.ac27
m---------contrib/scinterm0
m---------contrib/scintilla0
-rw-r--r--contrib/scintilla.am1
-rw-r--r--debian/changelog6
-rw-r--r--debian/control5
-rw-r--r--debian/copyright2
-rwxr-xr-xdebian/rules15
-rw-r--r--doc/Makefile.am76
-rwxr-xr-xdoc/grosciteco.tes.in (renamed from doc/grosciteco.tes)0
-rw-r--r--doc/sciteco.1.in14
-rw-r--r--doc/sciteco.7.template5
-rwxr-xr-xdoc/tedoc.tes.in (renamed from doc/tedoc.tes)0
-rw-r--r--fallback.teco_inibin2322 -> 2513 bytes
-rw-r--r--freebsd/Makefile14
-rwxr-xr-x[-rw-r--r--]freebsd/files/xvfb-run.sh0
-rw-r--r--lib/color.tes16
-rw-r--r--lib/colors/solarized.tes2
-rw-r--r--lib/lexer.tes9
-rw-r--r--lib/lexers/bash.tes8
-rw-r--r--lib/lexers/batch.tes3
-rw-r--r--lib/lexers/make.tes3
-rw-r--r--lib/lexers/sciteco.tesbin697 -> 809 bytes
-rw-r--r--lib/lexers/yaml.tes2
-rw-r--r--m4/ax_ptrdiff_aliases_int.m418
-rw-r--r--m4/ax_with_ncurses.m42
-rw-r--r--src/cmdline.c330
-rw-r--r--src/cmdline.h56
-rw-r--r--src/core-commands.c125
-rw-r--r--src/core-commands.h38
-rw-r--r--src/doc.c5
-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.c21
-rw-r--r--src/file-utils.h6
-rw-r--r--src/glob.c28
-rw-r--r--src/glob.h9
-rw-r--r--src/goto-commands.c44
-rw-r--r--src/goto-commands.h6
-rw-r--r--src/goto.c2
-rw-r--r--src/goto.h2
-rw-r--r--src/help.c11
-rw-r--r--src/help.h4
-rw-r--r--src/interface-curses/curses-icons.c6
-rw-r--r--src/interface-curses/curses-icons.h2
-rw-r--r--src/interface-curses/curses-info-popup.c54
-rw-r--r--src/interface-curses/curses-info-popup.h2
-rw-r--r--src/interface-curses/curses-utils.c2
-rw-r--r--src/interface-curses/curses-utils.h5
-rw-r--r--src/interface-curses/interface.c460
-rw-r--r--src/interface-gtk/gtk-info-popup.c25
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c46
-rw-r--r--src/interface-gtk/gtk-label.h6
-rw-r--r--src/interface-gtk/interface.c245
-rw-r--r--src/interface-gtk/view.c2
-rw-r--r--src/interface.c2
-rw-r--r--src/interface.h17
-rw-r--r--src/lexer.c42
-rw-r--r--src/lexer.h16
-rw-r--r--src/list.h2
-rw-r--r--src/main.c3
-rw-r--r--src/memory.c2
-rw-r--r--src/memory.h2
-rw-r--r--src/move-commands.c6
-rw-r--r--src/move-commands.h2
-rw-r--r--src/parser.c115
-rw-r--r--src/parser.h49
-rw-r--r--src/qreg-commands.c86
-rw-r--r--src/qreg-commands.h48
-rw-r--r--src/qreg.c52
-rw-r--r--src/qreg.h2
-rw-r--r--src/rb3str.c12
-rw-r--r--src/rb3str.h2
-rw-r--r--src/ring.c168
-rw-r--r--src/ring.h30
-rw-r--r--src/sciteco.h15
-rw-r--r--src/search.c126
-rw-r--r--src/search.h16
-rw-r--r--src/spawn.c18
-rw-r--r--src/spawn.h6
-rw-r--r--src/stdio-commands.c17
-rw-r--r--src/stdio-commands.h6
-rw-r--r--src/string-utils.c44
-rw-r--r--src/string-utils.h37
-rw-r--r--src/symbols.c75
-rw-r--r--src/symbols.h4
-rw-r--r--src/undo.c2
-rw-r--r--src/undo.h2
-rw-r--r--src/view.c44
-rw-r--r--src/view.h2
-rw-r--r--tests/atlocal.in2
-rw-r--r--tests/testsuite.at13
103 files changed, 1903 insertions, 1239 deletions
diff --git a/ChangeLog b/ChangeLog
index 84a3591..fa3e6c3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,143 @@ using a prebuilt binary) are included.
Entries marked with "(!)" might break macro portability
compared to the preceding release.
+Version 2.5.0 (2026-01-01)
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+* `make install` no longer touches already installed files,
+ which may appease some packaging systems (FreeBSD ports).
+ However `make install bindir=...` and the like are no longer supported.
+* fixed clicking the "(Unnamed)" buffer in 0EB popups
+* Implemented recovery file mechanism:
+ Unsaved changes are dumped to #filename#.
+ Use n,6EJ to configure the recovery file dumping interval.
+* Fixed building on platforms with very large FILENAME_MAX (GNU/Hurd)
+* fixed rub out of file writes to non-existing symlinks
+* allow configuring the command line height using h,5EJ
+* Added ED flag 2048 to redirect Scintilla messages to the command line view:
+ enables syntax highlighting on the command line.
+* The command line macro is now managed by an ordinary Scintilla view
+ both for GTK and Curses.
+* render tabs as "TAB" (without indention) in the command-line and in
+ SciTECO macros (SciTECO lexer)
+* throw an error immediately after `nEB` if n != 0
+* ./configure --enable-static-executables now automatically pulls in static
+ libraries of libraries via pkg-config.
+* ./configure supports $CURSES_CFLAGS and $CURSES_LIBS now
+* ./configure --with-launcher=LAUNCHER can be used to run SciTECO with a launcher
+ command (e.g. wine or wine64)
+* added high-contrast color scheme (contrast.tes)
+* mention both mailing list and personal mail in `sciteco --help`
+* Work around ncurses mouse handling bugs with GNOME Terminal and Xterm.
+ This has been fixed in ncurses since version 20250913.
+* fixed parallel builds: womanpages were sometimes broken
+* Support <:O>: if a label is not found, continue execution after the go-to statement.
+ Allows to use computed gotos as select-case-like constructs.
+* bumped minimum GCC version to v8.1, Gtk to 3.24
+* bumped Scintilla to v5.5.7
+* fully support NetBSD with its native libcurses (netbsd-curses)
+* support Groff v1.19.2 as still used by default on NetBSD 10
+* fixed building on openSUSE 15.5 and 15.6
+* fix up hash-bang lines only of the scripts really installed by the current
+ `make install` invocation
+* allow messages to be of arbitrary length: fixes crashes
+* added tecat.tes to standard library: can be installed as the Git textconv filter
+* some internal refactoring and simplifications
+* Fixed serious bug with certain alternative string termination chars in commands with
+ multiple string arguments. This affected curly braces, ^A and Escape ($).
+* (!) Whitespace is now ignored in front of alternative string terminators (as in TECO-64).
+ E.g. `@I /Hello world/` is valid now.
+* implemented the ^W command for refreshing the screen in loops, for sleeping and also the
+ CTRL+L immediate editing command for forcing a complete screen redraw
+* repl.tes: added script that reproduces the classic TECO REPL command-line.
+ This is not ready, though, as it terminates on the first error.
+* `ED&2` can be used to access the program termination flag now.
+ This can be useful for checking whether a macro has run <EX> or to cancel
+ the effect of EX.
+* Fixed using the command-line replacement register (Escape, $) in batch mode:
+ was causing assertions when entering interactive mode
+* fixed a,b,c^Uq...$: The arguments where written in the wrong (reverse) order
+* ncurses: support setting the window/tab title on XTerm-like emulators
+* if <EX> fails because of a dirty buffer, the buffer's id is now included
+ in the error message
+* FreeBSD: enable dlmalloc by default (--enable-malloc-replacement) which
+ gives a 20-25% speedup
+* Special Q-registers `$` (working directory) and the clipboard registers now
+ support the append operation (:Xq, :^Uq...), i.e. you can append (or cut-append)
+ to the clipboard
+* added topics for all colon-modified commands to the online help (`?` command)
+* (!) <nEL> (set EOL mode) now sets the buffer's dirty flag, forcing you to
+ save or discard changes
+* fixed minor memory leaks during SciTECO syntax highlighting and in case
+ of end-of-macro errors
+* The primary clipboard (`~` register) is now chosen by the 10th bit in the ED flags.
+ This allows you to use the "selection" X11 clipboard as the default backend of `~`.
+* Implemented <ER> command for reading a file into the current buffer.
+ This is a Video TECO extension.
+* <EW> now accepts a numeric argument to specify the buffer to save
+* <EF> supports a numeric buffer id now
+* sciteco(7): clarified SciTECO's policy with regards to TECO-11 and Video TECO compatibility
+* sciteco(7): minor manpage fixes
+* sciteco(7): added a help topic for booleans
+* PDCurses/Wincon: disable hardware cursor after window resize
+* Improved mouse support in PDCurses v4.5.1.
+ This is in the official Windows builds.
+* GTK: implemented --detach|-d option for detaching from controlling terminal
+* GTK: fixed scrolling on systems that only support smooth scrolling
+* GTK: monospaced sections in womanpages now respect lexer.font and variable-width
+ font is configurable via lexer.woman.font (refs #34)
+* fixed ^S/^Y for search-replacement commands
+* ^S/^Y fixed for <Gq> and <EN>
+* ^S/^Y calculates the glyph offsets earlier, so that deletions after an insert or search
+ no longer affect the results.
+ This also fixes querying ranges after <FD>.
+* added <FN> as a search-and-replace variant of <N>
+* Refactored some lexer configurations to make them more pleasurable to look at.
+ Most text should always be in the default colors.
+* SciTECO lexer: style comma, braces and two-character operators as operators
+* SciTECO lexer: now tries to avoid unnecessary restylings by styling
+ from the current line as well
+* YAML lexer: default to 2 character soft tabs
+* added LaTeX lexer config
+* added --quiet, --stdin and --stdout for easier integration into UNIX pipelines
+* Improved DEC TECO compatibility - makes SciTECO much more usable as a scripting language.
+ * (!) <nA> and <nQq> now return -1 in case the index n is out of range
+ * (!) <EI> has been repurposed and is the macro file inclusion (indirect file) command now.
+ <EM> is deprecated.
+ Pre-v2.5.0 <EI> can be replaced with `I^P`.
+ * (!) <^C> is a plain "return" command now, while <^C^C> exits from the program
+ * implemented <^B> for returning the current date
+ * implemented ^E<code> string building constructs for embedding bytes and codepoints
+ in a strtoul()-like manner
+ * support <:]q> (pop Q-Register) for getting a success/failure boolean
+ * support <==> and <===> for printing octal and hexadecimal numbers
+ * support :=/:==/:=== commands: print number without trailing linefeed
+ * Implemented the <^A> command for printing arbitrary strings.
+ You can use :^A to force raw ANSI output.
+ * implemented <:Gq> for printing the Q-Register string as a message instead of inserting it
+ * implemented the <T> (typeout) command for printing to the terminal from the current buffer
+ * implemented ^T command: allows typing by code and getting characters from stdin or the user
+ * implemented ^H command for returning the current time since midnight.
+ :^H returns the seconds since the UNIX epoch and ::^H the monotonic time in microseconds
+ (useful for benchmarking).
+ * added -v/--version and <EO> command to query the program version
+ * (!) the computed go-to command (O) is now 0-indexed and all invalid indexes and
+ empty labels are ignored
+* (!) Command-line arguments are no longer passed via the unnamed buffer,
+ but via special Q-registers ^Ax.
+ This introduces one point of incompatibility with DEC TECO.
+* new string building construct ^P disables all further string building magic
+* allow process exit status to be determined by macros
+* python lexer: fixed block comment styling
+* disallow command-line termination ($$) while editing the command-line replacement
+ register (after `{`)
+* fixed rubbing out stack operations in macro calls (was causing memory violations)
+* opener.tes: Fixed +line,column syntax
+* fnkeys.tes: support folding via F1 and clicks in the folding margin
+* implemented email and "git" lexer folding, as well as folding in womanpages and for
+ the SciTECO language lexer
+* There is an Alpine Linux package in the "community" repository now.
+
Version 2.4.0 (2025-04-19)
~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/INSTALL b/INSTALL
index ad5ee9e..b099247 100644
--- a/INSTALL
+++ b/INSTALL
@@ -75,6 +75,11 @@ On NetBSD:
$ sudo pkgin install git gmake pkg-config autoconf automake libtool-base \
glib2 gtk3+ doxygen
+On Haiku:
+
+ $ pkgman install git make pkgconfig autoconf automake libtool \
+ glib2_devel ncurses6_devel gtk3_devel groff doxygen
+
Building from Source Tar Ball or Repository
===========================================
@@ -104,6 +109,7 @@ If you already have a Git clone of the SciTECO repository and
want to update it, you should issue the following commands:
$ git pull
+ $ git submodule sync
$ git submodule update
When building from Git, you must first generate the ./configure
@@ -139,6 +145,9 @@ To install SciTECO, type something like:
$ sudo make install
+Overwriting installation directories at install-time (make install bindir=...)
+is not supported.
+
You are recommended to use the included "fallback.teco_ini" as a starting
point for your profile. On UNIX, you can copy it to your $HOME directory
at "~/.teco_ini" while on Windows, it should be in the same directory
diff --git a/NEWS b/NEWS
index 64d11d5..4382d33 100644
--- a/NEWS
+++ b/NEWS
@@ -2,19 +2,8 @@ News
====
<span class="nf nf-md-new_box"></span>
-The new official website is at [sciteco.fmsbw.de](https://sciteco.fmsbw.de/).
-Bug reporting and discussions take place via [mailing lists](https://git.fmsbw.de/?p=about) now.
-
-<span class="nf nf-md-new_box"></span>
-Releases, nightly builds, both as repositories and binary downloads for RPM-based distributions
-(Fedora, openSUSE etc.) and Debian-based distributions (Debian, Raspbian, Ubuntu) are now provided via
-[Open Build Service repositories](https://build.opensuse.org/package/show/home:rhaberkorn:sciteco):
-
-| | | | |
-| --- | --- | --- | --- |
-| **Releases** | [Common](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:STABLE&package=sciteco-common) | [Gtk](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:STABLE&package=sciteco-gtk) | [Curses](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:STABLE&package=sciteco-curses) |
-| **Nightlies** | [Common](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:UNSTABLE&package=sciteco-common) | [Gtk](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:UNSTABLE&package=sciteco-gtk) | [Curses](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:UNSTABLE&package=sciteco-curses) |
-
-The different links are only relevant when downloading packages - there are only two different OBS repositories per distribution.
-This deprecates the [Ubuntu PPA repository](https://launchpad.net/~robin-haberkorn/+archive/sciteco).
-FreeBSD, Windows and Mac OS binaries are still provided in the [download archive](https://sciteco.fmsbw.de/downloads).
+SciTECO [v2.5.0](https://sciteco.fmsbw.de/downloads/v2.5.0/) has been released.
+This release brings many new features, but most importantly
+makes the language much more usable as a non-interactive scripting language.
+Also, the command-line is syntax highlighted now and recovery files are written
+to help you recover from crashes and other unexpected terminations.
diff --git a/README b/README
index 9c6f923..d12140e 100644
--- a/README
+++ b/README
@@ -27,7 +27,7 @@ All X/Open-compatible libraries should be supported.
SVr4 curses without enhanced definitions is **not** supported.
Linux, FreeBSD, NetBSD, [Mac OS X](https://sciteco.fmsbw.de/knowledge/Mac%20OS%20Support),
-Windows (MinGW 32/64) ~~and [Haiku](https://www.haiku-os.org/) (gcc4)~~ are tested and supported.
+Windows (MinGW 32/64) and [Haiku](https://www.haiku-os.org/) (gcc4) are tested and supported.
SciTECO compiles with both GCC and Clang.
SciTECO should compile just fine on other UNIX-compatible platforms.
However UNIX-compatibility is not strictly required:
@@ -73,6 +73,9 @@ Features
This makes it even harder to destroy work by accident than in most other
editors.
Rubbed out commands can be re-inserted (redo).
+* Timing-based recovery mechanism:
+ Modified buffers are regularily dumped into **#**_files_**#** to protect against
+ crashes and unexpected restarts etc.
* Munging: Macros may be munged, that is executed in batch mode. In other words, SciTECO
can be used for scripting.
By default, a profile is munged.
@@ -105,6 +108,7 @@ Features
* Syntax highlighting, styles, line numbers, folding, etc. thanks to Scintilla, Lexilla and Scintillua.
Low-level Scintilla commands can also be accessed to extend SciTECO.
SciTECO even syntax highlights code, written in the SciTECO language itself.
+* Configurable command line with syntax highlighting.
* A growing standard library of macros with frameworks for color schemes, syntax highlighting
and buffer sessions.
Optimized for hack-ability rather than completeness.
diff --git a/TODO b/TODO
index 234275c..b2f5821 100644
--- a/TODO
+++ b/TODO
@@ -1,12 +1,13 @@
Tasks:
* Have a look at TECO-86.
* VEDIT and PMATE for MS-DOS
- * Update to Scinterm 5.5.
- Perhaps we can make use of the arbitrary RGB color feauture?
- * Macro to get the current word at dot (or by numeric argument)
- similar to double right click.
+ * Scintilla: upstream 2 patches
Known Bugs:
+ * OBS GTK builds sometimes fail: "cannot open display"
+ Obviously, xvfb-run isn't reliable or has race conditions.
+ * In multiline command lines, the asterisk can scroll out of
+ view. Perhaps it has to be drawn independently of Scinterm.
* FreeBSD's `crontab -e` is not compatible with SciTECO's savepoint mechanism.
* ncurses: GNOME Terminal and Xterm produces BUTTON3_PRESSED (without BUTTON3_RELEASED)
events when scrolling horizontally.
@@ -84,7 +85,7 @@ Known Bugs:
when using Solarized. Affects e.g. the message line which uses the
reverse of STYLE_DEFAULT.
Perhaps we must call init_color() before initializing color pairs
- (currently done by Scinterm).
+ (currently done first by Scinterm).
* Saving another user's file will only preserve the user when run as root.
Generally, it is hard to ensure that a) save point files can be created
and b) the file mode and ownership of re-created files can be preserved.
@@ -165,9 +166,6 @@ Known Bugs:
be set/disabled.
This doesn't even work with SCI_SETPROPERTY, probably since
we do lexing "in the container".
- * Mac OS: The colors are screwed up with the terminal.tes color scheme
- (and with --no-profile) under Mac OS terminal emulators.
- This does not happen under Linux with Darling.
* File name autocompletion should take glob patterns into account.
The simple reason is that if a filename really contains glob characters
and you are trying to open it with EB, you might end up not being
@@ -223,6 +221,8 @@ Features:
Old Docbook documents are sometimes SGML based.
* Folding support: Perhaps there should also be builtin commands
[:]F+ and [:]F-
+ The mouse events in fnkeys.tes would have to edit the
+ commandline, though.
* Gtk: special key macros for drag-and-drop interactions?
This could be used to send the tabs of one SciTECO process into another
instance and close the sender instance.
@@ -323,6 +323,10 @@ Features:
insertion commands by automatically terminating the command.
Even more simple, the function key flag could be effective
only when the termination character is $.
+ Instead of reserving a character, we could also reserverve
+ a bit in the function key maks.
+ It's however impossible to reliably return to the start state
+ from arbitrary parser states.
* Support more function keys.
We can define more function keys via define_key(3NCURSES).
Unfortunately they are not really standardized - st and urxvt for instance
@@ -364,6 +368,8 @@ Features:
One advantage in comparison to ::S
(which also supports arguments in SciTECO), would be the ability
to bound comparisons by line with n:FB.
+ * FRtext$ is equivalent to ^SDItext$ in DEC TECO.
+ If we adopt these semantics we should of course prefer ^YDItext$.
* Searches can extend beyond the given bounds in DEC TECO
as long as they start within the range.
That's why ::S is equivalent to .,.:FB in DEC TECO.
@@ -451,15 +457,6 @@ Features:
On Video TECO it appears to free the current Q-Reg, which probably
makes more sense than the current semantics.
Should be changed along with implementing FQq.
- * multiline commandline
- * Perhaps use Scintilla view as mini buffer.
- This means patching Scintilla, so it does not break lines
- on new line characters and we can use character representations
- (extend SCI_SETLINEENDTYPESALLOWED?).
- Also, we cannot currently force ^I to be rendered with representations.
- cmdline.c can then directly operate on the Scintilla document.
- * A Scintilla view will allow syntax highlighting
- * These Scintilla enhancements will also improve hex mode (M#hx).
* command line could highlight dead branches (e.g. gray them out)
* Perhaps add a ^E register analogous to ":", but working with
byte offsets. This would mainly be useful in ^E\^E.
@@ -492,11 +489,12 @@ Features:
* Touch restored save point files - should perhaps be configurable.
This is important when working with Makefiles, as make looks
at the modification times of files.
- * There should really be a backup mechanism. It would be relatively
- easy to implement portably, by using timeout() on Curses.
- The Gtk version can simply use a glib timer.
- Backup files should NOT be hidden and the timeout should be
- configurable (EJ?).
+ * Could we somehow offer to open #recovery# files?
+ This would require a hook on interactive startup and/or after
+ command-line termination.
+ Also, we'd need a command to fetch the modification timestamp
+ as well.
+ * Recovery files should probably be hidden during auto completions.
* Error handling in SciTECO macros: Allow throwing errors with
e.g. [n]^F<description>^F where n is an error code, defaulting
to 0 and description is the error string - there could be code-specific
@@ -762,8 +760,47 @@ Features:
often. If <pad> is negative it might perform a right padding with
spaces.
To format a hex byte, you would write 16^R 2,Qa\ ^D.
- The same extension might be useful for =/==/===.
- * OpenVMS port. Just for the fun of it.
+ The same extension might not be desirable for =/==/===, unless
+ they are colon-modified?.
+ * Curses: allow freely using RGB colors instead of only the constants
+ corresponding with the 16 default curses colors.
+ Scinterm v5.5 supports that. This means we also need a hash map for
+ mapping RGB color values to curses color numbers, analoguous to what
+ Scinterm does.
+ The problem of Scinterm reusing the same color namespace both for
+ arbitrary RGB values and for predefined special constants however
+ hasn't been solved yet.
+ * Scinterm: INDIC_PLAIN and INDIC_STRIKE could theoretically be supported.
+ PDCurses has A_STRIKETHROUGH. This would require Scintilla API
+ changes, though, as it currently calls SurfaceImpl::FillRectangle(),
+ which cannot be reliably identified as small horizontal lines.
+ We'd have to patch Scintilla APIs as well.
+ * Auto-completion popup should perhaps be above the line with the cursor
+ in multi-line command lines.
+ This would be tricky to do in Gtk, though since we're currently using
+ an overlay.
+ * <EB> could create directories on demand and clean them up on rubout.
+ Unless additional files appeared in the meantime, in which case we should
+ output a warning.
+ * Savepoint and recovery files could fall back to ~/ if writing in the same
+ directory is not possible.
+ Or savepoint files should perhaps fall back to /tmp.
+ * ^E@q could be a generic escape mechanism instead of having separate
+ constructs like ^E@q and ^ENq (for globbing).
+ You rarely need both in the same command and we would need potentially
+ many more variants, especially for escaping search patterns.
+ So ^E@q could just do the right thing in the right context via a
+ teco_state_expectstring_t callback (defaulting to ^EQq).
+ On Windows without POSIX-Shell emulation, it could also properly
+ escape for cmd.exe.
+ On the downside, ^E@q could no longer be used to construct shell
+ scripts in the buffer or Q-registers.
+ * session.tes: Allow sessions to be opened after startup and
+ navigating into the correct directory with FG.
+ This would help with non-terminal-based workflows.
+ * There could be a key macro script for my Russian-phonetic
+ keyboard layout. This would probably require some new
+ key macro states as well.
Optimizations:
* Use SC_DOCUMENTOPTION_STYLES_NONE in batch mode.
@@ -804,6 +841,9 @@ Optimizations:
using __VA_OPT__().
* A few macros like TECO_CTL_KEY() could be turned into
constexpr functions.
+ * teco_state_t should be a constexpr, so that static
+ assertions are guaranteed to work on them.
+ This means you can get rid of the TECO_ASSERT_SAFE() hack.
* Compound literals could be abused for default values in the
Scintilla SSM functions. All the wrapper functions would have to be
turned into macros, though.
@@ -878,6 +918,12 @@ Optimizations:
* fnkeys.tes: Don't insert (0C) or (0R).
* tecat.tes is too slow even though it doesn't even use Q-reg strings.
An ideal test case to study.
+ * the process_edit_cmd() callbacks from cmdline.c should probably
+ be in a separate compilation unit.
+ * Perhaps EOL normalization can be avoided by letting teco_view_glyphs2bytes() &
+ co take care of it.
+ However insertion commands would also have to take care of expanding
+ LF to the buffers EOL sequence.
Documentation:
* Doxygen docs could be deployed on Github pages
@@ -924,3 +970,5 @@ Documentation:
controlled by the SciTECO language and other state (scroll
position, window size, folding) and what that all means
for writing robust macros.
+ * Knowledge Base: Document how to open SciTECO from file managers,
+ so that sessions are initialized correctly.
diff --git a/configure.ac b/configure.ac
index 9d5f74d..0f74e40 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.65])
-AC_INIT([SciTECO], [2.4.0],
+AC_INIT([SciTECO], [2.5.0],
[hackers@fmsbw.de],
[sciteco],
[https://sciteco.fmsbw.de/])
@@ -101,6 +101,13 @@ AC_PROG_CXX([c++ g++ clang++])
AX_CXX_COMPILE_STDCXX(17, noext, mandatory)
AC_CHECK_TOOL(AR, ar)
+# If ptrdiff_t does not alias int, we need a workaround
+# in Scintilla.
+AX_PTRDIFF_ALIASES_INT
+if [[ "x$ax_cv_ptrdiff_aliases_int" = "xno" ]]; then
+ SCINTILLA_CXXFLAGS="$SCINTILLA_CXXFLAGS -DPTRDIFF_DOESNT_ALIAS_INT"
+fi
+
# Whether $CC is Clang
AM_CONDITIONAL(CLANG, [$CC --version | $GREP -i clang >/dev/null])
@@ -206,7 +213,9 @@ AC_CHECK_FUNCS([memset setlocale strchr strrchr fstat sscanf], , [
# glib defines G_OS_UNIX instead...
case $host in
*-*-linux* | *-*-*bsd* | *-*-darwin* | *-*-cygwin* | *-*-haiku*)
- AC_CHECK_FUNCS([realpath readlink pathconf fchown dup dup2 getpid open read kill mmap popen pclose isatty], , [
+ # NOTE: Keep this on a single line for compatibility
+ # with ancient versions of Autoconf.
+ AC_CHECK_FUNCS([realpath readlink pathconf fchown dup dup2 getpid open read kill mmap popen pclose isatty fork setsid], , [
AC_MSG_ERROR([Missing libc function])
])
AC_SEARCH_LIBS(dladdr, [dl], , [
@@ -325,6 +334,8 @@ case $INTERFACE in
xcurses)
AC_CHECK_PROG(XCURSES_CONFIG, xcurses-config, xcurses-config)
+ # NOTE: `xcurses-config --cflags` is expected to set
+ # -DPDC_WIDE and -DPDC_FORCE_UTF8 (if these features are enabled).
if [[ "x$CURSES_CFLAGS" = "x" -a "x$XCURSES_CONFIG" != "x" ]]; then
CURSES_CFLAGS=`$XCURSES_CONFIG --cflags`
fi
@@ -344,6 +355,10 @@ case $INTERFACE in
# It is crucial to define XCURSES before including curses.h.
AC_DEFINE(XCURSES, 1, [Enable PDCurses/XCurses extensions])
+ AC_CHECK_FUNC([has_mouse], [
+ AC_DEFINE(PDC_NCMOUSE, 1, [PDCurses built with ncurses mouse API])
+ ])
+
AC_DEFINE(PDCURSES_GUI, 1, [PDCurses with GUI window])
;;
@@ -443,7 +458,13 @@ AC_ARG_ENABLE(malloc-replacement,
[Replace the libc malloc() [default=check]]),
[malloc_replacement=$enableval], [malloc_replacement=check])
if [[ $malloc_replacement = check ]]; then
- # We currently do not support dlmalloc on Windows and Mac OS.
+ # malloc() replacement via dlmalloc should work practically everywhere
+ # but does not extend to shared libraries on Windows and Mac OS.
+ # That's why it is disabled by default on Windows and Mac OS where other cheap ways
+ # of introspection are available (see memory.c).
+ # On the remaining platforms you can try to combine --enable-malloc-replacement
+ # with --enable-static-executables to link in as many libraries statically
+ # as possible.
case $host in
*-*-darwin* | *-mingw*) malloc_replacement=no;;
*) malloc_replacement=yes;;
diff --git a/contrib/scinterm b/contrib/scinterm
-Subproject c2186fb728bb207b74a6dc1e502adc8e68c08f6
+Subproject 26f5d3f5290a2910e44dec16d05da2db33c1272
diff --git a/contrib/scintilla b/contrib/scintilla
-Subproject 7c7d68414f408e83c0b17088d92212fc10883ea
+Subproject b64652b857d3a5922c72ccc801ac77aa9cff27c
diff --git a/contrib/scintilla.am b/contrib/scintilla.am
index d24e5bb..8b6a531 100644
--- a/contrib/scintilla.am
+++ b/contrib/scintilla.am
@@ -25,6 +25,7 @@ MAKE_SCINTILLA = $(MAKE) -C @top_builddir@/contrib/scintilla/bin \
-f @SCINTERM_PATH@/Makefile \
srcdir=@SCINTERM_PATH@ basedir=@SCINTILLA_PATH@ \
scintilla=$(LIBSCINTILLA) \
+ CFLAGS='@SCINTILLA_CXXFLAGS@' \
CXXFLAGS='@SCINTILLA_CXXFLAGS@' \
CURSES_FLAGS='@CURSES_CFLAGS@'
endif
diff --git a/debian/changelog b/debian/changelog
index edd4ca3..0923f61 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+sciteco (2.5.0-0) unstable; urgency=low
+
+ * new upstream version v2.5.0
+
+ -- Robin Haberkorn <rhaberkorn@fmsbw.de> Wed, 31 Dec 2025 19:49:12 +0000
+
sciteco (2.4.0-0) unstable; urgency=low
* new upstream version v2.4.0
diff --git a/debian/control b/debian/control
index 8acd8b3..0237784 100644
--- a/debian/control
+++ b/debian/control
@@ -2,10 +2,11 @@ Source: sciteco
Section: editors
Priority: optional
Maintainer: Robin Haberkorn <rhaberkorn@fmsbw.de>
-Build-Depends: debhelper (>= 10), dh-exec, gcc (>= 4:8.1), g++ (>= 4:8.1),
+Build-Depends: debhelper (>= 10), dh-exec, dh-autoreconf,
+ gcc (>= 4:8.1), g++ (>= 4:8.1),
libglib2.0-dev (>= 2.44),
ncurses-term, libncurses-dev,
- libgtk-3-dev (>= 3.24), xvfb,
+ libgtk-3-dev (>= 3.24), xvfb, xauth,
groff (>= 1.19.2)
Standards-Version: 4.5.0
Homepage: https://sciteco.fmsbw.de/
diff --git a/debian/copyright b/debian/copyright
index f6c8ab2..b8908ae 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: rhaberkorn@fmsbw.de
Source: https://git.fmsbw.de/sciteco
Files: *
-Copyright: Copyright 2012-2025 Robin Haberkorn <rhaberkorn@fmsbw.de>
+Copyright: Copyright 2012-2026 Robin Haberkorn <rhaberkorn@fmsbw.de>
License: GPL-3
/usr/share/common-licenses/GPL-3
diff --git a/debian/rules b/debian/rules
index 5ce058d..cd684aa 100755
--- a/debian/rules
+++ b/debian/rules
@@ -27,18 +27,24 @@ MAKEFLAGS += -j$(NUMJOBS)
endif
# In case `make check` fails, there will be a complete log.
-export TESTSUITEFLAGS="--verbose --color=never"
+# FIXME: Not all distributions appear to support --color=never.
+#export TESTSUITEFLAGS="--verbose --color=never"
+export TESTSUITEFLAGS="--verbose"
%:
dh $@
build build-arch build-indep: build-curses-stamp build-gtk-stamp;
+autoreconf-stamp:
+ dh_testdir
+ test -x ./configure || dh_autoreconf
+ touch $@
+
# NOTE: The datadir will be relative to the binary location at runtime.
# This makes the binary relocateable, which is important when creating
# AppImages from the Debian packages.
-build-curses-stamp:
- dh_testdir
+build-curses-stamp: autoreconf-stamp
rm -rf build-curses
dh_auto_configure -Bbuild-curses -- \
--with-interface=ncurses \
@@ -49,8 +55,7 @@ build-curses-stamp:
# NOTE: This does not depend on install-curses-stamp and uses
# --disable-bootstrap to speed up builds since that would prevent parallel builds.
-build-gtk-stamp:
- dh_testdir
+build-gtk-stamp: autoreconf-stamp
rm -rf build-gtk
dh_auto_configure -Bbuild-gtk -- \
--program-prefix=g \
diff --git a/doc/Makefile.am b/doc/Makefile.am
index 9929099..f5b3d03 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -7,21 +7,20 @@ include $(top_srcdir)/bootstrap.am
# code.
# It generates Troff manpage markup and acts as a Troff
# preprocessor to manpage templates.
-dist_bin_SCRIPTS = tedoc.tes
-
-# Name of tedoc.tes after installation, taking --program-prefix
-# into account.
-TEDOC_INSTALLED = \
- $(bindir)/`echo tedoc.tes | @SED@ '$(transform)'`
+bin_SCRIPTS = tedoc.tes
+CLEANFILES = tedoc.tes
+dist_noinst_SCRIPTS = tedoc.tes.in
%.in : %.template tedoc.tes @top_srcdir@/src/*.c
- $(SCITECO_FULL) -m -- @srcdir@/tedoc.tes \
+ $(SCITECO_FULL) -m -- tedoc.tes \
-C $@ $< @top_srcdir@/src/*.c
# grosciteco is a troff postprocessor similar to grotty
# which can be used to produce SciTECO-friendly output
# (woman pages).
-dist_bin_SCRIPTS += grosciteco.tes
+bin_SCRIPTS += grosciteco.tes
+CLEANFILES += grosciteco.tes
+dist_noinst_SCRIPTS += grosciteco.tes.in
# grosciteco, like many other postprocessors, works
# best with its own macro package.
# Unfortunately, there is no way to query the built-in
@@ -32,34 +31,24 @@ dist_bin_SCRIPTS += grosciteco.tes
# the directory via `groff -M`.
dist_scitecodata_DATA = sciteco.tmac
-# Name of grosciteco.tes after installation, taking --program-prefix
-# into account.
-GROSCITECO_INSTALLED = \
- $(bindir)/`echo grosciteco.tes | @SED@ '$(transform)'`
-
-# Fix up the hash-bang line of installed SciTECO scripts upon
-# installation to refer to the installed sciteco binary.
-# This takes --program-prefix into account.
-# This wouldn't work on Windows since SciTECO cannot open
-# the /mingw64 paths.
-#
-# FIXME: This will patch the hash-bang line repeatedly.
-# Perhaps it would be better to do that using a preprocessing
-# step before installation - see the known problems that this
-# is causing on FreeBSD/Poudriere.
-if !WIN32
-install-exec-hook:
- $(SCITECO_FULL) -e "<%i^[ :Q[^A^E\\i]:; @EB'^EN[^A^E\\i]' \
- 0,^Q::@FR'#!^EM^Xsciteco'#!$(SCITECO_INSTALLED)'> \
- :EX" \
- $(DESTDIR)$(GROSCITECO_INSTALLED) \
- $(DESTDIR)$(TEDOC_INSTALLED)
-endif
+# Fix up the hash-bang line of installed SciTECO scripts.
+# This takes --prefix and --program-prefix into account.
+# On the other hand, since this is run at build time,
+# it breaks bindir-overwriting at `make install` time,
+# which could only be made to work with an install-exec-hook.
+# Patching installed scripts however broke the FreeBSD port
+# builds with Poudriere where BINMODE=555 is set.
+# It was therefore decided to sacrifice the rarely used
+# `make install bindir=...` and appease the FreeBSD port
+# committers instead.
+%.tes : %.tes.in
+ $(SCITECO_FULL) -qioe '0,^Q::@FR/#!^EM^Xsciteco/#!^EQ[^A1]/^[' \
+ $(SCITECO_INSTALLED) <$< >$@
womendir = $(scitecolibdir)/women
women_DATA = grosciteco.tes.1.woman grosciteco.tes.1.woman.tec
-CLEANFILES = grosciteco.tes.1
+CLEANFILES += grosciteco.tes.1
EXTRA_DIST = grosciteco.tes.1.in
women_DATA += tedoc.tes.1.woman tedoc.tes.1.woman.tec
@@ -80,26 +69,21 @@ EXTRA_DIST += tutorial.ms.in
CLEANFILES += $(women_DATA)
-# Using pattern rules is bugged in some versions of GNU make in
-# combination with grouped targets (&:).
-define woman_man_rule
-$(1).woman $(1).woman.tec &: $(1) sciteco.tmac grosciteco.tes
- @GROFF@ @GROFF_FLAGS@ -wall -Z -Tutf8 -t -man -M@srcdir@ -msciteco $$< | \
- $(SCITECO_FULL) -im -- @srcdir@/grosciteco.tes $$@
-endef
+# NOTE: grosciteco.tes generates two artifacts, but two targets in one rule would be independent.
+# Grouped targets (&:) on the other hand are unreliable/buggy.
+%.woman.tec : %.woman;
-$(eval $(call woman_man_rule,grosciteco.tes.1))
-$(eval $(call woman_man_rule,tedoc.tes.1))
-$(eval $(call woman_man_rule,sciteco.1))
-$(eval $(call woman_man_rule,sciteco.7))
+%.woman : % sciteco.tmac grosciteco.tes
+ @GROFF@ @GROFF_FLAGS@ -wall -Z -Tutf8 -t -man -M@srcdir@ -msciteco $< | \
+ $(SCITECO_FULL) -im -- grosciteco.tes $@
-tutorial.woman tutorial.woman.tec &: tutorial.ms sciteco.tmac grosciteco.tes
+tutorial.woman : tutorial.ms sciteco.tmac grosciteco.tes
@GROFF@ @GROFF_FLAGS@ -wall -Z -Tutf8 -t -ms -M@srcdir@ -msciteco $< | \
- $(SCITECO_FULL) -im -- @srcdir@/grosciteco.tes $@
+ $(SCITECO_FULL) -im -- grosciteco.tes $@
man_MANS = grosciteco.tes.1 tedoc.tes.1 sciteco.1 sciteco.7
-dist_noinst_SCRIPTS = htbl.tes
+dist_noinst_SCRIPTS += htbl.tes
if BUILD_HTMLDOCS
html_DATA = grosciteco.tes.1.html tedoc.tes.1.html \
diff --git a/doc/grosciteco.tes b/doc/grosciteco.tes.in
index c4f16e1..c4f16e1 100755
--- a/doc/grosciteco.tes
+++ b/doc/grosciteco.tes.in
diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in
index 0ed9f85..e47ca93 100644
--- a/doc/sciteco.1.in
+++ b/doc/sciteco.1.in
@@ -450,12 +450,22 @@ and opening files specified on the command line.
.B $SCITECOPATH/*.tes
Standard library macros.
.TP
-.SCITECO_TOPIC savepoint
+.BI # filename #
+Recovery file:
+After a configurable recovery interval \*(ST dumps up all modified buffers
+(unsaved changes), that have not been dumped previously.
+These files should be ignored by version control systems and may
+be left around after crashes and unexpected restarts.
+See \fB6EJ\fP for more details.
+.TP
+.SCITECO_TOPIC savepoint backup
.BI .teco- n - filename ~
Save point files created by \*(ST when saving files
during interactive execution have this format.
On Windows, these files have the hidden attribute set.
-Savepoint files are temporary and should be ignored by version
+They are internally used when rubbing out file saves
+and are conceptually similar to backup files in other editors.
+However they are temporary and should be ignored by version
control systems, etc.
.TP
.SCITECO_TOPIC ".teco_session" session
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index c097b70..cb718e0 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -874,8 +874,9 @@ areas\fP.
.IP \(bu
The \fIcommand line area\fP, showing the currently effective
and rubbed-out command line as it is edited.
-This is currently a single line of text that is scrolled
-automatically.
+This is a single line by default, but can be configured with \(lq5EJ\(rq.
+Since it is also a Scintilla view, it can be further customized by
+running \(lq0,2048ED\(rq and invoking Scintilla messages using \fBES\fP.
.
.SS Colors and Theming
.SCITECO_TOPIC colors theming
diff --git a/doc/tedoc.tes b/doc/tedoc.tes.in
index cc4f726..cc4f726 100755
--- a/doc/tedoc.tes
+++ b/doc/tedoc.tes.in
diff --git a/fallback.teco_ini b/fallback.teco_ini
index 7b10441..c0b3a49 100644
--- a/fallback.teco_ini
+++ b/fallback.teco_ini
Binary files differ
diff --git a/freebsd/Makefile b/freebsd/Makefile
index f4f0423..1dcd194 100644
--- a/freebsd/Makefile
+++ b/freebsd/Makefile
@@ -1,5 +1,5 @@
PORTNAME= sciteco
-DISTVERSION= 2.4.0
+DISTVERSION= 2.5.0
CATEGORIES= editors textproc devel
MASTER_SITES= https://sciteco.fmsbw.de/downloads/v${DISTVERSION}/ \
SOURCEFORGE/${PORTNAME}/v${DISTVERSION}/
@@ -33,10 +33,6 @@ CONFIGURE_OUTSOURCE= yes
MAKEFILE= GNUmakefile
TEST_TARGET= check
-# SciTECO uses an install-exec-hook to fix up hash-bang lines.
-# This is broken by the default 0555 mode.
-BINMODE= 755
-
# NOTE: Unlike on Debian, we cannot build a sciteco-common package.
# FreeBSD does not yet support subpackages.
# Therefore both flavors will install totally independant
@@ -65,19 +61,23 @@ PLIST_SUB+= GTK="" \
PROGRAM_PREFIX=g
.endif
-OPTIONS_DEFINE= LEXILLA MALLOC_REPLACEMENT TECO_INTEGER_32
+OPTIONS_DEFINE= LEXILLA MALLOC_REPLACEMENT TECO_INTEGER_32 LTO
OPTIONS_DEFAULT= LEXILLA MALLOC_REPLACEMENT
OPTIONS_SUB= yes
LEXILLA_DESC= Build with Lexilla lexer support (larger)
MALLOC_REPLACEMENT_DESC= Replace system malloc() for memory limiting
TECO_INTEGER_32_DESC= Use 32-bit TECO integers
+LTO_DESC= Apply Link-Time Optimizations (significantly faster)
LEXILLA_CONFIGURE_OFF= --without-lexilla
MALLOC_REPLACEMENT_CONFIGURE_ON= --enable-malloc-replacement
TECO_INTEGER_32_CONFIGURE_ON= --with-teco-integer=32
-WITH_LTO= yes
+# Once we support an --enable-lto site-config-option, we should rather use that.
+LTO_CFLAGS= -flto=thin
+LTO_CXXFLAGS= -flto=thin
+LTO_LDFLAGS= -flto=thin
.include <bsd.port.pre.mk>
diff --git a/freebsd/files/xvfb-run.sh b/freebsd/files/xvfb-run.sh
index b4fd5a0..b4fd5a0 100644..100755
--- a/freebsd/files/xvfb-run.sh
+++ b/freebsd/files/xvfb-run.sh
diff --git a/lib/color.tes b/lib/color.tes
index 347f29b..a45c646 100644
--- a/lib/color.tes
+++ b/lib/color.tes
@@ -80,11 +80,13 @@
:M[color.bracelight],34M[color.set]
:M[color.error],35M[color.set]
- !* Configure fold margin *!
- :M[color.linenumber]U.fU.b
- Q.b,1ESSETFOLDMARGINCOLOUR Q.b,1ESSETFOLDMARGINHICOLOUR
- 25U.x 7<Q.f,Q.xESMARKERSETFORE Q.b,Q.xESMARKERSETBACK %.x>
- 10000++,25ESMARKERDEFINE 10000+-,26ESMARKERDEFINE
- 10000++,30ESMARKERDEFINE 10000+-,31ESMARKERDEFINE
- (2^*25 # 2^*26 # 2^*30 # 2^*31),2ESSETMARGINMASKN 0,2ESSETMARGINTYPEN
+ !* Configure fold margin (except on the command line) *!
+ ED&2048"=
+ :M[color.linenumber]U.fU.b
+ Q.b,1ESSETFOLDMARGINCOLOUR Q.b,1ESSETFOLDMARGINHICOLOUR
+ 25U.x 7<Q.f,Q.xESMARKERSETFORE Q.b,Q.xESMARKERSETBACK %.x>
+ 10000++,25ESMARKERDEFINE 10000+-,26ESMARKERDEFINE
+ 10000++,30ESMARKERDEFINE 10000+-,31ESMARKERDEFINE
+ (2^*25 # 2^*26 # 2^*30 # 2^*31),2ESSETMARGINMASKN 0,2ESSETMARGINTYPEN
+ '
}
diff --git a/lib/colors/solarized.tes b/lib/colors/solarized.tes
index fd0823e..11567d9 100644
--- a/lib/colors/solarized.tes
+++ b/lib/colors/solarized.tes
@@ -131,6 +131,8 @@ Q[solarized.light]"T :M[solarized.light] | :M[solarized.dark] '
[*
EJ<%.bEB M[lexer.auto]>
EQ.b :M[color.init]
+ !* FIXME: What if the user disabled it in .teco_ini? *!
+ :M[lexer.set.cmdline]
]*
}
diff --git a/lib/lexer.tes b/lib/lexer.tes
index 4e3e1b5..af6793a 100644
--- a/lib/lexer.tes
+++ b/lib/lexer.tes
@@ -39,3 +39,12 @@
:@[lexer.auto]{
]_
}
+
+@[lexer.set.cmdline]{
+ 0,2048ED
+ :M[color.init]
+ !* indicator for rubbed out part of the command line *!
+ :M[color.default],8ESINDICSETFORE
+ :M[lexer.set.sciteco]
+ 2048,0ED
+}
diff --git a/lib/lexers/bash.tes b/lib/lexers/bash.tes
index 862a0c1..893fc8d 100644
--- a/lib/lexers/bash.tes
+++ b/lib/lexers/bash.tes
@@ -35,10 +35,12 @@
:M[color.number],3M[color.set]
:M[color.keyword],4M[color.set]
:M[color.string],5M[color.set]
- :M[color.string],6M[color.set]
+ :M[color.string2],6M[color.set]
:M[color.operator],7M[color.set]
- :M[color.target],8M[color.set] !* Identifiers, e.g. FOO=... *!
+ !!:M[color.target],8M[color.set] !* Identifiers, e.g. FOO=... *!
:M[color.variable],9M[color.set]
:M[color.variable],10M[color.set]
- :M[color.string2],11M[color.set] !* Backticks *!
+ :M[color.preproc],11M[color.set] !* Backticks *!
+ :M[color.target],12M[color.set] !* Heredoc delimiter *!
+ :M[color.preproc2],13M[color.set] !* Heredoc *!
}
diff --git a/lib/lexers/batch.tes b/lib/lexers/batch.tes
index dddd802..97717a1 100644
--- a/lib/lexers/batch.tes
+++ b/lib/lexers/batch.tes
@@ -17,7 +17,8 @@
:M[color.keyword],2M[color.set]
:M[color.target],3M[color.set] !* Labels *!
:M[color.preproc],4M[color.set] !* Hide Cmd @ *!
- :M[color.preproc2],5M[color.set] !* External Cmd *!
+ !!:M[color.preproc2],5M[color.set] !* External Cmd *!
:M[color.variable],6M[color.set]
:M[color.operator],7M[color.set]
+ :M[color.preproc2],8M[color.set] !* After label *!
}
diff --git a/lib/lexers/make.tes b/lib/lexers/make.tes
index 4ff519a..68acda1 100644
--- a/lib/lexers/make.tes
+++ b/lib/lexers/make.tes
@@ -3,6 +3,7 @@
@[lexer.test.make]{
:EN*/MakefileQ*"S -1 '
:EN*/makefileQ*"S -1 '
+ :EN*/GNUmakefileQ*"S -1 '
:EN*.makQ*
}
@@ -13,5 +14,5 @@
:M[color.variable],3M[color.set]
:M[color.operator],4M[color.set]
:M[color.target],5M[color.set]
- :M[color.error],6M[color.set]
+ :M[color.error],9M[color.set]
}
diff --git a/lib/lexers/sciteco.tes b/lib/lexers/sciteco.tes
index 7ca9c70..0e7db64 100644
--- a/lib/lexers/sciteco.tes
+++ b/lib/lexers/sciteco.tes
Binary files differ
diff --git a/lib/lexers/yaml.tes b/lib/lexers/yaml.tes
index 631f83a..2102afa 100644
--- a/lib/lexers/yaml.tes
+++ b/lib/lexers/yaml.tes
@@ -17,7 +17,7 @@
ESSETILEXERyaml
0ESSETKEYWORDStrue false yes no
:M[color.comment],1M[color.set]
- :M[color.target],2M[color.set]
+ :M[color.string],2M[color.set]
:M[color.keyword],3M[color.set]
:M[color.number],4M[color.set]
:M[color.variable],5M[color.set]
diff --git a/m4/ax_ptrdiff_aliases_int.m4 b/m4/ax_ptrdiff_aliases_int.m4
new file mode 100644
index 0000000..32e1712
--- /dev/null
+++ b/m4/ax_ptrdiff_aliases_int.m4
@@ -0,0 +1,18 @@
+
+AC_DEFUN([AX_PTRDIFF_ALIASES_INT], [
+ AC_CACHE_CHECK([whether ptrdiff_t* aliases int*], [ax_cv_ptrdiff_aliases_int], [
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+ @%:@include <stddef.h>
+ @%:@include <assert.h>
+ ]], [[
+ ptrdiff_t x = 23;
+ _Static_assert(_Generic(&x, int* : 1, default : 0),
+ "ptrdiff_t* does not alias int*");
+ ]])],
+ [ax_cv_ptrdiff_aliases_int=yes],
+ [ax_cv_ptrdiff_aliases_int=no])
+ ])
+ AS_IF([test "x$ax_cv_ptrdiff_aliases_int" = "xyes"], [
+ AC_DEFINE([PTRDIFF_ALIASES_INT], [1], [Whether ptrdiff_t* aliases int*])
+ ])
+])dnl
diff --git a/m4/ax_with_ncurses.m4 b/m4/ax_with_ncurses.m4
index 9e8076f..4dc2f33 100644
--- a/m4/ax_with_ncurses.m4
+++ b/m4/ax_with_ncurses.m4
@@ -75,7 +75,7 @@
# Copyright (c) 2009 Damian Pietras <daper@daper.net>
# Copyright (c) 2012 Reuben Thomas <rrt@sc3d.org>
# Copyright (c) 2011 John Zaitseff <J.Zaitseff@zap.org.au>
-# Copyright (c) 2025 Robin Haberkorn <rhaberkorn@fmsbw.de>
+# Copyright (c) 2025-2026 Robin Haberkorn <rhaberkorn@fmsbw.de>
#
# 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
diff --git a/src/cmdline.c b/src/cmdline.c
index 089bd7a..fa69d91 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -61,11 +61,49 @@ int malloc_trim(size_t pad);
#define TECO_DEFAULT_BREAK_CHARS " \t\v\r\n\f<>,;@"
-teco_cmdline_t teco_cmdline = {};
+/** Style used for the asterisk at the beginning of the command line */
+#define STYLE_ASTERISK 64
-/** Last terminated command line */
+teco_cmdline_t teco_cmdline = {
+ .height = 1
+};
+
+/**
+ * Last terminated command line.
+ * This is not a teco_doc_scintilla_t since we have to return it as a string
+ * at the end of the day.
+ */
static teco_string_t teco_last_cmdline = {NULL, 0};
+void
+teco_cmdline_init(void)
+{
+ teco_cmdline.view = teco_view_new();
+ teco_view_setup(teco_cmdline.view);
+
+ teco_cmdline_ssm(SCI_SETUNDOCOLLECTION, FALSE, 0);
+ teco_cmdline_ssm(SCI_SETVSCROLLBAR, FALSE, 0);
+ teco_cmdline_ssm(SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE);
+ teco_cmdline_ssm(SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT);
+ teco_cmdline_ssm(SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK);
+ teco_cmdline_ssm(SCI_SETMARGINWIDTHN, 1,
+ teco_cmdline_ssm(SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*"));
+ /* NOTE: might not work on all UIs */
+ teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_STRIKE);
+ teco_cmdline_ssm(SCI_INDICSETFORE, INDICATOR_RUBBEDOUT,
+ teco_cmdline_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
+
+ /* single line mode - EOL characters won't break the line */
+ teco_cmdline_ssm(SCI_SETLINEENDTYPESALLOWED, SC_LINE_END_TYPE_NONE, 0);
+ /* render tabs as "TAB" without indentation */
+ teco_cmdline_ssm(SCI_SETTABDRAWMODE, SCTD_CONTROLCHAR, 0);
+
+ /*
+ * FIXME: Something resets the margin text, so we have to set it last.
+ */
+ teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*");
+}
+
/**
* Insert string into command line and execute
* it immediately.
@@ -83,40 +121,49 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
g_auto(teco_string_t) old_cmdline = {NULL, 0};
gsize repl_pc = 0;
- teco_cmdline.machine.macro_pc = teco_cmdline.pc = teco_cmdline.effective_len;
+ gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_cmdline.machine.macro_pc = teco_cmdline.pc = effective_len;
+
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
- if (len <= teco_cmdline.str.len - teco_cmdline.effective_len &&
- !teco_string_cmp(&src, teco_cmdline.str.data + teco_cmdline.effective_len, len)) {
- teco_cmdline.effective_len += len;
+ if (len <= macro_len - effective_len &&
+ !teco_string_cmp(src, macro + effective_len, len)) {
+ /* extend effective command line from rubbed out part */
+ teco_cmdline_ssm(SCI_GOTOPOS, effective_len+len, 0);
} else {
- if (teco_cmdline.effective_len < teco_cmdline.str.len)
+ /* discard rubbed out part of the command line */
+ if (effective_len < macro_len)
/*
* Automatically disable immediate editing modifier.
* FIXME: Should we show a message as when pressing ^G?
*/
teco_cmdline.modifier_enabled = FALSE;
- teco_cmdline.str.len = teco_cmdline.effective_len;
- teco_string_append(&teco_cmdline.str, data, len);
- teco_cmdline.effective_len = teco_cmdline.str.len;
+ teco_cmdline_ssm(SCI_DELETERANGE, effective_len, macro_len - effective_len);
+ teco_cmdline_ssm(SCI_ADDTEXT, len, (sptr_t)data);
+
+ /* the pointer shouldn't have changed... */
+ macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
}
+ effective_len += len;
/*
* Parse/execute characters, one at a time so
* undo tokens get emitted for the corresponding characters.
*/
- while (teco_cmdline.pc < teco_cmdline.effective_len) {
+ while (teco_cmdline.pc < effective_len) {
g_autoptr(GError) tmp_error = NULL;
- if (!teco_machine_main_step(&teco_cmdline.machine, teco_cmdline.str.data,
- teco_cmdline.pc+1, &tmp_error)) {
+ if (!teco_machine_main_step(&teco_cmdline.machine, macro, teco_cmdline.pc+1, &tmp_error)) {
if (g_error_matches(tmp_error, TECO_ERROR, TECO_ERROR_CMDLINE)) {
/*
* Result of command line replacement (}):
- * Exchange command lines, avoiding deep copying
+ * Exchange command lines
*/
teco_qreg_t *cmdline_reg = teco_qreg_table_find(&teco_qreg_table_globals, "\e", 1);
- teco_string_t new_cmdline;
+ g_auto(teco_string_t) new_cmdline = {NULL, 0};
if (!cmdline_reg->vtable->get_string(cmdline_reg, &new_cmdline.data, &new_cmdline.len,
NULL, error))
@@ -127,16 +174,26 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
* new command line. This avoids unnecessary rubouts
* and insertions when the command line is updated.
*/
- teco_cmdline.pc = teco_string_diff(&teco_cmdline.str, new_cmdline.data, new_cmdline.len);
+ teco_cmdline.pc = teco_string_diff(new_cmdline, macro, effective_len);
teco_undo_pop(teco_cmdline.pc);
+ /*
+ * We don't replace the command line's document, since that would
+ * reset the line end type and other configurable settings.
+ * Also, we don't clear the document to avoid unnecessary restylings
+ * if syntax highlighting is enabled on the command line.
+ */
g_assert(old_cmdline.len == 0);
- old_cmdline = teco_cmdline.str;
- teco_cmdline.str = new_cmdline;
- teco_cmdline.effective_len = new_cmdline.len;
+ teco_string_init(&old_cmdline, macro, effective_len);
+ teco_cmdline_ssm(SCI_DELETERANGE, teco_cmdline.pc,
+ old_cmdline.len-teco_cmdline.pc);
+ teco_cmdline_ssm(SCI_ADDTEXT, new_cmdline.len-teco_cmdline.pc,
+ (sptr_t)new_cmdline.data+teco_cmdline.pc);
+
+ macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ macro_len = effective_len = new_cmdline.len;
teco_cmdline.machine.macro_pc = repl_pc = teco_cmdline.pc;
-
continue;
}
@@ -148,17 +205,26 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
/*
* Error during command-line replacement.
* Replay previous command-line.
- * This avoids deep copying.
+ * The commands leading up to the failed replacement
+ * will be left rubbed out.
*/
teco_undo_pop(repl_pc);
- teco_string_clear(&teco_cmdline.str);
- teco_cmdline.str = old_cmdline;
+ /*
+ * May cause restyling of the command lines,
+ * but that's probably okay - it's just a fallback.
+ */
+ teco_cmdline_ssm(SCI_CLEARALL, 0, 0);
+ teco_cmdline_ssm(SCI_ADDTEXT, old_cmdline.len, (sptr_t)old_cmdline.data);
+ teco_string_clear(&old_cmdline);
memset(&old_cmdline, 0, sizeof(old_cmdline));
teco_cmdline.machine.macro_pc = teco_cmdline.pc = repl_pc;
- /* rubout cmdline replacement command */
- teco_cmdline.effective_len--;
+ /* rub out cmdline replacement command */
+ teco_cmdline_ssm(SCI_GOTOPOS, --effective_len, 0);
+
+ macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
continue;
}
}
@@ -177,14 +243,18 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error)
static gboolean
teco_cmdline_rubin(GError **error)
{
- if (!teco_cmdline.str.len)
- return TRUE;
-
- const gchar *start, *end, *next;
- start = teco_cmdline.str.data+teco_cmdline.effective_len;
- end = teco_cmdline.str.data+teco_cmdline.str.len;
- next = g_utf8_find_next_char(start, end) ? : end;
- return teco_cmdline_insert(start, next-start, error);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
+ gsize pos = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
+ gchar buf[4+1];
+ struct Sci_TextRangeFull range = {
+ .chrg = {pos, MIN(macro_len, pos+sizeof(buf)-1)},
+ .lpstrText = buf
+ };
+ gsize len = teco_cmdline_ssm(SCI_GETTEXTRANGEFULL, 0, (sptr_t)&range);
+
+ const gchar *end = buf+len;
+ const gchar *next = g_utf8_find_next_char(buf, end) ? : end;
+ return teco_cmdline_insert(buf, next-buf, error);
}
/**
@@ -205,10 +275,9 @@ teco_cmdline_rubin(GError **error)
gboolean
teco_cmdline_keypress(const gchar *data, gsize len, GError **error)
{
- const teco_string_t str = {(gchar *)data, len};
teco_machine_t *machine = &teco_cmdline.machine.parent;
- if (!teco_string_validate_utf8(&str)) {
+ if (!teco_string_validate_utf8((teco_string_t){(gchar *)data, len})) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT,
"Invalid UTF-8 sequence");
return FALSE;
@@ -219,7 +288,7 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error)
*/
teco_interface_msg_clear();
- gsize start_pc = teco_cmdline.effective_len;
+ gsize start_pc = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
for (guint i = 0; i < len; i = g_utf8_next_char(data+i) - data) {
gunichar chr = g_utf8_get_char(data+i);
@@ -246,9 +315,9 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error)
* up until the insertion point.
*/
teco_undo_pop(start_pc);
- teco_cmdline.effective_len = start_pc;
+ teco_cmdline_ssm(SCI_GOTOPOS, start_pc, 0);
/* program counter could be messed up */
- teco_cmdline.machine.macro_pc = teco_cmdline.effective_len;
+ teco_cmdline.machine.macro_pc = start_pc;
#ifdef HAVE_MALLOC_TRIM
/*
@@ -297,12 +366,16 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error)
g_array_remove_range(teco_loop_stack, 0, teco_loop_stack->len);
teco_string_clear(&teco_last_cmdline);
- teco_last_cmdline = teco_cmdline.str;
+ teco_last_cmdline.len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_last_cmdline.data = g_malloc(teco_last_cmdline.len + 1);
+ teco_cmdline_ssm(SCI_GETTEXT, teco_last_cmdline.len,
+ (sptr_t)teco_last_cmdline.data);
/*
* FIXME: Preserve the command line after the $$.
+ * This would be useful for command line editing macros.
+ * Perhaps just call teco_cmdline_insert().
*/
- memset(&teco_cmdline.str, 0, sizeof(teco_cmdline.str));
- teco_cmdline.effective_len = 0;
+ teco_cmdline_ssm(SCI_CLEARALL, 0, 0);
#ifdef HAVE_MALLOC_TRIM
/* see above */
@@ -317,10 +390,8 @@ teco_cmdline_keypress(const gchar *data, gsize len, GError **error)
start_pc = 0;
}
- /*
- * Echo command line
- */
- teco_interface_cmdline_update(&teco_cmdline);
+ teco_cmdline_update();
+
return TRUE;
}
@@ -377,20 +448,48 @@ teco_cmdline_keymacro(const gchar *name, gssize name_len, GError **error)
static void
teco_cmdline_rubout(void)
{
- const gchar *p;
- p = g_utf8_find_prev_char(teco_cmdline.str.data,
- teco_cmdline.str.data+teco_cmdline.effective_len);
- if (p) {
- teco_cmdline.effective_len = p - teco_cmdline.str.data;
- teco_undo_pop(teco_cmdline.effective_len);
+ gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
+ gssize p = teco_view_glyphs2bytes_relative(teco_cmdline.view, effective_len, -1);
+ if (p >= 0) {
+ teco_cmdline_ssm(SCI_GOTOPOS, p, 0);
+ teco_undo_pop(p);
}
}
-static void TECO_DEBUG_CLEANUP
+/**
+ * Update the command line, i.e. prepare it for displaying.
+ *
+ * This updates the indicators and scrolls the caret, which isn't done every time
+ * we touch the command line itself.
+ */
+void
+teco_cmdline_update(void)
+{
+ /*
+ * FIXME: Perhaps this can be avoided completely by updating the
+ * indicators in teco_cmdline_insert().
+ */
+ gsize effective_len = teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
+ teco_cmdline_ssm(SCI_SETINDICATORCURRENT, INDICATOR_RUBBEDOUT, 0);
+ teco_cmdline_ssm(SCI_INDICATORCLEARRANGE, 0, macro_len);
+ teco_cmdline_ssm(SCI_INDICATORFILLRANGE, effective_len, macro_len - effective_len);
+
+ teco_cmdline_ssm(SCI_SCROLLCARET, 0, 0);
+
+ /*
+ * FIXME: This gets reset repeatedly.
+ * Setting it once per keypress however means you can no longer customize
+ * the margin text.
+ */
+ teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*");
+}
+
+void
teco_cmdline_cleanup(void)
{
teco_machine_main_clear(&teco_cmdline.machine);
- teco_string_clear(&teco_cmdline.str);
+ teco_view_free(teco_cmdline.view);
teco_string_clear(&teco_last_cmdline);
}
@@ -448,11 +547,12 @@ teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gun
if (teco_cmdline.modifier_enabled) {
/* reinsert construct */
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
do {
if (!teco_cmdline_rubin(error))
return FALSE;
} while (!ctx->current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len);
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len);
} else {
/* rubout construct */
do
@@ -508,6 +608,9 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
case TECO_CTL_KEY('W'): /* rubout/reinsert command */
teco_interface_popup_clear();
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
+
/*
* This mimics the behavior of the `Y` command,
* so it also rubs out no-op commands.
@@ -517,9 +620,8 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
/* reinsert command */
/* @ and : are not separate states, but practically belong to the command */
while (ctx->parent.current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- (teco_cmdline.str.data[teco_cmdline.effective_len] == ':' ||
- teco_cmdline.str.data[teco_cmdline.effective_len] == '@'))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ strchr(":@", macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]) != NULL)
if (!teco_cmdline_rubin(error))
return FALSE;
@@ -527,11 +629,11 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
if (!teco_cmdline_rubin(error))
return FALSE;
} while (!ctx->parent.current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len);
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len);
while (ctx->parent.current->is_start &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- teco_is_noop(teco_cmdline.str.data[teco_cmdline.effective_len]))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ teco_is_noop(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]))
if (!teco_cmdline_rubin(error))
return FALSE;
@@ -540,8 +642,8 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
/* rubout command */
while (ctx->parent.current->is_start &&
- teco_cmdline.effective_len > 0 &&
- teco_is_noop(teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) > 0 &&
+ teco_is_noop(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]))
teco_cmdline_rubout();
do
@@ -555,7 +657,7 @@ teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
*/
while (ctx->parent.current->is_start &&
(ctx->flags.modifier_at || ctx->flags.modifier_colon) &&
- teco_cmdline.effective_len > 0)
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) > 0)
teco_cmdline_rubout();
return TRUE;
@@ -570,6 +672,9 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
{
teco_state_t *current = ctx->parent.current;
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
+
switch (key) {
case TECO_CTL_KEY('W'): { /* rubout/reinsert word */
teco_interface_popup_clear();
@@ -587,15 +692,15 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
if (teco_cmdline.modifier_enabled) {
/* reinsert word chars */
while (ctx->parent.current == current &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len]))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]))
if (!teco_cmdline_rubin(error))
return FALSE;
/* reinsert non-word chars */
while (ctx->parent.current == current &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- !teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len]))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ !teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]))
if (!teco_cmdline_rubin(error))
return FALSE;
@@ -609,7 +714,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
* a result string even in parse-only mode.
*/
if (ctx->result && ctx->result->len > 0) {
- gboolean is_wordchar = teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1]);
+ gboolean is_wordchar = teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]);
teco_cmdline_rubout();
if (ctx->parent.current != current) {
/* rub out string building command */
@@ -625,13 +730,13 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
*/
if (!is_wordchar) {
while (ctx->result->len > 0 &&
- !teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ !teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]))
teco_cmdline_rubout();
}
/* rubout word chars */
while (ctx->result->len > 0 &&
- teco_string_contains(&wchars, teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ teco_string_contains(wchars, macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]))
teco_cmdline_rubout();
return TRUE;
@@ -648,8 +753,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
if (teco_cmdline.modifier_enabled) {
/* reinsert string */
- while (ctx->parent.current == current &&
- teco_cmdline.effective_len < teco_cmdline.str.len)
+ while (ctx->parent.current == current && teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len)
if (!teco_cmdline_rubin(error))
return FALSE;
@@ -687,7 +791,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
return TRUE;
}
- const gchar *filename = teco_string_last_occurrence(ctx->result,
+ const gchar *filename = teco_string_last_occurrence(*ctx->result,
TECO_DEFAULT_BREAK_CHARS);
g_auto(teco_string_t) new_chars, new_chars_escaped;
gboolean unambiguous = teco_file_auto_complete(filename, G_FILE_TEST_EXISTS, &new_chars);
@@ -718,11 +822,11 @@ 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)
+teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, 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_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);
}
@@ -767,7 +871,7 @@ 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_state_expectstring_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
teco_state_t *stringbuilding_current = stringbuilding_ctx->parent.current;
@@ -827,18 +931,21 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
case TECO_CTL_KEY('W'): /* rubout/reinsert file names including directories */
teco_interface_popup_clear();
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+ gsize macro_len = teco_cmdline_ssm(SCI_GETLENGTH, 0, 0);
+
if (teco_cmdline.modifier_enabled) {
/* reinsert one level of file name */
while (stringbuilding_ctx->parent.current == stringbuilding_current &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- !G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len]))
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ !G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]))
if (!teco_cmdline_rubin(error))
return FALSE;
/* reinsert final directory separator */
if (stringbuilding_ctx->parent.current == stringbuilding_current &&
- teco_cmdline.effective_len < teco_cmdline.str.len &&
- G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len]) &&
+ teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) < macro_len &&
+ G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)]) &&
!teco_cmdline_rubin(error))
return FALSE;
@@ -847,12 +954,12 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
if (ctx->expectstring.string.len > 0) {
/* rubout directory separator */
- if (G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ if (G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]))
teco_cmdline_rubout();
/* rubout one level of file name */
while (ctx->expectstring.string.len > 0 &&
- !G_IS_DIR_SEPARATOR(teco_cmdline.str.data[teco_cmdline.effective_len-1]))
+ !G_IS_DIR_SEPARATOR(macro[teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0)-1]))
teco_cmdline_rubout();
return TRUE;
@@ -873,7 +980,7 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
return TRUE;
}
- if (teco_string_contains(&ctx->expectstring.string, '\0'))
+ if (teco_string_contains(ctx->expectstring.string, '\0'))
/* null-byte not allowed in file names */
return TRUE;
@@ -892,13 +999,13 @@ 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_state_expectfile_insert_completion(teco_machine_main_t *ctx, 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])) &&
+ 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);
@@ -929,7 +1036,7 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
return TRUE;
}
- if (teco_string_contains(&ctx->expectstring.string, '\0'))
+ if (teco_string_contains(ctx->expectstring.string, '\0'))
/* null-byte not allowed in file names */
return TRUE;
@@ -960,14 +1067,14 @@ 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_state_expectglob_insert_completion(teco_machine_main_t *ctx, 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_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])) &&
+ 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);
@@ -998,7 +1105,7 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *
return TRUE;
}
- if (teco_string_contains(&ctx->expectstring.string, '\0'))
+ if (teco_string_contains(ctx->expectstring.string, '\0'))
/* null-byte not allowed in file names */
return TRUE;
@@ -1018,7 +1125,7 @@ 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_state_expectdir_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1026,7 +1133,7 @@ teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_stri
* 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);
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped);
return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
}
@@ -1043,7 +1150,7 @@ 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)
+teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
g_assert(ctx->expectqreg != NULL);
/*
@@ -1094,9 +1201,9 @@ 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)
+teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str, GError **error)
{
- return teco_cmdline_insert(str->data, str->len, error);
+ return teco_cmdline_insert(str.data, str.len, error);
}
gboolean
@@ -1140,12 +1247,12 @@ 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_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, 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_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);
}
@@ -1180,7 +1287,7 @@ teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
return TRUE;
}
- const gchar *filename = teco_string_last_occurrence(&ctx->expectstring.string,
+ const gchar *filename = teco_string_last_occurrence(ctx->expectstring.string,
TECO_DEFAULT_BREAK_CHARS);
g_auto(teco_string_t) new_chars, new_chars_escaped;
gboolean unambiguous = teco_file_auto_complete(filename, G_FILE_TEST_EXISTS, &new_chars);
@@ -1219,7 +1326,7 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac
return TRUE;
}
- const gchar *symbol = teco_string_last_occurrence(&ctx->expectstring.string, ",");
+ const gchar *symbol = teco_string_last_occurrence(ctx->expectstring.string, ",");
teco_symbol_list_t *list = symbol == ctx->expectstring.string.data
? &teco_symbol_list_scintilla
: &teco_symbol_list_scilexer;
@@ -1241,12 +1348,12 @@ 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_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, 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_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);
}
@@ -1276,7 +1383,7 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
}
teco_string_t label = ctx->expectstring.string;
- gint i = teco_string_rindex(&label, ',');
+ gint i = teco_string_rindex(label, ',');
if (i >= 0) {
label.data += i+1;
label.len -= i+1;
@@ -1299,12 +1406,12 @@ 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_state_goto_insert_completion(teco_machine_main_t *ctx, 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_machine_stringbuilding_escape(stringbuilding_ctx, str.data, str.len, &str_escaped);
/*
* FIXME: This does not escape `,`. Cannot be escaped via ^Q currently?
*/
@@ -1336,7 +1443,7 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
return TRUE;
}
- if (teco_string_contains(&ctx->expectstring.string, '\0'))
+ if (teco_string_contains(ctx->expectstring.string, '\0'))
/* help term must not contain null-byte */
return TRUE;
@@ -1355,12 +1462,12 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
}
gboolean
-teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_help_insert_completion(teco_machine_main_t *ctx, 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_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);
@@ -1396,5 +1503,6 @@ teco_state_save_cmdline_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg
* Q-Register <q>.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_save_cmdline,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_save_cmdline_got_register
);
diff --git a/src/cmdline.h b/src/cmdline.h
index abe9b53..f6d0345 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -19,10 +19,13 @@
#include <glib.h>
#include "sciteco.h"
-#include "string-utils.h"
#include "parser.h"
+#include "view.h"
#include "undo.h"
+/** Indicator number used for the rubbed out part of the command line */
+#define INDICATOR_RUBBEDOUT (INDICATOR_CONTAINER+0)
+
typedef struct {
/**
* State machine used for interactive mode (commandline macro).
@@ -34,16 +37,17 @@ typedef struct {
teco_machine_main_t machine;
/**
- * String containing the current command line
- * (both effective and rubbed out).
- */
- teco_string_t str;
- /**
- * Effective command line length.
- * The length of the rubbed out part of the command line
- * is (teco_cmdline.str.len - teco_cmdline.effective_len).
+ * Command-line Scintilla view.
+ * It's document contains the current command line macro.
+ * The current position (cursor) marks the end of the
+ * "effective" command line, while everything afterwards
+ * is the rubbed out part of the command line.
+ * The rubbed out part should be highlighted with an indicator.
*/
- gsize effective_len;
+ teco_view_t *view;
+
+ /** Height of the command line view in lines */
+ guint height;
/** Program counter within the command-line macro */
gsize pc;
@@ -60,6 +64,30 @@ typedef struct {
extern teco_cmdline_t teco_cmdline;
+void teco_cmdline_init(void);
+
+static inline sptr_t
+teco_cmdline_ssm(unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return teco_view_ssm(teco_cmdline.view, iMessage, wParam, lParam);
+}
+
+/**
+ * Update scroll beavior on command line after window resizes.
+ *
+ * This should ensure that the caret jumps to the middle of the command line.
+ *
+ * @param width Window (command line view) width in pixels or columns.
+ *
+ * @fixme
+ * On the other hand this limits how you can customize the scroll behavior.
+ */
+static inline void
+teco_cmdline_resized(guint width)
+{
+ teco_cmdline_ssm(SCI_SETXCARETPOLICY, CARET_SLOP | CARET_EVEN, width/2);
+}
+
gboolean teco_cmdline_keypress(const gchar *data, gsize len, GError **error);
typedef enum {
@@ -84,8 +112,12 @@ teco_cmdline_keymacro_c(gchar key, GError **error)
return TRUE;
}
+void teco_cmdline_update(void);
+
+void teco_cmdline_cleanup(void);
+
/*
* Command states
*/
-TECO_DECLARE_STATE(teco_state_save_cmdline);
+extern teco_state_t teco_state_save_cmdline;
diff --git a/src/core-commands.c b/src/core-commands.c
index 653a40f..cd9a8fa 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -551,9 +551,11 @@ teco_state_start_cmdline_push(teco_machine_main_t *ctx, GError **error)
!teco_qreg_table_edit_name(&teco_qreg_table_globals, "\e", 1, error))
return;
+ const gchar *macro = (const gchar *)teco_cmdline_ssm(SCI_GETCHARACTERPOINTER, 0, 0);
+
teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
teco_interface_ssm(SCI_CLEARALL, 0, 0);
- teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)teco_cmdline.str.data);
+ teco_interface_ssm(SCI_ADDTEXT, teco_cmdline.pc, (sptr_t)macro);
teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0);
/*
@@ -641,7 +643,7 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
['"'] = {&teco_state_condcommand},
['E'] = {&teco_state_ecommand,
.modifier_at = TRUE, .modifier_colon = 2},
- ['I'] = {&teco_state_insert_plain,
+ ['I'] = {&teco_state_insert,
.modifier_at = TRUE},
['?'] = {&teco_state_help,
.modifier_at = TRUE},
@@ -771,8 +773,10 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* for beginnings of command-lines?
* It could also be used for a corresponding KEYMACRO mask.
*/
- if (teco_cmdline.effective_len == 1 && teco_cmdline.str.data[0] == '*')
+ if (teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0) == 1 &&
+ teco_cmdline_ssm(SCI_GETCHARAT, 0, 0) == '*')
return &teco_state_save_cmdline;
+ /* treat as an operator */
break;
case '<':
@@ -911,7 +915,9 @@ teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_START(teco_state_start);
+TECO_DEFINE_STATE_START(teco_state_start,
+ .input_cb = (teco_state_input_cb_t)teco_state_start_input
+);
/*$ "F<" ":F<"
* F< -- Go to loop start or jump to beginning of macro
@@ -1076,7 +1082,9 @@ teco_state_fcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_fcommand);
+TECO_DEFINE_STATE_COMMAND(teco_state_fcommand,
+ .input_cb = (teco_state_input_cb_t)teco_state_fcommand_input
+);
static void
teco_undo_change_dir_action(gchar **dir, gboolean run)
@@ -1100,12 +1108,12 @@ teco_undo_change_dir_to_current(void)
}
static teco_state_t *
-teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_changedir_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- g_autofree gchar *dir = teco_file_expand_path(str->data);
+ g_autofree gchar *dir = teco_file_expand_path(str.data);
if (!*dir) {
teco_qreg_t *qreg = teco_qreg_table_find(&teco_qreg_table_globals, "$HOME", 5);
g_assert(qreg != NULL);
@@ -1116,7 +1124,7 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
/*
* Null-characters must not occur in file names.
*/
- if (teco_string_contains(&home, '\0')) {
+ if (teco_string_contains(home, '\0')) {
teco_string_clear(&home);
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Null-character not allowed in filenames");
@@ -1168,7 +1176,9 @@ teco_state_changedir_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* String-building characters are enabled on this
* command and directories can be tab-completed.
*/
-TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir);
+TECO_DEFINE_STATE_EXPECTDIR(teco_state_changedir,
+ .expectstring.done_cb = teco_state_changedir_done
+);
static teco_state_t *
teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
@@ -1283,7 +1293,8 @@ teco_state_condcommand_input(teco_machine_main_t *ctx, gunichar chr, GError **er
}
TECO_DEFINE_STATE_COMMAND(teco_state_condcommand,
- .style = SCE_SCITECO_OPERATOR
+ .style = SCE_SCITECO_OPERATOR,
+ .input_cb = (teco_state_input_cb_t)teco_state_condcommand_input
);
/*$ ^_ negate
@@ -1697,7 +1708,9 @@ teco_state_control_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_control);
+TECO_DEFINE_STATE_COMMAND(teco_state_control,
+ .input_cb = (teco_state_input_cb_t)teco_state_control_input
+);
static teco_state_t *
teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
@@ -1720,7 +1733,9 @@ teco_state_ascii_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
* Note that this command can be typed CTRL+Caret or
* Caret-Caret.
*/
-TECO_DEFINE_STATE(teco_state_ascii);
+TECO_DEFINE_STATE(teco_state_ascii,
+ .input_cb = (teco_state_input_cb_t)teco_state_ascii_input
+);
/*$ ^[^[ ^[$ $$ ^C terminate return
* [a1,a2,...]$$ -- Terminate command line or return from macro
@@ -1781,7 +1796,7 @@ teco_return(teco_machine_main_t *ctx, GError **error)
* command line with `}` after command-line termination.
*/
if (G_UNLIKELY(ctx == &teco_cmdline.machine &&
- teco_qreg_current && !teco_string_cmp(&teco_qreg_current->head.name, "\e", 1))) {
+ teco_qreg_current && !teco_string_cmp(teco_qreg_current->head.name, "\e", 1))) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Not allowed to terminate command-line while "
"editing command-line replacement register");
@@ -1854,6 +1869,7 @@ teco_state_escape_end_of_macro(teco_machine_t *ctx, GError **error)
}
TECO_DEFINE_STATE_START(teco_state_escape,
+ .input_cb = (teco_state_input_cb_t)teco_state_escape_input,
.end_of_macro_cb = teco_state_escape_end_of_macro
);
@@ -1888,7 +1904,8 @@ teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error)
}
TECO_DEFINE_STATE_START(teco_state_ctlc,
- .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial
+ .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial,
+ .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input
);
static teco_state_t *
@@ -1943,7 +1960,9 @@ teco_state_ctlc_control_input(teco_machine_main_t *ctx, gunichar chr, GError **e
* This state is necessary, so that you can type ^C^C exclusively with carets.
* Otherwise it would be very cumbersome to cause exits with ASCII characters only.
*/
-TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control);
+TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control,
+ .input_cb = (teco_state_input_cb_t)teco_state_ctlc_control_input
+);
/*$ ED flags
* flags ED -- Set and get ED-flags
@@ -1964,7 +1983,7 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control);
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * .IP 2: 5
+ * .IP 2: 6
* Reflects whether program termination has been requested
* by successfully performing the \fBEX\fP command.
* This flag can also be used to cancel the effect of any
@@ -2013,6 +2032,13 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control);
* by the \(lqNerd Fonts\(rq project.
* Changes to this flag in interactive mode may not become
* effective immediately.
+ * .IP 1024:
+ * If set the default clipboard register \(lq~\(rq will refer
+ * to the primary clipboard (\(lq~P\(rq) instead of the
+ * clipboard selection (\(lq~C\(rq).
+ * .IP 2048:
+ * Enable/Disable redirection of Scintilla messages (\fBES\fP)
+ * to the command line's Scintilla view.
*
* The features controlled thus are discribed in other sections
* of this manual.
@@ -2135,8 +2161,23 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* 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.
+ * Unlike most other settings, this is on purpose not restored on rubout,
+ * so it \(lqsurvives\(rq command line replacements.
+ * .IP 5:
+ * Height of the command line view in lines (1 by default).
+ * Must not be smaller than 1.
+ * .IP 6:
+ * .SCITECO_TOPIC recovery
+ * Interval in seconds for the creation of recovery files
+ * or 0 if those dumps are disabled (the default is 300 seconds).
+ * When enabled all dirty buffers are dumped to files with hash
+ * signs around the original basename (\fB#\fIfilename\fB#\fR).
+ * They are removed automatically when no longer required,
+ * but may be left around when the \*(ST crashes or terminates
+ * unexpectedly.
+ * After changing the interval, the new value may become
+ * active only after the previous interval expires.
+ * Recovery files are not dumped in batch mode.
* .
* .IP -1:
* Type of the last mouse event (\fBread-only\fP).
@@ -2189,7 +2230,9 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
EJ_INIT_COLOR,
- EJ_CARETX
+ EJ_CARETX,
+ EJ_CMDLINE_HEIGHT,
+ EJ_RECOVERY_INTERVAL
};
static teco_int_t caret_x = 0;
@@ -2226,9 +2269,31 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
break;
case EJ_CARETX:
+ /* DON'T undo on rubout */
caret_x = value;
break;
+ case EJ_CMDLINE_HEIGHT:
+ if (value < 1 || value > G_MAXUINT) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Invalid command line height %" TECO_INT_FORMAT " "
+ "for <EJ>", value);
+ return;
+ }
+ teco_undo_guint(teco_cmdline.height) = value;
+ break;
+
+ case EJ_RECOVERY_INTERVAL:
+ if (value < 0 || value > G_MAXUINT) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Invalid recovery interval %" TECO_INT_FORMAT "s "
+ "for <EJ>", value);
+ return;
+ }
+ teco_undo_guint(teco_ring_recovery_interval) = value;
+ /* FIXME: Perhaps signal the interface to reprogram timers */
+ break;
+
default:
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Cannot set property %" TECO_INT_FORMAT " "
@@ -2284,6 +2349,14 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
teco_expressions_push(caret_x);
break;
+ case EJ_CMDLINE_HEIGHT:
+ teco_expressions_push(teco_cmdline.height);
+ break;
+
+ case EJ_RECOVERY_INTERVAL:
+ teco_expressions_push(teco_ring_recovery_interval);
+ break;
+
default:
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Invalid property %" TECO_INT_FORMAT " "
@@ -2785,7 +2858,9 @@ teco_state_ecommand_input(teco_machine_main_t *ctx, gunichar chr, GError **error
teco_ascii_toupper(chr), error);
}
-TECO_DEFINE_STATE_COMMAND(teco_state_ecommand);
+TECO_DEFINE_STATE_COMMAND(teco_state_ecommand,
+ .input_cb = (teco_state_input_cb_t)teco_state_ecommand_input
+);
gboolean
teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
@@ -2855,14 +2930,14 @@ teco_state_insert_initial(teco_machine_main_t *ctx, GError **error)
}
gboolean
-teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str,
+teco_state_insert_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error)
{
g_assert(new_chars > 0);
teco_interface_ssm(SCI_BEGINUNDOACTION, 0, 0);
teco_interface_ssm(SCI_ADDTEXT, new_chars,
- (sptr_t)(str->data + str->len - new_chars));
+ (sptr_t)(str.data + str.len - new_chars));
teco_interface_ssm(SCI_ENDUNDOACTION, 0, 0);
teco_ring_dirtify();
@@ -2873,7 +2948,7 @@ teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str,
}
teco_state_t *
-teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -2904,7 +2979,7 @@ teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
* may be better, since it has string building characters
* disabled.
*/
-TECO_DEFINE_STATE_INSERT(teco_state_insert_plain);
+TECO_DEFINE_STATE_INSERT(teco_state_insert);
static gboolean
teco_state_insert_indent_initial(teco_machine_main_t *ctx, GError **error)
diff --git a/src/core-commands.h b/src/core-commands.h
index cb28dce..254c4a7 100644
--- a/src/core-commands.h
+++ b/src/core-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -55,15 +55,15 @@ gboolean teco_state_command_process_edit_cmd(teco_machine_main_t *ctx, teco_mach
* FIXME: Most of these states can probably be private/static
* as they are only referenced from teco_state_start.
*/
-TECO_DECLARE_STATE(teco_state_fcommand);
+extern teco_state_t teco_state_fcommand;
void teco_undo_change_dir_to_current(void);
-TECO_DECLARE_STATE(teco_state_changedir);
+extern teco_state_t teco_state_changedir;
-TECO_DECLARE_STATE(teco_state_condcommand);
-TECO_DECLARE_STATE(teco_state_control);
-TECO_DECLARE_STATE(teco_state_ascii);
-TECO_DECLARE_STATE(teco_state_ecommand);
+extern teco_state_t teco_state_condcommand;
+extern teco_state_t teco_state_control;
+extern teco_state_t teco_state_ascii;
+extern teco_state_t teco_state_ecommand;
typedef struct {
teco_int_t from; /*< start position in glyphs */
@@ -74,9 +74,9 @@ extern guint teco_ranges_count;
extern teco_range_t *teco_ranges;
gboolean teco_state_insert_initial(teco_machine_main_t *ctx, GError **error);
-gboolean teco_state_insert_process(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_insert_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error);
-teco_state_t *teco_state_insert_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
+teco_state_t *teco_state_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error);
/* in cmdline.c */
gboolean teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
@@ -88,23 +88,18 @@ gboolean teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machi
* @ingroup states
*
* @note Also serves as a base class of the replace-insertion commands.
- * @fixme Generating the done_cb could be avoided if there simply were a default.
*/
#define TECO_DEFINE_STATE_INSERT(NAME, ...) \
- static teco_state_t * \
- NAME##_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error) \
- { \
- return teco_state_insert_done(ctx, str, error); \
- } \
TECO_DEFINE_STATE_EXPECTSTRING(NAME, \
.initial_cb = (teco_state_initial_cb_t)teco_state_insert_initial, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_insert_process_edit_cmd, \
.expectstring.process_cb = teco_state_insert_process, \
+ .expectstring.done_cb = teco_state_insert_done, \
##__VA_ARGS__ \
)
-TECO_DECLARE_STATE(teco_state_insert_plain);
-TECO_DECLARE_STATE(teco_state_insert_indent);
+extern teco_state_t teco_state_insert;
+extern teco_state_t teco_state_insert_indent;
/**
* @class TECO_DEFINE_STATE_START
@@ -124,7 +119,8 @@ TECO_DECLARE_STATE(teco_state_insert_indent);
teco_state_t *teco_state_start_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
-TECO_DECLARE_STATE(teco_state_start);
-TECO_DECLARE_STATE(teco_state_escape);
-TECO_DECLARE_STATE(teco_state_ctlc);
-TECO_DECLARE_STATE(teco_state_ctlc_control);
+extern teco_state_t teco_state_start;
+extern teco_state_t teco_state_control;
+extern teco_state_t teco_state_escape;
+extern teco_state_t teco_state_ctlc;
+extern teco_state_t teco_state_ctlc_control;
diff --git a/src/doc.c b/src/doc.c
index 4ac96c8..9bc9dc8 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -205,7 +205,8 @@ teco_doc_get_string(teco_doc_t *ctx, gchar **str, gsize *outlen, guint *codepage
gsize len = teco_view_ssm(teco_qreg_view, SCI_GETLENGTH, 0, 0);
if (str) {
*str = g_malloc(len + 1);
- teco_view_ssm(teco_qreg_view, SCI_GETTEXT, len + 1, (sptr_t)*str);
+ /* null-terminates the string */
+ teco_view_ssm(teco_qreg_view, SCI_GETTEXT, len, (sptr_t)*str);
}
if (outlen)
*outlen = len;
diff --git a/src/doc.h b/src/doc.h
index 99568ff..3218a70 100644
--- a/src/doc.h
+++ b/src/doc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 e5cab2c..201e949 100644
--- a/src/eol.c
+++ b/src/eol.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 0bcb28c..be5a4b1 100644
--- a/src/eol.h
+++ b/src/eol.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 89f0f72..716b60b 100644
--- a/src/error.c
+++ b/src/error.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 2a12733..67de4aa 100644
--- a/src/error.h
+++ b/src/error.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 2fbb659..9561c46 100644
--- a/src/expressions.c
+++ b/src/expressions.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 631c867..3ef0faf 100644
--- a/src/expressions.h
+++ b/src/expressions.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 75bcb48..7c37b27 100644
--- a/src/file-utils.c
+++ b/src/file-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -109,6 +109,14 @@ teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attrs)
#ifdef G_OS_UNIX
+/*
+ * NOTE: This version does not resolve symlinks to non-existing paths.
+ * It could be improved by repeating readlink() and g_canonicalize_filename(),
+ * but it would require glib v2.58.0.
+ * Alternatively we could also iteratively resolve all path components.
+ * Currently, we simply do not rely on successful canonicalization of
+ * yet non-existing paths.
+ */
gchar *
teco_file_get_absolute_path(const gchar *path)
{
@@ -137,13 +145,12 @@ teco_file_is_visible(const gchar *path)
#if GLIB_CHECK_VERSION(2,58,0)
/*
- * FIXME: This should perhaps be preferred on any platform.
- * But it will complicate preprocessing.
+ * NOTE: Does not resolve symlinks.
*/
gchar *
teco_file_get_absolute_path(const gchar *path)
{
- return g_canonicalize_filename(path, NULL);
+ return path ? g_canonicalize_filename(path, NULL) : NULL;
}
#else /* !GLIB_CHECK_VERSION(2,58,0) */
@@ -353,7 +360,7 @@ teco_file_expand_path(const gchar *path)
*/
g_auto(teco_string_t) home = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &home.data, &home.len, NULL, NULL) ||
- teco_string_contains(&home, '\0'))
+ teco_string_contains(home, '\0'))
return g_strdup(path);
g_assert(home.data != NULL);
@@ -419,7 +426,7 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
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)
+ if (string_diff(cur_basename, basename, basename_len) != basename_len)
/* basename is not a prefix of cur_basename */
continue;
@@ -453,7 +460,7 @@ 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 = string_diff(&other_file, 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;
diff --git a/src/file-utils.h b/src/file-utils.h
index 12a9b83..11d6650 100644
--- a/src/file-utils.h
+++ b/src/file-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -32,9 +32,7 @@ void teco_file_set_attributes(const gchar *filename, teco_file_attributes_t attr
/**
* Get absolute/full version of a possibly relative path.
* The path is tried to be canonicalized so it does
- * not contain relative components.
- * Works with existing and non-existing paths (in the latter case,
- * heuristics may be applied).
+ * not contain relative components and symlinks.
* Depending on platform and existence of the path,
* canonicalization might fail, but the path returned is
* always absolute.
diff --git a/src/glob.c b/src/glob.c
index 9621f1a..d15f601 100644
--- a/src/glob.c
+++ b/src/glob.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -38,10 +38,7 @@
#include "undo.h"
#include "glob.h"
-/*
- * FIXME: This state could be static.
- */
-TECO_DECLARE_STATE(teco_state_glob_filename);
+static teco_state_t teco_state_glob_filename;
/** @memberof teco_globber_t */
void
@@ -308,13 +305,13 @@ teco_globber_compile_pattern(const gchar *pattern)
*/
static teco_state_t *
-teco_state_glob_pattern_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_glob_pattern_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_glob_filename;
- if (str->len > 0) {
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ if (str.len > 0) {
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
teco_qreg_t *glob_reg = teco_qreg_table_find(&teco_qreg_table_globals, "_", 1);
g_assert(glob_reg != NULL);
@@ -454,11 +451,12 @@ teco_state_glob_pattern_done(teco_machine_main_t *ctx, const teco_string_t *str,
* have to edit that register anyway.
*/
TECO_DEFINE_STATE_EXPECTGLOB(teco_state_glob_pattern,
- .expectstring.last = FALSE
+ .expectstring.last = FALSE,
+ .expectstring.done_cb = teco_state_glob_pattern_done
);
static teco_state_t *
-teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_glob_filename_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -497,16 +495,16 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str
if (!glob_reg->vtable->get_string(glob_reg, &pattern_str.data, &pattern_str.len,
NULL, error))
return NULL;
- if (teco_string_contains(&pattern_str, '\0')) {
+ if (teco_string_contains(pattern_str, '\0')) {
teco_error_qregcontainsnull_set(error, "_", 1, FALSE);
return NULL;
}
- if (str->len > 0) {
+ if (str.len > 0) {
/*
* Match pattern against provided file name
*/
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
g_autoptr(GRegex) pattern = teco_globber_compile_pattern(pattern_str.data);
if (g_regex_match(pattern, filename, 0, NULL) &&
@@ -593,4 +591,6 @@ teco_state_glob_filename_done(teco_machine_main_t *ctx, const teco_string_t *str
return &teco_state_start;
}
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_glob_filename);
+static TECO_DEFINE_STATE_EXPECTFILE(teco_state_glob_filename,
+ .expectstring.done_cb = teco_state_glob_filename_done
+);
diff --git a/src/glob.h b/src/glob.h
index d11fbce..995437c 100644
--- a/src/glob.h
+++ b/src/glob.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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,8 +47,9 @@ gchar *teco_globber_escape_pattern(const gchar *pattern);
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);
+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, teco_string_t str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTGLOB
@@ -68,4 +69,4 @@ gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const
* Command states
*/
-TECO_DECLARE_STATE(teco_state_glob_pattern);
+extern teco_state_t teco_state_glob_pattern;
diff --git a/src/goto-commands.c b/src/goto-commands.c
index 05d495f..a9ff3c2 100644
--- a/src/goto-commands.c
+++ b/src/goto-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -35,8 +35,8 @@
#include "goto.h"
#include "goto-commands.h"
-TECO_DECLARE_STATE(teco_state_blockcomment);
-TECO_DECLARE_STATE(teco_state_eolcomment);
+static teco_state_t teco_state_blockcomment;
+static teco_state_t teco_state_eolcomment;
/**
* In TECO_MODE_PARSE_ONLY_GOTO mode, we remain in parse-only mode
@@ -75,7 +75,7 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
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)) {
+ !teco_string_cmp(ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) {
teco_undo_string_own(teco_goto_skip_label);
memset(&teco_goto_skip_label, 0, sizeof(teco_goto_skip_label));
teco_undo_gssize(teco_goto_backup_pc) = -1;
@@ -113,16 +113,17 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
}
TECO_DEFINE_STATE(teco_state_label,
- .style = SCE_SCITECO_LABEL
+ .style = SCE_SCITECO_LABEL,
+ .input_cb = (teco_state_input_cb_t)teco_state_label_input
);
static teco_state_t *
-teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_goto_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- if (!str->len) {
+ if (!str.len) {
/* you can still write @O/,/, though... */
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"No labels given for <O>");
@@ -140,9 +141,9 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
*/
teco_string_t label = {NULL, 0};
while (value >= 0) {
- label.data = label.data ? label.data+label.len+1 : str->data;
- const gchar *p = label.data ? memchr(label.data, ',', str->len - (label.data - str->data)) : NULL;
- label.len = p ? p - label.data : str->len - (label.data - str->data);
+ label.data = label.data ? label.data+label.len+1 : str.data;
+ const gchar *p = label.data ? memchr(label.data, ',', str.len - (label.data - str.data)) : NULL;
+ label.len = p ? p - label.data : str.len - (label.data - str.data);
value--;
@@ -177,7 +178,7 @@ 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_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, teco_string_t str,
GError **error);
/*$ "O" ":O" goto
@@ -221,7 +222,8 @@ gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
.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
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_goto_insert_completion,
+ .expectstring.done_cb = teco_state_goto_done
);
/**
@@ -242,28 +244,34 @@ TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
)
static teco_state_t *
-teco_state_blockcomment_star_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_blockcomment_star_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
return chr == '!' ? &teco_state_start : &teco_state_blockcomment;
}
-TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment_star);
+static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment_star,
+ .input_cb = teco_state_blockcomment_star_input
+);
static teco_state_t *
-teco_state_blockcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_blockcomment_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
return chr == '*' ? &teco_state_blockcomment_star : &teco_state_blockcomment;
}
-TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment);
+static TECO_DEFINE_STATE_COMMENT(teco_state_blockcomment,
+ .input_cb = teco_state_blockcomment_input
+);
/*
* `!!` line comments are inspired by TECO-64.
*/
static teco_state_t *
-teco_state_eolcomment_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
+teco_state_eolcomment_input(teco_machine_t *ctx, gunichar chr, GError **error)
{
return chr == '\n' ? &teco_state_start : &teco_state_eolcomment;
}
-TECO_DEFINE_STATE_COMMENT(teco_state_eolcomment);
+static TECO_DEFINE_STATE_COMMENT(teco_state_eolcomment,
+ .input_cb = teco_state_eolcomment_input
+);
diff --git a/src/goto-commands.h b/src/goto-commands.h
index 45c68e9..3b44168 100644
--- a/src/goto-commands.h
+++ b/src/goto-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -24,5 +24,5 @@
extern teco_string_t teco_goto_skip_label;
extern gssize teco_goto_backup_pc;
-TECO_DECLARE_STATE(teco_state_label);
-TECO_DECLARE_STATE(teco_state_goto);
+extern teco_state_t teco_state_label;
+extern teco_state_t teco_state_goto;
diff --git a/src/goto.c b/src/goto.c
index 855c9f9..42eb964 100644
--- a/src/goto.c
+++ b/src/goto.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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.h b/src/goto.h
index 05c7598..c514692 100644
--- a/src/goto.h
+++ b/src/goto.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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/help.c b/src/help.c
index 0bbbc54..03cc595 100644
--- a/src/help.c
+++ b/src/help.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -271,7 +271,7 @@ teco_state_help_initial(teco_machine_main_t *ctx, GError **error)
}
static teco_state_t *
-teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_help_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -281,7 +281,7 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
"Help topic must not contain null-byte");
return NULL;
}
- const gchar *topic_name = str->data ? : "";
+ const gchar *topic_name = str.data ? : "";
teco_help_topic_t *topic = teco_help_find(topic_name);
if (!topic) {
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
@@ -316,7 +316,7 @@ 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_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_help_insert_completion(teco_machine_main_t *ctx, teco_string_t str,
GError **error);
/*$ "?" help
@@ -388,5 +388,6 @@ 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
+ .expectstring.string_building = FALSE,
+ .expectstring.done_cb = teco_state_help_done
);
diff --git a/src/help.h b/src/help.h
index 3148e01..c2accc4 100644
--- a/src/help.h
+++ b/src/help.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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,4 +27,4 @@ gboolean teco_help_auto_complete(const gchar *topic_name, teco_string_t *insert)
* Command states
*/
-TECO_DECLARE_STATE(teco_state_help);
+extern teco_state_t teco_state_help;
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c
index 7c021d6..0e14655 100644
--- a/src/interface-curses/curses-icons.c
+++ b/src/interface-curses/curses-icons.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -362,6 +362,10 @@ teco_curses_icon_cmp(const void *a, const void *b)
gunichar
teco_curses_icons_lookup_file(const gchar *filename)
{
+ if (!filename || !*filename)
+ /* "(Unnamed)" file */
+ return 0xf1036; /* ó±€¶ */
+
g_autofree gchar *basename = g_path_get_basename(filename);
const teco_curses_icon_t *icon;
diff --git a/src/interface-curses/curses-icons.h b/src/interface-curses/curses-icons.h
index fce9d75..a12fe88 100644
--- a/src/interface-curses/curses-icons.h
+++ b/src/interface-curses/curses-icons.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 332d434..edb6e15 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -19,6 +19,8 @@
#include "config.h"
#endif
+#include <string.h>
+
#include <glib.h>
#include <curses.h>
@@ -26,6 +28,7 @@
#include "list.h"
#include "string-utils.h"
#include "interface.h"
+#include "cmdline.h"
#include "curses-utils.h"
#include "curses-info-popup.h"
#include "curses-icons.h"
@@ -37,6 +40,7 @@ typedef struct {
teco_stailq_entry_t entry;
teco_popup_entry_type_t type;
+ /** entry name or empty string for the "(Unnamed)" buffer */
teco_string_t name;
gboolean highlight;
} teco_popup_entry_t;
@@ -71,7 +75,6 @@ teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_
static void
teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
{
- int cols = getmaxx(stdscr); /**! screen width */
int pad_lines; /**! pad height */
gint pad_cols; /**! entry columns */
gint pad_colwidth; /**! width per entry column */
@@ -82,10 +85,10 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
* 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_colwidth = MIN(ctx->longest + reserve, COLS - 2);
/* pad_cols = floor((cols - 2) / pad_colwidth) */
- pad_cols = (cols - 2) / pad_colwidth;
+ pad_cols = (COLS - 2) / pad_colwidth;
/* pad_lines = ceil(length / pad_cols) */
pad_lines = (ctx->length+pad_cols-1) / pad_cols;
@@ -96,7 +99,7 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
* it will be drawn into the popup window which has left
* and right borders.
*/
- ctx->pad = newpad(pad_lines, cols - 2);
+ ctx->pad = newpad(pad_lines, COLS - 2);
/*
* NOTE: attr could contain A_REVERSE on monochrome terminals,
@@ -122,25 +125,32 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr)
if (entry->highlight)
wattron(ctx->pad, A_BOLD);
+ teco_string_t name = entry->name;
+ if (!name.len) {
+ name.data = TECO_UNNAMED_FILE;
+ name.len = strlen(name.data);
+ }
+
switch (entry->type) {
case TECO_POPUP_FILE:
- g_assert(!teco_string_contains(&entry->name, '\0'));
+ g_assert(!teco_string_contains(name, '\0'));
if (teco_ed & TECO_ED_ICONS) {
+ /* "(Unnamed)" buffer is looked up as "" */
teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_file(entry->name.data));
waddch(ctx->pad, ' ');
}
- teco_curses_format_filename(ctx->pad, entry->name.data, -1);
+ teco_curses_format_filename(ctx->pad, name.data, -1);
break;
case TECO_POPUP_DIRECTORY:
- g_assert(!teco_string_contains(&entry->name, '\0'));
+ g_assert(!teco_string_contains(name, '\0'));
if (teco_ed & TECO_ED_ICONS) {
teco_curses_add_wc(ctx->pad, teco_curses_icons_lookup_dir(entry->name.data));
waddch(ctx->pad, ' ');
}
- teco_curses_format_filename(ctx->pad, entry->name.data, -1);
+ teco_curses_format_filename(ctx->pad, name.data, -1);
break;
default:
- teco_curses_format_str(ctx->pad, entry->name.data, entry->name.len, -1);
+ teco_curses_format_str(ctx->pad, name.data, name.len, -1);
break;
}
@@ -157,9 +167,6 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
/* nothing to display */
return;
- int lines, cols; /* screen dimensions */
- getmaxyx(stdscr, lines, cols);
-
if (ctx->window)
delwin(ctx->window);
@@ -171,10 +178,10 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
* Popup window can cover all but one screen row.
* Another row is reserved for the top border.
*/
- gint popup_lines = MIN(pad_lines + 1, lines - 1);
+ gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height);
/* window covers message, scintilla and info windows */
- ctx->window = newwin(popup_lines, 0, lines - 1 - popup_lines, 0);
+ ctx->window = newwin(popup_lines, 0, LINES - teco_cmdline.height - popup_lines, 0);
wattrset(ctx->window, attr);
@@ -188,7 +195,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
copywin(ctx->pad, ctx->window,
ctx->pad_first_line, 0,
- 1, 1, popup_lines - 1, cols - 2, FALSE);
+ 1, 1, popup_lines - 1, COLS - 2, FALSE);
if (pad_lines <= popup_lines - 1)
/* no need for scrollbar */
@@ -200,13 +207,13 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
/* bar_y = floor(pad_first_line/pad_lines * (popup_lines-2)) + 1 */
gint bar_y = ctx->pad_first_line*(popup_lines-2) / pad_lines + 1;
- mvwvline(ctx->window, 1, cols-1, ACS_CKBOARD, popup_lines-2);
+ mvwvline(ctx->window, 1, COLS-1, ACS_CKBOARD, popup_lines-2);
/*
* We do not use ACS_BLOCK here since it will not
* always be drawn as a solid block (e.g. xterm).
* Instead, simply draw reverse blanks.
*/
- wmove(ctx->window, bar_y, cols-1);
+ wmove(ctx->window, bar_y, COLS-1);
wattrset(ctx->window, attr ^ A_REVERSE);
wvline(ctx->window, ' ', bar_height);
}
@@ -227,7 +234,6 @@ 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)
{
- int cols = getmaxx(stdscr); /**! screen width */
gint pad_cols; /**! entry columns */
gint pad_colwidth; /**! width per entry column */
@@ -240,10 +246,10 @@ teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x)
* 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_colwidth = MIN(ctx->longest + reserve, COLS - 2);
/* pad_cols = floor((cols - 2) / pad_colwidth) */
- pad_cols = (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) {
@@ -265,9 +271,8 @@ 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)
{
- gint lines = getmaxy(stdscr);
gint pad_lines = getmaxy(ctx->pad);
- gint popup_lines = MIN(pad_lines + 1, lines - 1);
+ gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height);
/* progress scroll position */
ctx->pad_first_line += popup_lines - 1;
@@ -281,9 +286,8 @@ 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)
{
- gint lines = getmaxy(stdscr);
gint pad_lines = getmaxy(ctx->pad);
- gint popup_lines = MIN(pad_lines + 1, lines - 1);
+ gint popup_lines = MIN(pad_lines + 1, LINES - teco_cmdline.height);
ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0);
if (pad_lines - ctx->pad_first_line < popup_lines - 1)
diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h
index d845b29..fd923e9 100644
--- a/src/interface-curses/curses-info-popup.h
+++ b/src/interface-curses/curses-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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.c b/src/interface-curses/curses-utils.c
index f94b6dc..875c332 100644
--- a/src/interface-curses/curses-utils.c
+++ b/src/interface-curses/curses-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 18cdd3d..97fc1cc 100644
--- a/src/interface-curses/curses-utils.h
+++ b/src/interface-curses/curses-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -20,6 +20,9 @@
#include <curses.h>
+/** what is displayed for unnamed buffers in the info line and popups */
+#define TECO_UNNAMED_FILE "(Unnamed)"
+
guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width);
guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width);
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index 6c8c812..9ace2ee 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -138,8 +138,6 @@ static gint teco_xterm_version(void) G_GNUC_UNUSED;
static gint teco_interface_blocking_getch(void);
-#define UNNAMED_FILE "(Unnamed)"
-
/**
* Get bright variant of one of the 8 standard
* curses colors.
@@ -166,19 +164,98 @@ static gint teco_interface_blocking_getch(void);
#define COLOR_LCYAN COLOR_LIGHT(COLOR_CYAN)
#define COLOR_LWHITE COLOR_LIGHT(COLOR_WHITE)
+static struct {
+ /**
+ * Mapping of foreground and background curses color tuples
+ * (encoded into a pointer) to a color pair number.
+ */
+ GHashTable *pair_table;
+
+ /**
+ * Mapping of the first 16 curses color codes (that may or may not
+ * correspond with the standard terminal color codes) to
+ * Scintilla-compatible RGB values (red is LSB) to initialize after
+ * Curses startup.
+ * Negative values mean no color redefinition (keep the original
+ * palette entry).
+ */
+ gint32 color_table[16];
+
+ /**
+ * Mapping of the first 16 curses color codes to their
+ * original values for restoring them on shutdown.
+ * Unfortunately, this may not be supported on all
+ * curses ports, so this array may be unused.
+ */
+ struct {
+ gshort r, g, b;
+ } orig_color_table[16];
+
+ int stdin_orig, stdout_orig, stderr_orig;
+ SCREEN *screen;
+ FILE *screen_tty;
+
+ WINDOW *info_window;
+ enum {
+ TECO_INFO_TYPE_BUFFER = 0,
+ TECO_INFO_TYPE_QREG
+ } info_type;
+ /* current document's name or empty string for "(Unnamed)" buffer */
+ teco_string_t info_current;
+ gboolean info_dirty;
+
+ /** timer to track the recovery interval */
+ GTimer *recovery_timer;
+
+ WINDOW *msg_window;
+
+ /**
+ * Pad used exclusively for wgetch() as it will not
+ * result in unwanted wrefresh().
+ */
+ WINDOW *input_pad;
+ GQueue *input_queue;
+
+ teco_curses_info_popup_t popup;
+ gsize popup_prefix_len;
+
+ /**
+ * GError "thrown" by teco_interface_event_loop_iter().
+ * Having this in a variable avoids problems with EMScripten.
+ */
+ GError *event_loop_error;
+} teco_interface;
+
/**
- * Returns the curses `COLOR_PAIR` for the given curses foreground and background `COLOR`s.
- * This is used simply to enumerate every possible color combination.
- * Note: only 256 combinations are possible due to curses portability.
+ * Returns the curses color pair for the given curses foreground and background colors.
+ * Initializes a new pair if necessary.
*
- * @param fg The curses foreground `COLOR`.
- * @param bg The curses background `COLOR`.
- * @return number for defining a curses `COLOR_PAIR`.
+ * Scinterm no longer initializes all color pairs for all combinations of
+ * the builtin foreground and background colors.
+ * Since curses guarantees only 256 color pairs, we cannot do that either.
+ * Instead we allocate color pairs beginnig at 128 on demand
+ * (similar to what Scinterm does).
+ *
+ * @note Scinterm now also has scintilla_set_color_offsets(),
+ * so we could use the lower 127 color pairs as well.
+ *
+ * @param fg curses foreground color
+ * @param bg curses background color
+ * @return curses color pair number
*/
-static inline gshort
+static gshort
teco_color_pair(gshort fg, gshort bg)
{
- return bg * (COLORS < 16 ? 8 : 16) + fg + 1;
+ static gshort last_pair = 127;
+
+ G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint));
+ gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg);
+ gpointer value = g_hash_table_lookup(teco_interface.pair_table, key);
+ if (G_LIKELY(value != NULL))
+ return GPOINTER_TO_UINT(value);
+ init_pair(++last_pair, fg, bg);
+ g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair));
+ return last_pair;
}
/**
@@ -201,8 +278,8 @@ teco_color_attr(gshort fg, gshort bg)
* Basic support for monochrome terminals:
* Every background, that is not black is assumed to be a
* dark-on-bright area, rendered in reverse.
- * This will at least work with the terminal.tes
- * color scheme.
+ * This will at least work with the terminal.tes and contrast.tes
+ * color schemes.
*/
return bg != COLOR_BLACK ? A_REVERSE : 0;
}
@@ -336,61 +413,6 @@ teco_view_free(teco_view_t *ctx)
scintilla_delete(ctx);
}
-static struct {
- /**
- * Mapping of the first 16 curses color codes (that may or may not
- * correspond with the standard terminal color codes) to
- * Scintilla-compatible RGB values (red is LSB) to initialize after
- * Curses startup.
- * Negative values mean no color redefinition (keep the original
- * palette entry).
- */
- gint32 color_table[16];
-
- /**
- * Mapping of the first 16 curses color codes to their
- * original values for restoring them on shutdown.
- * Unfortunately, this may not be supported on all
- * curses ports, so this array may be unused.
- */
- struct {
- gshort r, g, b;
- } orig_color_table[16];
-
- int stdin_orig, stdout_orig, stderr_orig;
- SCREEN *screen;
- FILE *screen_tty;
-
- WINDOW *info_window;
- enum {
- TECO_INFO_TYPE_BUFFER = 0,
- TECO_INFO_TYPE_QREG
- } info_type;
- teco_string_t info_current;
- gboolean info_dirty;
-
- WINDOW *msg_window;
-
- WINDOW *cmdline_window, *cmdline_pad;
- guint cmdline_len, cmdline_rubout_len;
-
- /**
- * Pad used exclusively for wgetch() as it will not
- * result in unwanted wrefresh().
- */
- WINDOW *input_pad;
- GQueue *input_queue;
-
- teco_curses_info_popup_t popup;
- gsize popup_prefix_len;
-
- /**
- * GError "thrown" by teco_interface_event_loop_iter().
- * Having this in a variable avoids problems with EMScripten.
- */
- GError *event_loop_error;
-} teco_interface;
-
static void teco_interface_init_color_safe(guint color, guint32 rgb);
static void teco_interface_restore_colors(void);
@@ -404,7 +426,6 @@ static void teco_interface_resize_all_windows(void);
static void teco_interface_set_window_title(const gchar *title);
static void teco_interface_draw_info(void);
-static void teco_interface_draw_cmdline(void);
void
teco_interface_init(void)
@@ -418,11 +439,12 @@ teco_interface_init(void)
teco_curses_info_popup_init(&teco_interface.popup);
+ teco_cmdline_init();
/*
- * Make sure we have a string for the info line
- * even if teco_interface_info_update() is never called.
+ * The default INDIC_STRIKE wouldn't be visible.
+ * Instead we use INDIC_SQUIGGLE, which is rendered as A_UNDERLINE.
*/
- teco_string_init(&teco_interface.info_current, PACKAGE_NAME, strlen(PACKAGE_NAME));
+ teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE);
/*
* On all platforms except Curses/XTerm, it's
@@ -561,7 +583,7 @@ teco_interface_init_color(guint color, guint32 rgb)
((color & 0x1) << 2) | ((color & 0x4) >> 2);
#endif
- if (teco_interface.cmdline_window) {
+ if (teco_interface.input_pad) {
/* interactive mode */
if (!can_change_color())
return;
@@ -717,6 +739,9 @@ teco_interface_init_interactive(GError **error)
teco_interface_init_screen();
+ teco_interface.pair_table = g_hash_table_new(g_direct_hash, g_direct_equal);
+ start_color();
+
/*
* On UNIX terminals, the escape key is usually
* delivered as the escape character even though function
@@ -768,8 +793,12 @@ teco_interface_init_interactive(GError **error)
leaveok(stdscr, TRUE);
teco_interface.info_window = newwin(1, 0, 0, 0);
- teco_interface.msg_window = newwin(1, 0, LINES - 2, 0);
- teco_interface.cmdline_window = newwin(0, 0, LINES - 1, 0);
+ teco_interface.msg_window = newwin(1, 0, LINES - teco_cmdline.height - 1, 0);
+
+ WINDOW *cmdline_win = teco_view_get_window(teco_cmdline.view);
+ wresize(cmdline_win, teco_cmdline.height, COLS);
+ mvwin(cmdline_win, LINES - teco_cmdline.height, 0);
+ teco_cmdline_resized(COLS);
teco_interface.input_pad = newpad(1, 1);
/*
@@ -783,7 +812,7 @@ teco_interface_init_interactive(GError **error)
* must always be TRUE so we receive KEY_RESIZE.
*/
keypad(teco_interface.input_pad, TRUE);
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
teco_interface.input_queue = g_queue_new();
@@ -861,39 +890,37 @@ teco_interface_restore_batch(void)
#endif
/*
- * cmdline_window determines whether we're in batch mode.
+ * input_pad determines whether we're in batch mode.
*/
- if (teco_interface.cmdline_window) {
- delwin(teco_interface.cmdline_window);
- teco_interface.cmdline_window = NULL;
+ if (teco_interface.input_pad) {
+ delwin(teco_interface.input_pad);
+ teco_interface.input_pad = NULL;
}
}
static void
teco_interface_resize_all_windows(void)
{
- int lines, cols; /* screen dimensions */
-
- getmaxyx(stdscr, lines, cols);
-
- wresize(teco_interface.info_window, 1, cols);
+ wresize(teco_interface.info_window, 1, COLS);
wresize(teco_view_get_window(teco_interface_current_view),
- lines - 3, cols);
- wresize(teco_interface.msg_window, 1, cols);
- mvwin(teco_interface.msg_window, lines - 2, 0);
- wresize(teco_interface.cmdline_window, 1, cols);
- mvwin(teco_interface.cmdline_window, lines - 1, 0);
+ LINES - 2 - teco_cmdline.height, COLS);
+ wresize(teco_interface.msg_window, 1, COLS);
+ mvwin(teco_interface.msg_window, LINES - 1 - teco_cmdline.height, 0);
+
+ WINDOW *cmdline_win = teco_view_get_window(teco_cmdline.view);
+ wresize(cmdline_win, teco_cmdline.height, COLS);
+ mvwin(cmdline_win, LINES - teco_cmdline.height, 0);
+ teco_cmdline_resized(COLS);
teco_interface_draw_info();
teco_interface_msg_clear(); /* FIXME: use saved message */
teco_interface_popup_clear();
- teco_interface_draw_cmdline();
}
void
teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
{
- if (!teco_interface.cmdline_window) { /* batch mode */
+ if (!teco_interface.input_pad) { /* batch mode */
teco_interface_stdio_msg(type, str, len);
return;
}
@@ -936,7 +963,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len)
void
teco_interface_msg_clear(void)
{
- if (!teco_interface.cmdline_window) /* batch mode */
+ if (!teco_interface.input_pad) /* batch mode */
return;
short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
@@ -950,7 +977,7 @@ teco_interface_msg_clear(void)
teco_int_t
teco_interface_getch(gboolean widechar)
{
- if (!teco_interface.cmdline_window) /* batch mode */
+ if (!teco_interface.input_pad) /* batch mode */
return teco_interface_stdio_getch(widechar);
teco_interface_refresh(FALSE);
@@ -996,7 +1023,7 @@ teco_interface_show_view(teco_view_t *view)
{
teco_interface_current_view = view;
- if (!teco_interface.cmdline_window) /* batch mode */
+ if (!teco_interface.input_pad) /* batch mode */
return;
WINDOW *current_view_win = teco_view_get_window(teco_interface_current_view);
@@ -1005,9 +1032,7 @@ teco_interface_show_view(teco_view_t *view)
* screen size might have changed since
* this view's WINDOW was last active
*/
- int lines, cols; /* screen dimensions */
- getmaxyx(stdscr, lines, cols);
- wresize(current_view_win, lines - 3, cols);
+ wresize(current_view_win, LINES - 2 - teco_cmdline.height, COLS);
/* Set up window position: never changes */
mvwin(current_view_win, 1, 0);
}
@@ -1129,26 +1154,30 @@ teco_interface_draw_info(void)
waddstr(teco_interface.info_window, PACKAGE_NAME " ");
+ teco_string_t info_current = teco_interface.info_current;
+ if (!info_current.len) {
+ info_current.data = TECO_UNNAMED_FILE;
+ info_current.len = strlen(info_current.data);
+ }
+
switch (teco_interface.info_type) {
case TECO_INFO_TYPE_QREG:
info_type_str = PACKAGE_NAME " - <QRegister> ";
teco_curses_add_wc(teco_interface.info_window,
teco_ed & TECO_ED_ICONS ? TECO_CURSES_ICONS_QREG : '-');
waddstr(teco_interface.info_window, " <QRegister> ");
- /* same formatting as in command lines */
teco_curses_format_str(teco_interface.info_window,
- teco_interface.info_current.data,
- teco_interface.info_current.len, -1);
+ info_current.data, info_current.len, -1);
break;
case TECO_INFO_TYPE_BUFFER:
info_type_str = PACKAGE_NAME " - <Buffer> ";
- g_assert(!teco_string_contains(&teco_interface.info_current, '\0'));
+ g_assert(!teco_string_contains(info_current, '\0'));
+ /* "(Unnamed)" buffer has to be looked up as "" */
teco_curses_add_wc(teco_interface.info_window,
teco_ed & TECO_ED_ICONS ? teco_curses_icons_lookup_file(teco_interface.info_current.data) : '-');
waddstr(teco_interface.info_window, " <Buffer> ");
- teco_curses_format_filename(teco_interface.info_window,
- teco_interface.info_current.data,
+ teco_curses_format_filename(teco_interface.info_window, info_current.data,
getmaxx(teco_interface.info_window) -
getcurx(teco_interface.info_window) - 1);
waddch(teco_interface.info_window, teco_interface.info_dirty ? '*' : ' ');
@@ -1164,8 +1193,7 @@ teco_interface_draw_info(void)
* Make sure the title will consist only of printable characters.
*/
g_autofree gchar *info_current_printable;
- info_current_printable = teco_string_echo(teco_interface.info_current.data,
- teco_interface.info_current.len);
+ info_current_printable = teco_string_echo(info_current.data, info_current.len);
g_autofree gchar *title = g_strconcat(info_type_str, info_current_printable,
teco_interface.info_dirty ? "*" : "", NULL);
teco_interface_set_window_title(title);
@@ -1185,123 +1213,14 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg)
void
teco_interface_info_update_buffer(const teco_buffer_t *buffer)
{
- const gchar *filename = buffer->filename ? : UNNAMED_FILE;
-
teco_string_clear(&teco_interface.info_current);
- teco_string_init(&teco_interface.info_current, filename, strlen(filename));
- teco_interface.info_dirty = buffer->dirty;
+ teco_string_init(&teco_interface.info_current, buffer->filename,
+ buffer->filename ? strlen(buffer->filename) : 0);
+ teco_interface.info_dirty = buffer->state > TECO_BUFFER_CLEAN;
teco_interface.info_type = TECO_INFO_TYPE_BUFFER;
/* NOTE: drawn in teco_interface_event_loop_iter() */
}
-void
-teco_interface_cmdline_update(const teco_cmdline_t *cmdline)
-{
- /*
- * Especially important on PDCurses, which can crash
- * in newpad() when run with --fake-cmdline.
- */
- if (!teco_interface.cmdline_window) /* batch mode */
- return;
-
- /*
- * Replace entire pre-formatted command-line.
- * We don't know if it is similar to the last one,
- * so resizing makes no sense.
- * We approximate the size of the new formatted command-line,
- * wasting a few bytes for control characters and
- * multi-byte Unicode sequences.
- */
- if (teco_interface.cmdline_pad)
- delwin(teco_interface.cmdline_pad);
-
- int max_cols = 1;
- for (guint i = 0; i < cmdline->str.len; i++)
- max_cols += TECO_IS_CTL(cmdline->str.data[i]) ? 3 : 1;
- teco_interface.cmdline_pad = newpad(1, max_cols);
-
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
- wattrset(teco_interface.cmdline_pad, teco_color_attr(fg, bg));
-
- /* format effective command line */
- teco_interface.cmdline_len =
- teco_curses_format_str(teco_interface.cmdline_pad,
- cmdline->str.data, cmdline->effective_len, -1);
-
- /*
- * A_BOLD should result in either a bold font or a brighter
- * color both on 8 and 16 color terminals.
- * This is not quite color-scheme-agnostic, but works
- * with both the `terminal` and `solarized` themes.
- * This problem will be gone once we use a Scintilla view
- * as command line, since we can then define a style
- * for rubbed out parts of the command line which will
- * be user-configurable.
- * The attributes, supported by the terminal can theoretically
- * be queried with term_attrs().
- */
- wattron(teco_interface.cmdline_pad, A_UNDERLINE | A_BOLD);
-
- /*
- * Format rubbed-out command line.
- * NOTE: This formatting will never be truncated since we're
- * writing into the pad which is large enough.
- */
- teco_interface.cmdline_rubout_len =
- teco_curses_format_str(teco_interface.cmdline_pad, cmdline->str.data + cmdline->effective_len,
- cmdline->str.len - cmdline->effective_len, -1);
-
- /*
- * Highlight cursor after effective command line
- * FIXME: This should use SCI_GETCARETFORE().
- */
- attr_t attr = A_NORMAL;
- short pair = 0;
- if (teco_interface.cmdline_rubout_len) {
- wmove(teco_interface.cmdline_pad, 0, teco_interface.cmdline_len);
- wattr_get(teco_interface.cmdline_pad, &attr, &pair, NULL);
- wchgat(teco_interface.cmdline_pad, 1,
- (attr & (A_UNDERLINE | A_REVERSE)) ^ A_REVERSE, pair, NULL);
- } else {
- teco_interface.cmdline_len++;
- wattr_get(teco_interface.cmdline_pad, &attr, &pair, NULL);
- wattr_set(teco_interface.cmdline_pad, (attr & ~(A_UNDERLINE | A_BOLD)) ^ A_REVERSE, pair, NULL);
- waddch(teco_interface.cmdline_pad, ' ');
- }
-
- teco_interface_draw_cmdline();
-}
-
-static void
-teco_interface_draw_cmdline(void)
-{
- /* total width available for command line */
- guint total_width = getmaxx(teco_interface.cmdline_window) - 1;
-
- /* beginning of command line to show */
- guint disp_offset = teco_interface.cmdline_len -
- MIN(teco_interface.cmdline_len,
- total_width/2 + teco_interface.cmdline_len % MAX(total_width/2, 1));
- /*
- * length of command line to show
- *
- * NOTE: we do not use getmaxx(cmdline_pad) here since it may be
- * larger than the text the pad contains.
- */
- guint disp_len = MIN(total_width, teco_interface.cmdline_len +
- teco_interface.cmdline_rubout_len - disp_offset);
-
- short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0));
- short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0));
-
- wattrset(teco_interface.cmdline_window, teco_color_attr(fg, bg));
- mvwaddch(teco_interface.cmdline_window, 0, 0, '*' | A_BOLD);
- teco_curses_clrtobot(teco_interface.cmdline_window);
- copywin(teco_interface.cmdline_pad, teco_interface.cmdline_window,
- 0, disp_offset, 0, 1, 0, disp_len, FALSE);
-}
-
#if PDCURSES
/*
@@ -1310,7 +1229,20 @@ teco_interface_draw_cmdline(void)
* default clipboard ("~") as we do not know whether
* it corresponds to the X11 PRIMARY, SECONDARY or
* CLIPBOARD selections.
+ *
+ * On XCurses we must not (and don't have to) probe
+ * the clipboard as it would be before Xinitscr().
*/
+#ifdef XCURSES
+
+static void
+teco_interface_init_clipboard(void)
+{
+ teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+}
+
+#else /* XCURSES */
+
static void
teco_interface_init_clipboard(void)
{
@@ -1335,6 +1267,8 @@ teco_interface_init_clipboard(void)
teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
}
+#endif /* !XCURSES */
+
gboolean
teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error)
{
@@ -1506,7 +1440,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G
* We restore all changed Curses settings before returning
* to be on the safe side.
*/
- halfdelay(1); /* 100ms timeout */
+ wtimeout(teco_interface.input_pad, 100);
/* don't interpret escape sequences */
keypad(teco_interface.input_pad, FALSE);
@@ -1573,7 +1507,7 @@ teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, G
cleanup:
keypad(teco_interface.input_pad, TRUE);
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
return ret;
}
@@ -1624,7 +1558,7 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
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')) {
+ if (teco_string_contains(command, '\0')) {
teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
return FALSE;
}
@@ -1685,7 +1619,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
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')) {
+ if (teco_string_contains(command, '\0')) {
teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
return FALSE;
}
@@ -1771,7 +1705,7 @@ void
teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize name_len,
gboolean highlight)
{
- if (teco_interface.cmdline_window)
+ if (teco_interface.input_pad)
/* interactive mode */
teco_curses_info_popup_add(&teco_interface.popup, type, name, name_len, highlight);
}
@@ -1779,7 +1713,7 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
void
teco_interface_popup_show(gsize prefix_len)
{
- if (!teco_interface.cmdline_window)
+ if (!teco_interface.input_pad)
/* batch mode */
return;
@@ -1793,7 +1727,7 @@ teco_interface_popup_show(gsize prefix_len)
void
teco_interface_popup_scroll(void)
{
- if (!teco_interface.cmdline_window)
+ if (!teco_interface.input_pad)
/* batch mode */
return;
@@ -1880,15 +1814,13 @@ teco_interface_is_interrupted(void)
void
teco_interface_refresh(gboolean force)
{
- if (!teco_interface.cmdline_window)
+ if (!teco_interface.input_pad)
/* batch mode */
return;
#ifdef NETBSD_CURSES
/* works around crashes in doupdate() */
- gint y, x;
- getmaxyx(stdscr, y, x);
- if (G_UNLIKELY(x <= 1 || y <= 1))
+ if (G_UNLIKELY(COLS <= 1 || LINES <= 1))
return;
#endif
@@ -1905,7 +1837,7 @@ teco_interface_refresh(gboolean force)
wnoutrefresh(teco_interface.info_window);
teco_view_noutrefresh(teco_interface_current_view);
wnoutrefresh(teco_interface.msg_window);
- wnoutrefresh(teco_interface.cmdline_window);
+ teco_view_noutrefresh(teco_cmdline.view);
teco_curses_info_popup_noutrefresh(&teco_interface.popup);
doupdate();
}
@@ -1944,15 +1876,18 @@ teco_interface_process_mevent(MEVENT *event, GError **error)
event->y, event->x);
if (insert && machine->current->insert_completion_cb) {
- /* successfully clicked popup item */
+ /*
+ * Successfully clicked popup item.
+ * `insert` is the empty string for the "(Unnamed)" buffer.
+ */
const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len,
insert->len - teco_interface.popup_prefix_len};
- if (!machine->current->insert_completion_cb(machine, &insert_suffix, error))
+ 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);
+ teco_cmdline_update();
}
return TRUE;
@@ -2047,6 +1982,20 @@ teco_interface_process_mevent(MEVENT *event, GError **error)
return teco_cmdline_keymacro("MOUSE", -1, error);
}
+#ifdef __PDCURSES__
+
+static gboolean
+teco_interface_getmouse(GError **error)
+{
+ MEVENT event;
+
+ /* in contrast to ncurses, there is no separate mouse event queue */
+ return getmouse(&event) != OK ||
+ teco_interface_process_mevent(&event, error);
+}
+
+#else /* __PDCURSES__ */
+
static gboolean
teco_interface_getmouse(GError **error)
{
@@ -2059,6 +2008,8 @@ teco_interface_getmouse(GError **error)
return TRUE;
}
+#endif /* !__PDCURSES__ */
+
#endif /* NCURSES_MOUSE_VERSION >= 2 */
static gint
@@ -2080,7 +2031,8 @@ teco_interface_blocking_getch(void)
gboolean new_mousekey = (teco_ed & TECO_ED_MOUSEKEY) != 0;
if (new_mousekey != old_mousekey) {
old_mousekey = new_mousekey;
- mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED);
+ mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED) |
+ BUTTON_SHIFT | BUTTON_CTRL | BUTTON_ALT;
#ifdef __PDCURSES__
/*
* On PDCurses it's crucial NOT to mask for BUTTONX_CLICKED.
@@ -2095,7 +2047,20 @@ teco_interface_blocking_getch(void)
/* no special <CTRL/C> handling */
raw();
- nodelay(teco_interface.input_pad, FALSE);
+
+ /*
+ * Make sure we return when it's time to create recovery dumps.
+ */
+ if (teco_ring_recovery_interval != 0) {
+ if (G_UNLIKELY(!teco_interface.recovery_timer))
+ teco_interface.recovery_timer = g_timer_new();
+ gdouble elapsed = g_timer_elapsed(teco_interface.recovery_timer, NULL);
+ wtimeout(teco_interface.input_pad,
+ MAX((gdouble)teco_ring_recovery_interval - elapsed, 0)*1000);
+ } else {
+ wtimeout(teco_interface.input_pad, -1);
+ }
+
/*
* Memory limiting is stopped temporarily, since it might otherwise
* constantly place 100% load on the CPU.
@@ -2105,12 +2070,18 @@ teco_interface_blocking_getch(void)
teco_memory_start_limiting();
/* allow asynchronous interruptions on <CTRL/C> */
teco_interrupted = FALSE;
- nodelay(teco_interface.input_pad, TRUE);
+ wtimeout(teco_interface.input_pad, 0);
#if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32)
noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */
cbreak();
#endif
+ if (key == ERR && teco_ring_recovery_interval != 0 &&
+ g_timer_elapsed(teco_interface.recovery_timer, NULL) >= teco_ring_recovery_interval) {
+ teco_ring_dump_recovery();
+ g_timer_start(teco_interface.recovery_timer);
+ }
+
return key;
}
@@ -2136,6 +2107,7 @@ teco_interface_event_loop_iter(void)
const teco_view_t *last_view = teco_interface_current_view;
sptr_t last_vpos = teco_interface_ssm(SCI_GETFIRSTVISIBLELINE, 0, 0);
+ guint last_cmdline_height = teco_cmdline.height;
switch (key) {
case ERR:
@@ -2262,6 +2234,10 @@ teco_interface_event_loop_iter(void)
}
}
+ if (G_UNLIKELY(teco_cmdline.height != last_cmdline_height))
+ /* command line height was changed with h,5EJ */
+ teco_interface_resize_all_windows();
+
/*
* Scintilla has been patched to avoid any automatic scrolling since that
* has been benchmarked to be a very costly operation.
@@ -2286,8 +2262,6 @@ teco_interface_event_loop(GError **error)
if (!teco_interface_init_interactive(error))
return FALSE;
- 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);
/*
@@ -2342,10 +2316,6 @@ teco_interface_cleanup(void)
teco_string_clear(&teco_interface.info_current);
if (teco_interface.input_queue)
g_queue_free(teco_interface.input_queue);
- if (teco_interface.cmdline_window)
- delwin(teco_interface.cmdline_window);
- if (teco_interface.cmdline_pad)
- delwin(teco_interface.cmdline_pad);
if (teco_interface.msg_window)
delwin(teco_interface.msg_window);
if (teco_interface.input_pad)
@@ -2370,4 +2340,10 @@ teco_interface_cleanup(void)
close(teco_interface.stderr_orig);
if (teco_interface.stdout_orig >= 0)
close(teco_interface.stdout_orig);
+
+ if (teco_interface.pair_table)
+ g_hash_table_destroy(teco_interface.pair_table);
+
+ if (teco_interface.recovery_timer)
+ g_timer_destroy(teco_interface.recovery_timer);
}
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index aaa0a65..f2c8dc8 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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,8 @@
#include "gtk-label.h"
#include "gtk-info-popup.h"
+#define TECO_UNNAMED_FILE "(Unnamed)"
+
/*
* FIXME: This is redundant with curses-info-popup.c.
*/
@@ -37,6 +39,7 @@ typedef struct {
teco_stailq_entry_t entry;
teco_popup_entry_type_t type;
+ /** entry name or empty string for the "(Unnamed)" buffer */
teco_string_t name;
gboolean highlight;
} teco_popup_entry_t;
@@ -109,10 +112,10 @@ teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpoint
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));
+ 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);
+ str.data, (gulong)str.len);
}
static void
@@ -249,6 +252,10 @@ teco_gtk_info_popup_new(void)
GIcon *
teco_gtk_info_popup_get_icon_for_path(const gchar *path, const gchar *fallback_name)
{
+ if (!path || !*path)
+ /* "(Unnamed)" file */
+ return g_icon_new_for_string(fallback_name, NULL);
+
GIcon *icon = NULL;
g_autoptr(GFile) file = g_file_new_for_path(path);
@@ -299,7 +306,7 @@ teco_gtk_info_popup_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
static void
teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t type,
- const gchar *name, gssize len, gboolean highlight)
+ const gchar *name, gsize len, gboolean highlight)
{
g_return_if_fail(self != NULL);
g_return_if_fail(TECO_IS_GTK_INFO_POPUP(self));
@@ -318,12 +325,8 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
const gchar *fallback = type == TECO_POPUP_FILE ? "text-x-generic"
: "folder";
- /*
- * `name` is not guaranteed to be null-terminated.
- */
- g_autofree gchar *path = len < 0 ? g_strdup(name) : g_strndup(name, len);
-
- g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(path, fallback);
+ /* name comes from a teco_string_t and is guaranteed to be null-terminated */
+ g_autoptr(GIcon) icon = teco_gtk_info_popup_get_icon_for_path(name, fallback);
if (icon) {
gint width, height;
gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &width, &height);
@@ -335,7 +338,7 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
}
}
- GtkWidget *label = teco_gtk_label_new(name, len);
+ GtkWidget *label = teco_gtk_label_new(name, len, TECO_UNNAMED_FILE);
/*
* Gtk v3.20 changed the CSS element names.
* Adding a style class eases writing a portable fallback.css.
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index ad79b84..3ce8e1f 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 e4b2823..6e05045 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -32,7 +32,7 @@
#include "gtk-label.h"
-#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
+#define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16))
struct _TecoGtkLabel {
GtkLabel parent_instance;
@@ -40,7 +40,10 @@ struct _TecoGtkLabel {
PangoColor fg, bg;
guint16 fg_alpha, bg_alpha;
+ /** text backing the label or empty string for fallback */
teco_string_t string;
+ /** fallback string to render if `string` is empty or NULL */
+ const gchar *fallback;
};
G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL)
@@ -122,11 +125,21 @@ teco_gtk_label_class_init(TecoGtkLabelClass *klass)
static void teco_gtk_label_init(TecoGtkLabel *self) {}
+/**
+ * Create new TECO label widget.
+ *
+ * @param str String to render (can be NULL)
+ * @param len Length of str or negative value for null-terminated strings.
+ * @param fallback Null-terminated fallback string to render
+ * instead of empty strings.
+ * Must be a string constant with global lifetime or NULL.
+ */
GtkWidget *
-teco_gtk_label_new(const gchar *str, gssize len)
+teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback)
{
TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL));
+ widget->fallback = fallback;
teco_gtk_label_set_text(widget, str, len);
return GTK_WIDGET(widget);
@@ -251,27 +264,30 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
teco_string_clear(&self->string);
teco_string_init(&self->string, str, len < 0 ? strlen(str) : len);
+ teco_string_t string = self->string;
+ if (!string.len && self->fallback) {
+ string.data = (gchar *)self->fallback;
+ string.len = strlen(string.data);
+ }
+
g_autofree gchar *plaintext = NULL;
+ PangoAttrList *attribs = NULL;
- if (self->string.len > 0) {
- PangoAttrList *attribs = NULL;
+ teco_gtk_label_parse_string(string.data, string.len,
+ &self->fg, self->fg_alpha,
+ &self->bg, self->bg_alpha,
+ &attribs, &plaintext);
- teco_gtk_label_parse_string(self->string.data, self->string.len,
- &self->fg, self->fg_alpha,
- &self->bg, self->bg_alpha,
- &attribs, &plaintext);
-
- gtk_label_set_attributes(GTK_LABEL(self), attribs);
- pango_attr_list_unref(attribs);
- }
+ gtk_label_set_attributes(GTK_LABEL(self), attribs);
+ pango_attr_list_unref(attribs);
gtk_label_set_text(GTK_LABEL(self), plaintext);
}
-const teco_string_t *
+teco_string_t
teco_gtk_label_get_text(TecoGtkLabel *self)
{
- return &self->string;
+ return self->string;
}
/**
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index 3cd4cb9..a84608a 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -24,10 +24,10 @@
#define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type()
G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
-GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
+GtkWidget *teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback);
void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
-const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self);
+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 dcf3660..ace3a2f 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -19,6 +19,7 @@
#include "config.h"
#endif
+#include <stdlib.h>
#include <string.h>
#include <signal.h>
@@ -27,6 +28,7 @@
#include <glib/gstdio.h>
#ifdef G_OS_UNIX
+#include <unistd.h>
#include <glib-unix.h>
#endif
@@ -60,6 +62,7 @@
//#define DEBUG
static gboolean teco_interface_busy_timeout_cb(gpointer user_data);
+static gboolean teco_interface_dump_recovery_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,
@@ -77,21 +80,13 @@ static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *
static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
static gchar teco_interface_get_ansi_key(GdkEventKey *event);
-#define UNNAMED_FILE "(Unnamed)"
+#define TECO_UNNAMED_FILE "(Unnamed)"
#define USER_CSS_FILE ".teco_css"
/** printf() format for CSS RGB colors given as guint32 */
#define CSS_COLOR_FORMAT "#%06" G_GINT32_MODIFIER "X"
-/** Style used for the asterisk at the beginning of the command line */
-#define STYLE_ASTERISK 16
-
-/** Indicator number used for control characters in the command line */
-#define INDIC_CONTROLCHAR (INDIC_CONTAINER+0)
-/** Indicator number used for the rubbed out part of the command line */
-#define INDIC_RUBBEDOUT (INDIC_CONTAINER+1)
-
/** Convert Scintilla-style BGR color triple to RGB. */
static inline guint32
teco_bgr2rgb(guint32 bgr)
@@ -109,9 +104,10 @@ static struct {
TECO_INFO_TYPE_BUFFER_DIRTY,
TECO_INFO_TYPE_QREG
} info_type;
+ /* current document's name or empty string for "(Unnamed)" buffer */
teco_string_t info_current;
- gboolean no_csd;
+ gboolean no_csd, detach;
gint xembed_id;
GtkWidget *info_bar_widget;
@@ -125,7 +121,6 @@ static struct {
GtkWidget *message_bar_widget;
GtkWidget *message_widget;
- teco_view_t *cmdline_view;
GtkIMContext *input_method;
GtkWidget *popup_widget;
@@ -139,6 +134,26 @@ static struct {
void
teco_interface_init(void)
{
+#ifdef G_OS_UNIX
+ if (teco_interface.detach) {
+ /*
+ * NOTE: There is also daemon() on BSD/Linux,
+ * but the following should be more portable.
+ */
+ pid_t pid = fork();
+ g_assert(pid >= 0);
+ if (pid != 0)
+ /* parent process */
+ exit(EXIT_SUCCESS);
+
+ setsid();
+
+ g_freopen("/dev/null", "r", stdin);
+ g_freopen("/dev/null", "a+", stdout);
+ g_freopen("/dev/null", "a+", stderr);
+ }
+#endif
+
/*
* gtk_init() is not necessary when using gtk_get_option_group(),
* but this will open the default display.
@@ -187,7 +202,7 @@ teco_interface_init(void)
*/
teco_interface.info_bar_widget = gtk_header_bar_new();
gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar");
- teco_interface.info_name_widget = teco_gtk_label_new("", 0);
+ teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0, NULL);
gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget),
@@ -227,7 +242,7 @@ teco_interface_init(void)
/*
* Overlay widget will allow overlaying the Scintilla view
* and message widgets with the info popup.
- * Therefore overlay_vbox (containing the view and popup)
+ * Therefore overlay_vbox (containing the view and message line)
* will be the main child of the overlay.
*/
GtkWidget *overlay_widget = gtk_overlay_new();
@@ -273,7 +288,7 @@ teco_interface_init(void)
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
GtkWidget *message_bar_content =
gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget));
- teco_interface.message_widget = teco_gtk_label_new(NULL, 0);
+ teco_interface.message_widget = teco_gtk_label_new(NULL, 0, NULL);
/* eases writing portable fallback.css that avoids CSS element names */
gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget),
"label");
@@ -286,23 +301,9 @@ teco_interface_init(void)
gtk_container_add(GTK_CONTAINER(overlay_widget), overlay_vbox);
gtk_box_pack_start(GTK_BOX(vbox), overlay_widget, TRUE, TRUE, 0);
- teco_interface.cmdline_view = teco_view_new();
- teco_view_setup(teco_interface.cmdline_view);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETUNDOCOLLECTION, FALSE, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETVSCROLLBAR, FALSE, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINTYPEN, 1, SC_MARGIN_TEXT);
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETSTYLE, 0, STYLE_ASTERISK);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETMARGINWIDTHN, 1,
- teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTWIDTH, STYLE_ASTERISK, (sptr_t)"*"));
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
- /* only required as long as we avoid ordinary character representations */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_CONTROLCHAR, INDIC_ROUNDBOX);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETALPHA, INDIC_CONTROLCHAR, 128);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETSTYLE, INDIC_RUBBEDOUT, INDIC_STRIKE);
- /* 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 = GTK_WIDGET(teco_interface.cmdline_view);
+ teco_cmdline_init();
+
+ GtkWidget *cmdline_widget = GTK_WIDGET(teco_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);
@@ -337,10 +338,6 @@ teco_interface_init(void)
*/
gtk_widget_set_can_focus(teco_interface.message_widget, FALSE);
gtk_widget_set_can_focus(teco_interface.info_name_widget, FALSE);
-
- teco_cmdline_t empty_cmdline;
- memset(&empty_cmdline, 0, sizeof(empty_cmdline));
- teco_interface_cmdline_update(&empty_cmdline);
}
static void
@@ -366,6 +363,11 @@ teco_interface_get_options(void)
G_OPTION_ARG_INT, &teco_interface.xembed_id,
"Embed into an existing X11 Window.", "ID"},
#endif
+#ifdef G_OS_UNIX
+ {"detach", 'd', G_OPTION_FLAG_IN_MAIN,
+ G_OPTION_ARG_NONE, &teco_interface.detach,
+ "Detach from controlling terminal (daemonize).", NULL},
+#endif
{NULL}
};
@@ -527,8 +529,13 @@ teco_interface_refresh_info(void)
gtk_style_context_remove_class(style, "dirty");
g_auto(teco_string_t) info_current_temp;
- teco_string_init(&info_current_temp,
- teco_interface.info_current.data, teco_interface.info_current.len);
+
+ if (!teco_interface.info_current.len)
+ teco_string_init(&info_current_temp, TECO_UNNAMED_FILE, strlen(TECO_UNNAMED_FILE));
+ else
+ teco_string_init(&info_current_temp,
+ teco_interface.info_current.data, teco_interface.info_current.len);
+
if (teco_interface.info_type == TECO_INFO_TYPE_BUFFER_DIRTY)
teco_string_append_c(&info_current_temp, '*');
teco_gtk_label_set_text(TECO_GTK_LABEL(teco_interface.info_name_widget),
@@ -596,87 +603,11 @@ teco_interface_info_update_qreg(const teco_qreg_t *reg)
void
teco_interface_info_update_buffer(const teco_buffer_t *buffer)
{
- const gchar *filename = buffer->filename ? : UNNAMED_FILE;
-
teco_string_clear(&teco_interface.info_current);
- teco_string_init(&teco_interface.info_current, filename, strlen(filename));
- teco_interface.info_type = buffer->dirty ? TECO_INFO_TYPE_BUFFER_DIRTY
- : TECO_INFO_TYPE_BUFFER;
-}
-
-/**
- * Insert a single character into the command line.
- *
- * @fixme
- * Control characters should be inserted verbatim since the Scintilla
- * representations of them should be preferred.
- * However, Scintilla would break the line on every CR/LF and there is
- * currently no way to prevent this.
- * Scintilla needs to be patched.
- *
- * @see teco_view_set_representations()
- * @see teco_curses_format_str()
- */
-static void
-teco_interface_cmdline_insert_c(gchar chr)
-{
- gchar buffer[3+1] = "";
-
- /*
- * NOTE: This mapping is similar to teco_view_set_representations()
- */
- switch (chr) {
- case '\e': strcpy(buffer, "$"); break;
- case '\r': strcpy(buffer, "CR"); break;
- case '\n': strcpy(buffer, "LF"); break;
- case '\t': strcpy(buffer, "TAB"); break;
- default:
- if (TECO_IS_CTL(chr)) {
- buffer[0] = '^';
- buffer[1] = TECO_CTL_ECHO(chr);
- buffer[2] = '\0';
- }
- }
-
- if (*buffer) {
- gsize len = strlen(buffer);
- teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, len, (sptr_t)buffer);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_CONTROLCHAR, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE,
- teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - len, len);
- } else {
- teco_view_ssm(teco_interface.cmdline_view, SCI_APPENDTEXT, 1, (sptr_t)&chr);
- }
-}
-
-void
-teco_interface_cmdline_update(const teco_cmdline_t *cmdline)
-{
- /*
- * We don't know if the new command line is similar to
- * the old one, so we can just as well rebuild it.
- *
- * NOTE: teco_view_ssm() already locks the GDK lock.
- */
- teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALL, 0, 0);
-
- /* format effective command line */
- for (guint i = 0; i < cmdline->effective_len; i++)
- teco_interface_cmdline_insert_c(cmdline->str.data[i]);
-
- /* cursor should be after effective command line */
- guint pos = teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_GOTOPOS, pos, 0);
-
- /* format rubbed out command line */
- for (guint i = cmdline->effective_len; i < cmdline->str.len; i++)
- teco_interface_cmdline_insert_c(cmdline->str.data[i]);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETINDICATORCURRENT, INDIC_RUBBEDOUT, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICATORFILLRANGE, pos,
- teco_view_ssm(teco_interface.cmdline_view, SCI_GETLENGTH, 0, 0) - pos);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_SCROLLCARET, 0, 0);
+ teco_string_init(&teco_interface.info_current, buffer->filename,
+ buffer->filename ? strlen(buffer->filename) : 0);
+ teco_interface.info_type = buffer->state > TECO_BUFFER_CLEAN
+ ? TECO_INFO_TYPE_BUFFER_DIRTY : TECO_INFO_TYPE_BUFFER;
}
static GdkAtom
@@ -885,40 +816,6 @@ teco_interface_set_css_variables(teco_view_t *view)
guint32 calltip_bg_color = teco_view_ssm(view, SCI_STYLEGETBACK, STYLE_CALLTIP, 0);
/*
- * FIXME: Font and colors of Scintilla views cannot be set via CSS.
- * But some day, there will be a way to send messages to the commandline view
- * from SciTECO code via ES.
- * Configuration will then be in the hands of color schemes.
- *
- * NOTE: We don't actually know apriori how large the font_size buffer should be,
- * but luckily SCI_STYLEGETFONT with a sptr==0 will return only the size.
- * This is undocumented in the Scintilla docs.
- */
- g_autofree gchar *font_name = g_malloc(teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, 0) + 1);
- teco_view_ssm(view, SCI_STYLEGETFONT, STYLE_DEFAULT, (sptr_t)font_name);
-
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_DEFAULT, default_fg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_DEFAULT, default_bg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)font_name);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETSIZE, STYLE_DEFAULT,
- teco_view_ssm(view, SCI_STYLEGETSIZE, STYLE_DEFAULT, 0));
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLECLEARALL, 0, 0);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETFORE, STYLE_CALLTIP, calltip_fg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBACK, STYLE_CALLTIP, calltip_bg_color);
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETCARETFORE,
- teco_view_ssm(view, SCI_GETCARETFORE, 0, 0), 0);
- /* used for the asterisk at the beginning of the command line */
- teco_view_ssm(teco_interface.cmdline_view, SCI_STYLESETBOLD, STYLE_ASTERISK, TRUE);
- /* used for character representations */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_CONTROLCHAR, default_fg_color);
- /* used for the rubbed out command line */
- teco_view_ssm(teco_interface.cmdline_view, SCI_INDICSETFORE, INDIC_RUBBEDOUT, default_fg_color);
- /* this somehow gets reset */
- teco_view_ssm(teco_interface.cmdline_view, SCI_MARGINSETTEXT, 0, (sptr_t)"*");
-
- guint text_height = teco_view_ssm(teco_interface.cmdline_view, SCI_TEXTHEIGHT, 0, 0);
-
- /*
* Generates a CSS that sets some predefined color variables.
* This effectively "exports" Scintilla styles into the CSS
* world.
@@ -944,12 +841,13 @@ teco_interface_set_css_variables(teco_view_t *view)
gtk_css_provider_load_from_data(teco_interface.css_var_provider, css, -1, NULL);
/*
- * The font and size of the commandline view might have changed,
+ * The font and size and height of the command-line view might have changed,
* so we resize it.
* 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(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height);
+ g_assert(teco_cmdline.height > 0);
+ gtk_widget_set_size_request(GTK_WIDGET(teco_cmdline.view), -1,
+ teco_cmdline.height*teco_cmdline_ssm(SCI_TEXTHEIGHT, 0, 0));
}
static void
@@ -1263,7 +1161,7 @@ teco_interface_event_loop(GError **error)
if (!scitecoconfig_reg->vtable->get_string(scitecoconfig_reg,
&scitecoconfig.data, &scitecoconfig.len, NULL, error))
return FALSE;
- if (teco_string_contains(&scitecoconfig, '\0')) {
+ if (teco_string_contains(scitecoconfig, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Null-character not allowed in filenames");
return FALSE;
@@ -1355,6 +1253,10 @@ teco_interface_event_loop(GError **error)
g_unix_signal_add(SIGTERM, teco_interface_sigterm_handler, NULL);
#endif
+ /* the interval might have been changed in the profile */
+ g_timeout_add_seconds(teco_ring_recovery_interval,
+ teco_interface_dump_recovery_cb, NULL);
+
/* don't limit while waiting for input as this might be a busy operation */
teco_memory_stop_limiting();
@@ -1413,6 +1315,20 @@ teco_interface_busy_timeout_cb(gpointer user_data)
return G_SOURCE_REMOVE;
}
+static gboolean
+teco_interface_dump_recovery_cb(gpointer user_data)
+{
+ teco_ring_dump_recovery();
+
+ /*
+ * The backup interval could have changed (6EJ).
+ * New intervals will not be effective immediately, though.
+ */
+ g_timeout_add_seconds(teco_ring_recovery_interval,
+ teco_interface_dump_recovery_cb, NULL);
+ return G_SOURCE_REMOVE;
+}
+
static void
teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
{
@@ -1420,17 +1336,12 @@ teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
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.
- */
+/** Called when the commandline widget is resized */
static void
teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation, gpointer user_data)
{
- teco_view_ssm(teco_interface.cmdline_view, SCI_SETXCARETPOLICY,
- CARET_SLOP | CARET_EVEN, allocation->width/2);
+ teco_cmdline_resized(allocation->width);
}
static gboolean
@@ -1593,7 +1504,9 @@ 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};
+ /* str is an empty string for the "(Unnamed)" buffer */
+ const teco_string_t insert = {str+teco_interface.popup_prefix_len,
+ len-teco_interface.popup_prefix_len};
teco_machine_t *machine = &teco_cmdline.machine.parent;
const teco_view_t *last_view = teco_interface_current_view;
@@ -1603,10 +1516,10 @@ teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpoint
* A auto completion should never result in program termination.
*/
if (machine->current->insert_completion_cb &&
- !machine->current->insert_completion_cb(machine, &insert, NULL))
+ !machine->current->insert_completion_cb(machine, insert, NULL))
return;
teco_interface_popup_clear();
- teco_interface_cmdline_update(&teco_cmdline);
+ teco_cmdline_update();
teco_interface_update(teco_interface_current_view != last_view);
}
diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c
index 44e3988..3a18f33 100644
--- a/src/interface-gtk/view.c
+++ b/src/interface-gtk/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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.c b/src/interface.c
index aa7e00e..2343a16 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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.h b/src/interface.h
index 9531d37..f196a83 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -118,9 +118,6 @@ void undo__teco_interface_info_update_qreg(const teco_qreg_t *);
void undo__teco_interface_info_update_buffer(const teco_buffer_t *);
/** @pure */
-void teco_interface_cmdline_update(const teco_cmdline_t *cmdline);
-
-/** @pure */
gboolean teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
GError **error);
void teco_interface_undo_set_clipboard(const gchar *name, gchar *str, gsize len);
@@ -138,7 +135,17 @@ typedef enum {
TECO_POPUP_DIRECTORY
} teco_popup_entry_type_t;
-/** @pure */
+/**
+ * Add entry to popup.
+ *
+ * @param type Entry type
+ * @param name
+ * Name string of the entry or NULL for "(Unnamed)".
+ * It is not necessarily null-terminated.
+ * @param name_len Length of string in name
+ * @param highlight Whether to highlight the entry
+ * @pure
+ */
void teco_interface_popup_add(teco_popup_entry_type_t type,
const gchar *name, gsize name_len, gboolean highlight);
/** @pure */
diff --git a/src/lexer.c b/src/lexer.c
index 2f43b76..25ea8f2 100644
--- a/src/lexer.c
+++ b/src/lexer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -26,6 +26,7 @@
#include "sciteco.h"
#include "view.h"
#include "parser.h"
+#include "core-commands.h"
#include "lexer.h"
static teco_style_t
@@ -37,16 +38,21 @@ teco_lexer_getstyle(teco_view_t *view, teco_machine_main_t *machine,
/*
* FIXME: At least this special workaround for numbers might be
* unnecessary once we get a special parser state for parsing numbers.
- *
- * FIXME: What about ^* and ^/?
- * They are currently highlighted as commands.
*/
if (machine->parent.current->keymacro_mask & TECO_KEYMACRO_MASK_START &&
chr <= 0xFF) {
if (g_ascii_isdigit(chr))
style = SCE_SCITECO_NUMBER;
- else if (strchr("+-*/#&", chr))
+ else if (strchr(",+-*/#&()", chr))
style = SCE_SCITECO_OPERATOR;
+ } else if (machine->parent.current == &teco_state_control) {
+ /*
+ * Two-character operators must always begin with caret
+ * They get a separate style, so we can extend it back to
+ * the caret in teco_lexter_step.
+ */
+ if (strchr("*/#", chr))
+ style = SCE_SCITECO_OPERATOR2;
}
/*
@@ -174,8 +180,9 @@ teco_lexer_step(teco_view_t *view, teco_machine_main_t *machine,
/*
* True comments begin with `!*` or `!!`, but only the second character gets
* the correct style by default, so we extend it backwards.
+ * The same is true for two-letter operators.
*/
- if (style == SCE_SCITECO_COMMENT)
+ if (style == SCE_SCITECO_COMMENT || style == SCE_SCITECO_OPERATOR2)
old_pc--;
teco_view_ssm(view, SCI_STARTSTYLING, start+old_pc, 0);
@@ -212,22 +219,35 @@ teco_lexer_style(teco_view_t *view, gsize end)
gsize start = teco_view_ssm(view, SCI_GETENDSTYLED, 0, 0);
guint start_line = teco_view_ssm(view, SCI_LINEFROMPOSITION, start, 0);
- gint start_col = 0;
/*
* The line state stores the laster character (column) in bytes,
* that starts from a fresh parser state.
* It's -1 if the line does not have a clean parser state.
- * Therefore we search for the first line before `start` that has a
- * known clean parser state.
+ * If the cached position on start_line does not fit our needs,
+ * we backtrack and search in previous lines
+ * for a known clean parser state.
+ *
+ * NOTE: It's crucial to consider the line state of the first possible
+ * line since we might be styling for a single-line command line view.
+ *
+ * FIXME: During rubout of regular commands we will frequently have the
+ * situation that the cached line state points after the last styled position
+ * forcing us to restyle the entire command line macro.
+ * If this turns out to be problematic, we might detect that
+ * view == teco_cmdline.view and inspect teco_cmdline.machine.
*/
- if (start_line > 0) {
+ gint start_col = teco_view_ssm(view, SCI_GETLINESTATE, start_line, 0);
+ if (start_col > start - teco_view_ssm(view, SCI_POSITIONFROMLINE, start_line, 0))
+ /* we are asked to style __before__ the last known start state */
+ start_col = -1;
+ if (start_col < 0 && start_line > 0) {
do
start_line--;
while ((start_col = teco_view_ssm(view, SCI_GETLINESTATE, start_line, 0)) < 0 &&
start_line > 0);
- start_col = MAX(start_col, 0);
}
+ start_col = MAX(start_col, 0);
start = teco_view_ssm(view, SCI_POSITIONFROMLINE, start_line, 0) + start_col;
g_assert(end > start);
diff --git a/src/lexer.h b/src/lexer.h
index 2b011be..e7073c2 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -25,12 +25,14 @@ typedef enum {
SCE_SCITECO_DEFAULT = 0,
SCE_SCITECO_COMMAND = 1,
SCE_SCITECO_OPERATOR = 2,
- SCE_SCITECO_QREG = 3,
- SCE_SCITECO_STRING = 4,
- SCE_SCITECO_NUMBER = 5,
- SCE_SCITECO_LABEL = 6,
- SCE_SCITECO_COMMENT = 7,
- SCE_SCITECO_INVALID = 8
+ /** two-character operators */
+ SCE_SCITECO_OPERATOR2 = 3,
+ SCE_SCITECO_QREG = 4,
+ SCE_SCITECO_STRING = 5,
+ SCE_SCITECO_NUMBER = 6,
+ SCE_SCITECO_LABEL = 7,
+ SCE_SCITECO_COMMENT = 8,
+ SCE_SCITECO_INVALID = 9
} teco_style_t;
void teco_lexer_style(teco_view_t *view, gsize end);
diff --git a/src/list.h b/src/list.h
index 2a7ab91..fc4b247 100644
--- a/src/list.h
+++ b/src/list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 b615e78..4d46817 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -640,6 +640,7 @@ cleanup:
teco_qreg_table_clear(&teco_qreg_table_globals);
teco_qreg_stack_clear();
teco_view_free(teco_qreg_view);
+ teco_cmdline_cleanup();
#endif
teco_interface_cleanup();
diff --git a/src/memory.c b/src/memory.c
index 264c235..d8de483 100644
--- a/src/memory.c
+++ b/src/memory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 ae7b506..9826073 100644
--- a/src/memory.h
+++ b/src/memory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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/move-commands.c b/src/move-commands.c
index cf5d6b0..45afc4e 100644
--- a/src/move-commands.c
+++ b/src/move-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -276,7 +276,7 @@ teco_find_words(gsize *pos, teco_int_t n, gboolean end_of_word)
/*
* FIXME: Is this safe or do we have to look up Unicode code points?
*/
- if ((!teco_string_contains(&wchars, *p)) == skip_word) {
+ if ((!teco_string_contains(wchars, *p)) == skip_word) {
if (skip_word == end_of_word)
break;
skip_word = !skip_word;
@@ -314,7 +314,7 @@ teco_find_words(gsize *pos, teco_int_t n, gboolean end_of_word)
/*
* FIXME: Is this safe or do we have to look up Unicode code points?
*/
- if ((!teco_string_contains(&wchars, p[-1])) == skip_word) {
+ if ((!teco_string_contains(wchars, p[-1])) == skip_word) {
if (skip_word != end_of_word)
break;
skip_word = !skip_word;
diff --git a/src/move-commands.h b/src/move-commands.h
index 1f32151..cc92961 100644
--- a/src/move-commands.h
+++ b/src/move-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 a5e6e4f..747249d 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -162,9 +162,7 @@ gboolean
teco_execute_macro(const gchar *macro, gsize macro_len,
teco_qreg_table_t *qreg_table_locals, GError **error)
{
- const teco_string_t str = {(gchar *)macro, macro_len};
-
- if (!teco_string_validate_utf8(&str)) {
+ if (!teco_string_validate_utf8((teco_string_t){(gchar *)macro, macro_len})) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_CODEPOINT,
"Invalid UTF-8 byte sequence in macro");
return FALSE;
@@ -476,27 +474,24 @@ teco_machine_stringbuilding_append_c(teco_machine_stringbuilding_t *ctx, teco_in
return TRUE;
}
-/*
- * FIXME: All teco_state_stringbuilding_* states could be static?
- */
static teco_state_t *teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx,
gunichar chr, GError **error);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctl);
+static teco_state_t teco_state_stringbuilding_ctl;
static teco_state_t *teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx,
gunichar chr, GError **error);
-TECO_DECLARE_STATE(teco_state_stringbuilding_escaped);
+static teco_state_t teco_state_stringbuilding_escaped;
-TECO_DECLARE_STATE(teco_state_stringbuilding_lower);
-TECO_DECLARE_STATE(teco_state_stringbuilding_upper);
+static teco_state_t teco_state_stringbuilding_lower;
+static teco_state_t teco_state_stringbuilding_upper;
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_num);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_u);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_code);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_q);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_quote);
-TECO_DECLARE_STATE(teco_state_stringbuilding_ctle_n);
+static teco_state_t teco_state_stringbuilding_ctle;
+static teco_state_t teco_state_stringbuilding_ctle_num;
+static teco_state_t teco_state_stringbuilding_ctle_u;
+static teco_state_t teco_state_stringbuilding_ctle_code;
+static teco_state_t teco_state_stringbuilding_ctle_q;
+static teco_state_t teco_state_stringbuilding_ctle_quote;
+static teco_state_t teco_state_stringbuilding_ctle_n;
static teco_state_t *
teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -524,14 +519,15 @@ 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,
- .insert_completion_cb = (teco_state_insert_completion_cb_t)
- teco_state_stringbuilding_insert_completion
+gboolean teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, teco_string_t str, GError **error);
+
+static TECO_DEFINE_STATE(teco_state_stringbuilding_start,
+ .is_start = TRUE,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_start_input,
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)
+ 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 *
@@ -581,7 +577,9 @@ teco_state_stringbuilding_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl);
+static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctl,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctl_input,
+);
static teco_state_t *
teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -613,7 +611,8 @@ teco_state_stringbuilding_escaped_input(teco_machine_stringbuilding_t *ctx, guni
gboolean teco_state_stringbuilding_escaped_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
-TECO_DEFINE_STATE(teco_state_stringbuilding_escaped,
+static TECO_DEFINE_STATE(teco_state_stringbuilding_escaped,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_escaped_input,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)
teco_state_stringbuilding_escaped_process_edit_cmd
);
@@ -639,7 +638,9 @@ teco_state_stringbuilding_lower_ctl_input(teco_machine_stringbuilding_t *ctx, gu
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_lower_ctl);
+static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_lower_ctl,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_lower_ctl_input
+);
static teco_state_t *
teco_state_stringbuilding_lower_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -657,7 +658,9 @@ teco_state_stringbuilding_lower_input(teco_machine_stringbuilding_t *ctx, gunich
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE(teco_state_stringbuilding_lower);
+static TECO_DEFINE_STATE(teco_state_stringbuilding_lower,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_lower_input
+);
static teco_state_t *
teco_state_stringbuilding_upper_ctl_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -680,7 +683,9 @@ teco_state_stringbuilding_upper_ctl_input(teco_machine_stringbuilding_t *ctx, gu
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_upper_ctl);
+static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_upper_ctl,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_upper_ctl_input
+);
static teco_state_t *
teco_state_stringbuilding_upper_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -698,7 +703,9 @@ teco_state_stringbuilding_upper_input(teco_machine_stringbuilding_t *ctx, gunich
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE(teco_state_stringbuilding_upper);
+static TECO_DEFINE_STATE(teco_state_stringbuilding_upper,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_upper_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -731,7 +738,9 @@ teco_state_stringbuilding_ctle_input(teco_machine_stringbuilding_t *ctx, gunicha
return next;
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctle);
+static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_stringbuilding_ctle,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_input
+);
/* in cmdline.c */
gboolean teco_state_stringbuilding_qreg_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
@@ -782,7 +791,9 @@ teco_state_stringbuilding_ctle_num_input(teco_machine_stringbuilding_t *ctx, gun
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_num);
+static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_num,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_num_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_u_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -817,7 +828,9 @@ teco_state_stringbuilding_ctle_u_input(teco_machine_stringbuilding_t *ctx, gunic
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_u);
+static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_u,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_u_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_code_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -869,7 +882,9 @@ teco_state_stringbuilding_ctle_code_input(teco_machine_stringbuilding_t *ctx, gu
return &teco_state_stringbuilding_ctle_code;
}
-TECO_DEFINE_STATE(teco_state_stringbuilding_ctle_code);
+static TECO_DEFINE_STATE(teco_state_stringbuilding_ctle_code,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_code_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_q_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -897,7 +912,9 @@ teco_state_stringbuilding_ctle_q_input(teco_machine_stringbuilding_t *ctx, gunic
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_q);
+static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_q,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_q_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -929,7 +946,7 @@ teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, g
* in command line arguments anyway.
* Otherwise, we'd have to implement our own POSIX shell escape function.
*/
- if (teco_string_contains(&str, '\0')) {
+ if (teco_string_contains(str, '\0')) {
teco_error_qregcontainsnull_set(error, qreg->head.name.data, qreg->head.name.len,
table != &teco_qreg_table_globals);
return NULL;
@@ -940,7 +957,9 @@ teco_state_stringbuilding_ctle_quote_input(teco_machine_stringbuilding_t *ctx, g
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_quote);
+static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_quote,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_quote_input
+);
static teco_state_t *
teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunichar chr, GError **error)
@@ -965,7 +984,7 @@ teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunic
g_auto(teco_string_t) str = {NULL, 0};
if (!qreg->vtable->get_string(qreg, &str.data, &str.len, NULL, error))
return NULL;
- if (teco_string_contains(&str, '\0')) {
+ if (teco_string_contains(str, '\0')) {
teco_error_qregcontainsnull_set(error, qreg->head.name.data, qreg->head.name.len,
table != &teco_qreg_table_globals);
return NULL;
@@ -977,7 +996,9 @@ teco_state_stringbuilding_ctle_n_input(teco_machine_stringbuilding_t *ctx, gunic
return &teco_state_stringbuilding_start;
}
-TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_n);
+static TECO_DEFINE_STATE_STRINGBUILDING_QREG(teco_state_stringbuilding_ctle_n,
+ .input_cb = (teco_state_input_cb_t)teco_state_stringbuilding_ctle_n_input
+);
void
teco_machine_stringbuilding_init(teco_machine_stringbuilding_t *ctx, gunichar escape_char,
@@ -1115,11 +1136,11 @@ teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar chr, GError **e
* so they may do their main activity in process_cb().
*/
if (ctx->expectstring.insert_len && current->expectstring.process_cb &&
- !current->expectstring.process_cb(ctx, &ctx->expectstring.string,
+ !current->expectstring.process_cb(ctx, ctx->expectstring.string,
ctx->expectstring.insert_len, error))
return NULL;
- teco_state_t *next = current->expectstring.done_cb(ctx, &ctx->expectstring.string, error);
+ teco_state_t *next = current->expectstring.done_cb(ctx, ctx->expectstring.string, error);
if (ctx->parent.must_undo)
teco_undo_string_own(ctx->expectstring.string);
@@ -1194,7 +1215,7 @@ teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error)
/* never calls process_cb() in parse-only mode */
if (ctx->expectstring.insert_len && current->expectstring.process_cb &&
- !current->expectstring.process_cb(ctx, &ctx->expectstring.string,
+ !current->expectstring.process_cb(ctx, ctx->expectstring.string,
ctx->expectstring.insert_len, error))
return FALSE;
@@ -1206,10 +1227,10 @@ teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error)
}
gboolean
-teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str,
+teco_state_expectfile_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error)
{
- g_assert(str->data != NULL);
+ g_assert(str.data != NULL);
/*
* Null-chars must not occur in filename/path strings and at some point
@@ -1218,7 +1239,7 @@ teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str
* Doing it here ensures that teco_file_expand_path() can be safely called
* from the done_cb().
*/
- if (memchr(str->data + str->len - new_chars, '\0', new_chars)) {
+ if (memchr(str.data + str.len - new_chars, '\0', new_chars)) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Null-character not allowed in filenames");
return FALSE;
diff --git a/src/parser.h b/src/parser.h
index 2308925..0c389cc 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -85,7 +85,7 @@ typedef const struct {
*
* Can be NULL if no interactive feedback is required.
*/
- gboolean (*process_cb)(teco_machine_main_t *ctx, const teco_string_t *str,
+ gboolean (*process_cb)(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error);
/**
@@ -93,7 +93,7 @@ typedef const struct {
* Commands that don't give interactive feedback can use this callback
* to perform their main processing.
*/
- teco_state_t *(*done_cb)(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
+ teco_state_t *(*done_cb)(teco_machine_main_t *ctx, teco_string_t str, GError **error);
} teco_state_expectstring_t;
typedef const struct {
@@ -110,7 +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 gboolean (*teco_state_insert_completion_cb_t)(teco_machine_t *ctx, teco_string_t str, GError **error);
typedef enum {
TECO_KEYMACRO_MASK_START = (1 << 0),
@@ -248,15 +248,18 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent
* @implements teco_state_t
* @ingroup states
*
- * @todo Should we eliminate required callbacks, this could be turned into a
- * struct initializer TECO_INIT_STATE() and TECO_DECLARE_STATE() would become pointless.
- * This would also ease declaring static states.
+ * Base class of all states.
+ *
+ * Since states are constant, you can append static assertions for required callbacks
+ * and other conditions.
+ * You should use TECO_ASSERT_SAFE(), but it won't be checked on all supported compilers.
+ * You should not put anything in front of the definition, though, so you
+ * can write `static TECO_DEFINE_STATE(...)`.
*/
#define TECO_DEFINE_STATE(NAME, ...) \
/** @ingroup states */ \
teco_state_t NAME = { \
.initial_cb = NULL, /* do nothing */ \
- .input_cb = (teco_state_input_cb_t)NAME##_input, /* always required */ \
.refresh_cb = NULL, /* do nothing */ \
.end_of_macro_cb = teco_state_end_of_macro, \
.process_edit_cmd_cb = teco_state_process_edit_cmd, \
@@ -265,11 +268,8 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent
.keymacro_mask = TECO_KEYMACRO_MASK_DEFAULT, \
.style = SCE_SCITECO_DEFAULT, \
##__VA_ARGS__ \
- }
-
-/** @ingroup states */
-#define TECO_DECLARE_STATE(NAME) \
- extern teco_state_t NAME
+ }; \
+ TECO_ASSERT_SAFE(NAME.input_cb != NULL)
/* in cmdline.c */
gboolean teco_state_caseinsensitive_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error);
@@ -599,7 +599,7 @@ gboolean teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **erro
/* 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_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, teco_string_t str,
GError **error);
/**
@@ -610,18 +610,11 @@ gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, con
* Super-class for states accepting string arguments
* Opaquely cares about alternative-escape characters,
* string building commands and accumulation into a string
- *
- * @note Generating the input_cb could be avoided if there were a default
- * implementation.
*/
#define TECO_DEFINE_STATE_EXPECTSTRING(NAME, ...) \
- static teco_state_t * \
- NAME##_input(teco_machine_main_t *ctx, gunichar chr, GError **error) \
- { \
- return teco_state_expectstring_input(ctx, chr, error); \
- } \
TECO_DEFINE_STATE(NAME, \
.initial_cb = (teco_state_initial_cb_t)teco_state_expectstring_initial, \
+ .input_cb = (teco_state_input_cb_t)teco_state_expectstring_input, \
.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, \
@@ -631,17 +624,17 @@ gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, con
.style = SCE_SCITECO_STRING, \
.expectstring.string_building = TRUE, \
.expectstring.last = TRUE, \
- .expectstring.process_cb = NULL, /* do nothing */ \
- .expectstring.done_cb = NAME##_done, /* always required */ \
+ .expectstring.process_cb = NULL, /* do nothing */ \
##__VA_ARGS__ \
- )
+ ); \
+ TECO_ASSERT_SAFE(NAME.expectstring.done_cb != NULL)
-gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error);
/* 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);
+gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTFILE
@@ -660,7 +653,7 @@ gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const
/* 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);
+gboolean teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, teco_string_t str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTDIR
diff --git a/src/qreg-commands.c b/src/qreg-commands.c
index 83a45b1..4ede403 100644
--- a/src/qreg-commands.c
+++ b/src/qreg-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -91,7 +91,9 @@ teco_state_pushqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* Save Q-Register <q> contents on the global Q-Register push-down
* stack.
*/
-TECO_DEFINE_STATE_EXPECTQREG(teco_state_pushqreg);
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_pushqreg,
+ .expectqreg.got_register_cb = teco_state_pushqreg_got_register
+);
static teco_state_t *
teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
@@ -128,7 +130,8 @@ teco_state_popqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* Memory is reclaimed on command-line termination.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_popqreg,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_popqreg_got_register
);
static teco_state_t *
@@ -142,11 +145,12 @@ teco_state_eqcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
}
TECO_DEFINE_STATE_EXPECTQREG(teco_state_eqcommand,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_eqcommand_got_register
);
static teco_state_t *
-teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_loadqreg_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
teco_qreg_t *qreg;
teco_qreg_table_t *table;
@@ -157,9 +161,9 @@ teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- if (str->len > 0) {
+ if (str.len > 0) {
/* Load file into Q-Register */
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
if (!qreg->vtable->load(qreg, filename, error))
return NULL;
} else {
@@ -187,7 +191,9 @@ teco_state_loadqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr
* Undefined Q-Registers will be defined.
* The command fails if <file> could not be read.
*/
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_loadqreg);
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_loadqreg,
+ .expectstring.done_cb = teco_state_loadqreg_done
+);
static teco_state_t *
teco_state_epctcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
@@ -199,10 +205,12 @@ teco_state_epctcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
return &teco_state_saveqreg;
}
-TECO_DEFINE_STATE_EXPECTQREG(teco_state_epctcommand);
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_epctcommand,
+ .expectqreg.got_register_cb = teco_state_epctcommand_got_register
+);
static teco_state_t *
-teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_saveqreg_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
teco_qreg_t *qreg;
@@ -212,7 +220,7 @@ teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
return qreg->vtable->save(qreg, filename, error) ? &teco_state_start : NULL;
}
@@ -231,7 +239,9 @@ teco_state_saveqreg_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr
* File names may also be tab-completed and string building
* characters are enabled by default.
*/
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_saveqreg);
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_saveqreg,
+ .expectstring.done_cb = teco_state_saveqreg_done
+);
static gboolean
teco_state_queryqreg_initial(teco_machine_main_t *ctx, GError **error)
@@ -346,7 +356,8 @@ teco_state_queryqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* boolean.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_queryqreg,
- .initial_cb = (teco_state_initial_cb_t)teco_state_queryqreg_initial
+ .initial_cb = (teco_state_initial_cb_t)teco_state_queryqreg_initial,
+ .expectqreg.got_register_cb = teco_state_queryqreg_got_register
);
static teco_state_t *
@@ -360,12 +371,13 @@ teco_state_ctlucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
}
TECO_DEFINE_STATE_EXPECTQREG(teco_state_ctlucommand,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_ctlucommand_got_register
);
static teco_state_t *
teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx,
- const teco_string_t *str, GError **error)
+ teco_string_t str, GError **error)
{
teco_qreg_t *qreg;
@@ -434,12 +446,12 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx,
if (args > 0 || colon_modified) {
/* append to register */
- if (!qreg->vtable->append_string(qreg, str->data, str->len, error))
+ if (!qreg->vtable->append_string(qreg, str.data, str.len, error))
return NULL;
} else {
/* set register */
if (!qreg->vtable->undo_set_string(qreg, error) ||
- !qreg->vtable->set_string(qreg, str->data, str->len,
+ !qreg->vtable->set_string(qreg, str.data, str.len,
teco_default_codepage(), error))
return NULL;
}
@@ -474,7 +486,8 @@ teco_state_setqregstring_nobuilding_done(teco_machine_main_t *ctx,
* is desired.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_nobuilding,
- .expectstring.string_building = FALSE
+ .expectstring.string_building = FALSE,
+ .expectstring.done_cb = teco_state_setqregstring_nobuilding_done
);
static teco_state_t *
@@ -488,7 +501,8 @@ teco_state_eucommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
}
TECO_DEFINE_STATE_EXPECTQREG(teco_state_eucommand,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_eucommand_got_register
);
static gboolean
@@ -511,12 +525,6 @@ teco_state_setqregstring_building_initial(teco_machine_main_t *ctx, GError **err
return TRUE;
}
-static teco_state_t *
-teco_state_setqregstring_building_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
-{
- return teco_state_setqregstring_nobuilding_done(ctx, str, error);
-}
-
/*$ "EU" "EUq" ":EUq"
* [c1,c2,...]EUq[string]$ -- Set or append to Q-Register string with string building characters
* [c1,c2,...]:EUq[string]$
@@ -529,7 +537,8 @@ teco_state_setqregstring_building_done(teco_machine_main_t *ctx, const teco_stri
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_setqregstring_building,
.initial_cb = (teco_state_initial_cb_t)teco_state_setqregstring_building_initial,
- .expectstring.string_building = TRUE
+ .expectstring.string_building = TRUE,
+ .expectstring.done_cb = teco_state_setqregstring_nobuilding_done
);
static teco_state_t *
@@ -582,7 +591,9 @@ teco_state_getqregstring_got_register(teco_machine_main_t *ctx, teco_qreg_t *qre
*
* Specifying an undefined <q> yields an error.
*/
-TECO_DEFINE_STATE_EXPECTQREG(teco_state_getqregstring);
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_getqregstring,
+ .expectqreg.got_register_cb = teco_state_getqregstring_got_register
+);
static teco_state_t *
teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
@@ -631,7 +642,8 @@ teco_state_setqreginteger_got_register(teco_machine_main_t *ctx, teco_qreg_t *qr
* The register is defined if it does not exist.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_setqreginteger,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_setqreginteger_got_register
);
static teco_state_t *
@@ -665,7 +677,8 @@ teco_state_increaseqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg
* <q> will be defined if it does not exist.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_increaseqreg,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_increaseqreg_got_register
);
static teco_state_t *
@@ -724,15 +737,17 @@ teco_state_macro_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* (as reported by \fBEE\fP), its contents must be and are checked to be in
* valid UTF-8.
*/
-TECO_DEFINE_STATE_EXPECTQREG(teco_state_macro);
+TECO_DEFINE_STATE_EXPECTQREG(teco_state_macro,
+ .expectqreg.got_register_cb = teco_state_macro_got_register
+);
static teco_state_t *
-teco_state_indirect_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_indirect_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
if (teco_machine_main_eval_colon(ctx) > 0) {
/* don't create new local Q-Registers if colon modifier is given */
@@ -762,7 +777,9 @@ teco_state_indirect_done(teco_machine_main_t *ctx, const teco_string_t *str, GEr
* As all \*(ST code, the contents of <file> must be in valid UTF-8
* even if operating in the \(lqdefault ANSI\(rq mode as configured by \fBED\fP.
*/
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_indirect);
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_indirect,
+ .expectstring.done_cb = teco_state_indirect_done
+);
static teco_state_t *
teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
@@ -865,5 +882,6 @@ teco_state_copytoqreg_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* Register <q> will be created if it is undefined.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_copytoqreg,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_copytoqreg_got_register
);
diff --git a/src/qreg-commands.h b/src/qreg-commands.h
index e224797..51f792b 100644
--- a/src/qreg-commands.h
+++ b/src/qreg-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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,7 +40,7 @@ teco_state_t *teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr
/* 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_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, teco_string_t str,
GError **error);
/**
@@ -51,48 +51,44 @@ gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const
* Super class for states accepting Q-Register specifications.
*/
#define TECO_DEFINE_STATE_EXPECTQREG(NAME, ...) \
- static teco_state_t * \
- NAME##_input(teco_machine_main_t *ctx, gunichar chr, GError **error) \
- { \
- return teco_state_expectqreg_input(ctx, chr, error); \
- } \
TECO_DEFINE_STATE(NAME, \
.initial_cb = (teco_state_initial_cb_t)teco_state_expectqreg_initial, \
+ .input_cb = (teco_state_input_cb_t)teco_state_expectqreg_input, \
.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 */ \
##__VA_ARGS__ \
- )
+ ); \
+ TECO_ASSERT_SAFE(NAME.expectqreg.got_register_cb != NULL)
/*
* FIXME: Some of these states are referenced only in qreg-commands.c,
* so they should be moved there?
*/
-TECO_DECLARE_STATE(teco_state_pushqreg);
-TECO_DECLARE_STATE(teco_state_popqreg);
+extern teco_state_t teco_state_pushqreg;
+extern teco_state_t teco_state_popqreg;
-TECO_DECLARE_STATE(teco_state_eqcommand);
-TECO_DECLARE_STATE(teco_state_loadqreg);
+extern teco_state_t teco_state_eqcommand;
+extern teco_state_t teco_state_loadqreg;
-TECO_DECLARE_STATE(teco_state_epctcommand);
-TECO_DECLARE_STATE(teco_state_saveqreg);
+extern teco_state_t teco_state_epctcommand;
+extern teco_state_t teco_state_saveqreg;
-TECO_DECLARE_STATE(teco_state_queryqreg);
+extern teco_state_t teco_state_queryqreg;
-TECO_DECLARE_STATE(teco_state_ctlucommand);
-TECO_DECLARE_STATE(teco_state_setqregstring_nobuilding);
-TECO_DECLARE_STATE(teco_state_eucommand);
-TECO_DECLARE_STATE(teco_state_setqregstring_building);
+extern teco_state_t teco_state_ctlucommand;
+extern teco_state_t teco_state_setqregstring_nobuilding;
+extern teco_state_t teco_state_eucommand;
+extern teco_state_t teco_state_setqregstring_building;
-TECO_DECLARE_STATE(teco_state_getqregstring);
-TECO_DECLARE_STATE(teco_state_setqreginteger);
-TECO_DECLARE_STATE(teco_state_increaseqreg);
+extern teco_state_t teco_state_getqregstring;
+extern teco_state_t teco_state_setqreginteger;
+extern teco_state_t teco_state_increaseqreg;
-TECO_DECLARE_STATE(teco_state_macro);
-TECO_DECLARE_STATE(teco_state_indirect);
+extern teco_state_t teco_state_macro;
+extern teco_state_t teco_state_indirect;
-TECO_DECLARE_STATE(teco_state_copytoqreg);
+extern teco_state_t teco_state_copytoqreg;
diff --git a/src/qreg.c b/src/qreg.c
index 8fca9e2..4cf92f0 100644
--- a/src/qreg.c
+++ b/src/qreg.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -732,7 +732,7 @@ teco_qreg_workingdir_set_string(teco_qreg_t *qreg, const gchar *str, gsize len,
g_auto(teco_string_t) dir;
teco_string_init(&dir, str, len);
- if (teco_string_contains(&dir, '\0')) {
+ if (teco_string_contains(dir, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
"Directory contains null-character");
return FALSE;
@@ -1107,7 +1107,7 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error)
for (teco_qreg_t *cur = first;
cur && cur->head.name.data[0] == '$';
cur = (teco_qreg_t *)teco_rb3str_get_next(&cur->head)) {
- const teco_string_t *name = &cur->head.name;
+ const teco_string_t name = cur->head.name;
/*
* Ignore the "$" register (not an environment
@@ -1115,7 +1115,7 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error)
* name contains "=" or null (not allowed in environment
* variable names).
*/
- if (name->len == 1 ||
+ if (name.len == 1 ||
teco_string_contains(name, '=') || teco_string_contains(name, '\0'))
continue;
@@ -1124,16 +1124,16 @@ teco_qreg_table_get_environ(teco_qreg_table_t *table, GError **error)
g_strfreev(envp);
return NULL;
}
- if (teco_string_contains(&value, '\0')) {
+ if (teco_string_contains(value, '\0')) {
g_strfreev(envp);
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Environment register \"%s\" must not contain null characters",
- name->data);
+ name.data);
return NULL;
}
/* more efficient than g_environ_setenv() */
- *p++ = g_strconcat(name->data+1, "=", value.data, NULL);
+ *p++ = g_strconcat(name.data+1, "=", value.data, NULL);
}
*p = NULL;
@@ -1377,15 +1377,12 @@ TECO_DEFINE_UNDO_SCALAR(teco_machine_qregspec_flags_t);
#define teco_undo_qregspec_flags(VAR) \
(*teco_undo_object_teco_machine_qregspec_flags_t_push(&(VAR)))
-/*
- * FIXME: All teco_state_qregspec_* states could be static?
- */
-TECO_DECLARE_STATE(teco_state_qregspec_start);
-TECO_DECLARE_STATE(teco_state_qregspec_start_global);
-TECO_DECLARE_STATE(teco_state_qregspec_caret);
-TECO_DECLARE_STATE(teco_state_qregspec_firstchar);
-TECO_DECLARE_STATE(teco_state_qregspec_secondchar);
-TECO_DECLARE_STATE(teco_state_qregspec_string);
+static teco_state_t teco_state_qregspec_start;
+static teco_state_t teco_state_qregspec_start_global;
+static teco_state_t teco_state_qregspec_caret;
+static teco_state_t teco_state_qregspec_firstchar;
+static teco_state_t teco_state_qregspec_secondchar;
+static teco_state_t teco_state_qregspec_string;
static teco_state_t *teco_state_qregspec_start_global_input(teco_machine_qregspec_t *ctx,
gunichar chr, GError **error);
@@ -1442,11 +1439,12 @@ 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_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str,
+gboolean teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str,
GError **error);
-TECO_DEFINE_STATE(teco_state_qregspec_start,
+static TECO_DEFINE_STATE(teco_state_qregspec_start,
.is_start = TRUE,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_start_input,
.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
);
@@ -1482,7 +1480,8 @@ teco_state_qregspec_start_global_input(teco_machine_qregspec_t *ctx, gunichar ch
* Alternatively, we'd have to introduce a teco_machine_qregspec_t::status attribute.
* Or even better, why not use special pointers like ((teco_state_t *)"teco_state_qregspec_done")?
*/
-TECO_DEFINE_STATE(teco_state_qregspec_start_global,
+static TECO_DEFINE_STATE(teco_state_qregspec_start_global,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_start_global_input,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd
);
@@ -1503,7 +1502,9 @@ teco_state_qregspec_caret_input(teco_machine_qregspec_t *ctx, gunichar chr, GErr
return teco_state_qregspec_done(ctx, error);
}
-TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_qregspec_caret);
+static TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_qregspec_caret,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_caret_input
+);
static teco_state_t *
teco_state_qregspec_firstchar_input(teco_machine_qregspec_t *ctx, gunichar chr, GError **error)
@@ -1519,7 +1520,8 @@ teco_state_qregspec_firstchar_input(teco_machine_qregspec_t *ctx, gunichar chr,
return &teco_state_qregspec_secondchar;
}
-TECO_DEFINE_STATE(teco_state_qregspec_firstchar,
+static TECO_DEFINE_STATE(teco_state_qregspec_firstchar,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_firstchar_input,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd
);
@@ -1537,7 +1539,8 @@ teco_state_qregspec_secondchar_input(teco_machine_qregspec_t *ctx, gunichar chr,
return teco_state_qregspec_done(ctx, error);
}
-TECO_DEFINE_STATE(teco_state_qregspec_secondchar,
+static TECO_DEFINE_STATE(teco_state_qregspec_secondchar,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_secondchar_input,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd
);
@@ -1584,10 +1587,11 @@ 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,
+gboolean teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, teco_string_t str,
GError **error);
-TECO_DEFINE_STATE(teco_state_qregspec_string,
+static TECO_DEFINE_STATE(teco_state_qregspec_string,
+ .input_cb = (teco_state_input_cb_t)teco_state_qregspec_string_input,
.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
);
diff --git a/src/qreg.h b/src/qreg.h
index 6daaff3..c5e9461 100644
--- a/src/qreg.h
+++ b/src/qreg.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 276b624..f4c16fa 100644
--- a/src/rb3str.c
+++ b/src/rb3str.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -34,13 +34,13 @@
static gint
teco_rb3str_cmp(const teco_rb3str_head_t *head, const teco_string_t *data)
{
- return teco_string_cmp(&head->key, data->data, data->len);
+ return teco_string_cmp(head->key, data->data, data->len);
}
static gint
teco_rb3str_casecmp(const teco_rb3str_head_t *head, const teco_string_t *data)
{
- return teco_string_casecmp(&head->key, data->data, data->len);
+ return teco_string_casecmp(head->key, data->data, data->len);
}
/** @memberof teco_rb3str_tree_t */
@@ -113,7 +113,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
guint prefixed_entries = 0;
for (teco_rb3str_head_t *cur = teco_rb3str_nfind(tree, case_sensitive, str, str_len);
- cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len;
+ cur && cur->key.len >= str_len && diff(cur->key, str, str_len) == str_len;
cur = teco_rb3str_get_next(cur)) {
if (restrict_len && g_utf8_strlen(cur->key.data, cur->key.len) != restrict_len)
continue;
@@ -122,7 +122,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
first = cur;
prefix_len = cur->key.len - str_len;
} else {
- gsize len = diff(&cur->key, first->key.data, first->key.len) - str_len;
+ gsize len = diff(cur->key, first->key.data, first->key.len) - str_len;
if (len < prefix_len)
prefix_len = len;
}
@@ -134,7 +134,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
teco_string_init(insert, first->key.data + str_len, prefix_len);
} else if (prefixed_entries > 1) {
for (teco_rb3str_head_t *cur = first;
- cur && cur->key.len >= str_len && diff(&cur->key, str, str_len) == str_len;
+ cur && cur->key.len >= str_len && diff(cur->key, str, str_len) == str_len;
cur = teco_rb3str_get_next(cur)) {
if (restrict_len && g_utf8_strlen(cur->key.data, cur->key.len) != restrict_len)
continue;
diff --git a/src/rb3str.h b/src/rb3str.h
index 466cf90..00d1791 100644
--- a/src/rb3str.h
+++ b/src/rb3str.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 0dbe911..2445718 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -21,6 +21,7 @@
#include <glib.h>
#include <glib/gprintf.h>
+#include <glib/gstdio.h>
#include <Scintilla.h>
@@ -75,16 +76,23 @@ teco_buffer_undo_edit(teco_buffer_t *ctx)
}
/** @private @memberof teco_buffer_t */
+static inline gchar *
+teco_buffer_get_recovery(teco_buffer_t *ctx)
+{
+ g_autofree gchar *dirname = g_path_get_dirname(ctx->filename);
+ g_autofree gchar *basename = g_path_get_basename(ctx->filename);
+ return g_strconcat(dirname, G_DIR_SEPARATOR_S, "#", basename, "#", NULL);
+}
+
+/** @private @memberof teco_buffer_t */
static gboolean
teco_buffer_load(teco_buffer_t *ctx, const gchar *filename, GError **error)
{
if (!teco_view_load(ctx->view, filename, TRUE, error))
return FALSE;
-#if 0 /* NOTE: currently buffer cannot be dirty */
- undo__teco_interface_info_update_buffer(ctx);
- teco_undo_gboolean(ctx->dirty) = FALSE;
-#endif
+ /* currently buffer cannot be dirty */
+ g_assert(ctx->state == TECO_BUFFER_CLEAN);
teco_buffer_set_filename(ctx, filename);
return TRUE;
@@ -110,7 +118,13 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error)
*/
if (ctx == teco_ring_current && !teco_qreg_current)
undo__teco_interface_info_update_buffer(ctx);
- teco_undo_gboolean(ctx->dirty) = FALSE;
+ if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) {
+ g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx);
+ g_unlink(filename_recovery);
+ /* on rubout, we do not restore the recovery file */
+ ctx->state = TECO_BUFFER_DIRTY_NO_DUMP;
+ }
+ teco_undo_guint(ctx->state) = TECO_BUFFER_CLEAN;
/*
* FIXME: necessary also if the filename was not specified but the file
@@ -129,6 +143,11 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error)
static inline void
teco_buffer_free(teco_buffer_t *ctx)
{
+ if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) {
+ g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx);
+ g_unlink(filename_recovery);
+ }
+
teco_view_free(ctx->view);
g_free(ctx->filename);
g_free(ctx);
@@ -222,15 +241,41 @@ teco_ring_find_by_id(teco_int_t id)
return NULL;
}
+static void
+teco_ring_undirtify(void)
+{
+ if (teco_ring_current->state > TECO_BUFFER_DIRTY_NO_DUMP) {
+ g_autofree gchar *filename_recovery = teco_buffer_get_recovery(teco_ring_current);
+ g_unlink(filename_recovery);
+ }
+
+ teco_ring_current->state = TECO_BUFFER_CLEAN;
+ teco_interface_info_update(teco_ring_current);
+}
+
+TECO_DEFINE_UNDO_CALL(teco_ring_undirtify);
+
void
teco_ring_dirtify(void)
{
- if (teco_qreg_current || teco_ring_current->dirty)
+ if (teco_qreg_current)
return;
- undo__teco_interface_info_update_buffer(teco_ring_current);
- teco_undo_gboolean(teco_ring_current->dirty) = TRUE;
- teco_interface_info_update(teco_ring_current);
+ switch ((teco_buffer_state_t)teco_ring_current->state) {
+ case TECO_BUFFER_CLEAN:
+ teco_ring_current->state = TECO_BUFFER_DIRTY_NO_DUMP;
+ teco_interface_info_update(teco_ring_current);
+ undo__teco_ring_undirtify();
+ break;
+ case TECO_BUFFER_DIRTY_NO_DUMP:
+ case TECO_BUFFER_DIRTY_OUTDATED_DUMP:
+ break;
+ case TECO_BUFFER_DIRTY_RECENT_DUMP:
+ teco_ring_current->state = TECO_BUFFER_DIRTY_OUTDATED_DUMP;
+ /* set to TECO_BUFFER_DIRTY_OUTDATED_DUMP on rubout */
+ teco_undo_guint(teco_ring_current->state);
+ break;
+ }
}
/** Get id of first dirty buffer, or otherwise 0 */
@@ -241,7 +286,7 @@ teco_ring_get_first_dirty(void)
for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) {
teco_buffer_t *buffer = (teco_buffer_t *)cur;
- if (buffer->dirty)
+ if (buffer->state > TECO_BUFFER_CLEAN)
return id;
id++;
}
@@ -255,13 +300,72 @@ teco_ring_save_all_dirty_buffers(GError **error)
for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) {
teco_buffer_t *buffer = (teco_buffer_t *)cur;
/* NOTE: Will fail for a dirty unnamed file */
- if (buffer->dirty && !teco_buffer_save(buffer, NULL, error))
+ if (buffer->state > TECO_BUFFER_CLEAN &&
+ !teco_buffer_save(buffer, NULL, error))
return FALSE;
}
return TRUE;
}
+/**
+ * Recovery creation interval in seconds or 0 if disabled.
+ * It's not currently enforced in batch mode.
+ */
+guint teco_ring_recovery_interval = 5*60;
+
+/**
+ * Create recovery files for all dirty buffers.
+ *
+ * Should be called by the interface every teco_ring_recovery_interval seconds.
+ * This does not generate or expect undo tokens, so it can be called
+ * even when idlying.
+ */
+void
+teco_ring_dump_recovery(void)
+{
+ g_assert(teco_ring_recovery_interval > 0);
+
+ for (teco_tailq_entry_t *cur = teco_ring_head.first; cur != NULL; cur = cur->next) {
+ teco_buffer_t *buffer = (teco_buffer_t *)cur;
+ /* already dumped buffers don't have to be written again */
+ if (buffer->state != TECO_BUFFER_DIRTY_NO_DUMP &&
+ buffer->state != TECO_BUFFER_DIRTY_OUTDATED_DUMP)
+ continue;
+
+ /*
+ * Dirty unnamed buffers cannot be backed up.
+ * FIXME: Perhaps they should be dumped under ~/#UNNAMED#?
+ */
+ if (!buffer->filename)
+ continue;
+
+ g_autofree gchar *filename_recovery = teco_buffer_get_recovery(buffer);
+
+ g_autoptr(GIOChannel) channel = g_io_channel_new_file(filename_recovery, "w", NULL);
+ if (!channel)
+ continue;
+
+ /*
+ * teco_view_save_to_channel() expects a buffered and blocking channel.
+ */
+ g_io_channel_set_encoding(channel, NULL, NULL);
+ g_io_channel_set_buffered(channel, TRUE);
+
+ /*
+ * This does not use teco_view_save_to_file() since we must not
+ * emit undo tokens.
+ *
+ * FIXME: Errors are silently ignored.
+ * Should we log warnings instead?
+ */
+ if (!teco_view_save_to_channel(buffer->view, channel, NULL))
+ continue;
+
+ buffer->state = TECO_BUFFER_DIRTY_RECENT_DUMP;
+ }
+}
+
gboolean
teco_ring_edit_by_name(const gchar *filename, GError **error)
{
@@ -418,11 +522,10 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error)
ctx->flags.allow_filename = TRUE;
if (id == 0) {
- for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur)) {
- const gchar *filename = cur->filename ? : "(Unnamed)";
- teco_interface_popup_add(TECO_POPUP_FILE, filename,
- strlen(filename), cur == teco_ring_current);
- }
+ for (teco_buffer_t *cur = teco_ring_first(); cur; cur = teco_buffer_next(cur))
+ teco_interface_popup_add(TECO_POPUP_FILE, cur->filename,
+ cur->filename ? strlen(cur->filename) : 0,
+ cur == teco_ring_current);
teco_interface_popup_show(0);
} else if (id > 0) {
@@ -436,7 +539,7 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error)
}
gboolean
-teco_state_edit_file_process(teco_machine_main_t *ctx, const teco_string_t *str,
+teco_state_edit_file_process(teco_machine_main_t *ctx, teco_string_t str,
gsize new_chars, GError **error)
{
g_assert(new_chars > 0);
@@ -452,18 +555,18 @@ teco_state_edit_file_process(teco_machine_main_t *ctx, const teco_string_t *str,
}
static teco_state_t *
-teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_edit_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
if (!ctx->flags.allow_filename) {
- /* process_cb() already throws error if str->len > 0 */
- g_assert(str->len == 0);
+ /* process_cb() already throws error if str.len > 0 */
+ g_assert(str.len == 0);
return &teco_state_start;
}
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
if (teco_globber_is_pattern(filename)) {
g_auto(teco_globber_t) globber;
teco_globber_init(&globber, filename, G_FILE_TEST_IS_REGULAR);
@@ -541,11 +644,12 @@ teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
*/
TECO_DEFINE_STATE_EXPECTGLOB(teco_state_edit_file,
.initial_cb = (teco_state_initial_cb_t)teco_state_edit_file_initial,
- .expectstring.process_cb = teco_state_edit_file_process
+ .expectstring.process_cb = teco_state_edit_file_process,
+ .expectstring.done_cb = teco_state_edit_file_done
);
static teco_state_t *
-teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_save_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -553,7 +657,7 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
if (!teco_expressions_eval(FALSE, error))
return NULL;
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
/*
* This is like implying teco_ring_get_id(teco_ring_current)
@@ -616,17 +720,19 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* File names may also be tab-completed and string building
* characters are enabled by default.
*/
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file);
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file,
+ .expectstring.done_cb = teco_state_save_file_done
+);
static teco_state_t *
-teco_state_read_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_read_file_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
- g_autofree gchar *filename = teco_file_expand_path(str->data);
+ g_autofree gchar *filename = teco_file_expand_path(str.data);
/* FIXME: Add wrapper to interface.h? */
if (!teco_view_load(teco_interface_current_view, filename, FALSE, error))
return NULL;
@@ -650,7 +756,9 @@ teco_state_read_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
/*
* NOTE: Video TECO allows glob patterns as an argument.
*/
-TECO_DEFINE_STATE_EXPECTFILE(teco_state_read_file);
+TECO_DEFINE_STATE_EXPECTFILE(teco_state_read_file,
+ .expectstring.done_cb = teco_state_read_file_done
+);
/*$ "EF" ":EF" close
* [n]EF -- Remove buffer from ring
@@ -719,7 +827,7 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
if (teco_machine_main_eval_colon(ctx) > 0) {
if (!teco_buffer_save(buffer, NULL, error))
return;
- } else if (!force && buffer->dirty) {
+ } else if (!force && buffer->state > TECO_BUFFER_CLEAN) {
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Buffer \"%s\" is dirty",
buffer->filename ? : "(Unnamed)");
diff --git a/src/ring.h b/src/ring.h
index 6466a69..e45434d 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -25,13 +25,29 @@
#include "parser.h"
#include "list.h"
+typedef enum {
+ /** buffer is freshly opened or saved */
+ TECO_BUFFER_CLEAN = 0,
+ /** buffer modified, but a recovery file does not yet exist */
+ TECO_BUFFER_DIRTY_NO_DUMP,
+ /** buffer modified, recovery file outdated */
+ TECO_BUFFER_DIRTY_OUTDATED_DUMP,
+ /** buffer modified and recovery file is up to date */
+ TECO_BUFFER_DIRTY_RECENT_DUMP
+} teco_buffer_state_t;
+
typedef struct teco_buffer_t {
teco_tailq_entry_t entry;
teco_view_t *view;
gchar *filename;
- gboolean dirty;
+
+ /**
+ * A teco_buffer_state_t.
+ * This is still a guint, so you can call teco_undo_guint().
+ */
+ guint state;
} teco_buffer_t;
/** @memberof teco_buffer_t */
@@ -70,6 +86,10 @@ void teco_ring_dirtify(void);
guint teco_ring_get_first_dirty(void);
gboolean teco_ring_save_all_dirty_buffers(GError **error);
+extern guint teco_ring_recovery_interval;
+
+void teco_ring_dump_recovery(void);
+
gboolean teco_ring_edit_by_name(const gchar *filename, GError **error);
gboolean teco_ring_edit_by_id(teco_int_t id, GError **error);
@@ -97,9 +117,9 @@ void teco_ring_cleanup(void);
* Command states
*/
-TECO_DECLARE_STATE(teco_state_edit_file);
-TECO_DECLARE_STATE(teco_state_save_file);
-TECO_DECLARE_STATE(teco_state_read_file);
+extern teco_state_t teco_state_edit_file;
+extern teco_state_t teco_state_save_file;
+extern teco_state_t teco_state_read_file;
void teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error);
diff --git a/src/sciteco.h b/src/sciteco.h
index cc43368..16dba69 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -96,7 +96,8 @@ enum {
TECO_ED_SHELLEMU = (1 << 7),
TECO_ED_OSC52 = (1 << 8),
TECO_ED_ICONS = (1 << 9),
- TECO_ED_CLIP_PRIMARY = (1 << 10)
+ TECO_ED_CLIP_PRIMARY = (1 << 10),
+ TECO_ED_MINIBUF_SSM = (1 << 11)
};
/* in main.c */
@@ -116,6 +117,16 @@ extern volatile sig_atomic_t teco_interrupted;
*/
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FILE, fclose);
+/**
+ * A "safe" compile-time assertion, which also passes if the expression is not constant.
+ *
+ * Can be useful since different compilers have different ideas about what's a constant expression.
+ * In particular GCC does not treat `static const` objects as constant (in the way it qualifies
+ * for _Static_assert()), while constexpr is only available since C23.
+ */
+#define TECO_ASSERT_SAFE(EXPR) \
+ G_STATIC_ASSERT(!__builtin_constant_p(EXPR) || (EXPR))
+
/*
* BEWARE DRAGONS!
*/
diff --git a/src/search.c b/src/search.c
index 5ef2179..856d079 100644
--- a/src/search.c
+++ b/src/search.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -537,7 +537,7 @@ teco_pattern2regexp(teco_string_t *pattern, teco_machine_qregspec_t *qreg_machin
if (state == TECO_SEARCH_STATE_ALT)
teco_string_append_c(&re, ')');
- g_assert(!teco_string_contains(&re, '\0'));
+ g_assert(!teco_string_contains(re, '\0'));
return g_steal_pointer(&re.data) ? : g_strdup("");
}
@@ -696,7 +696,7 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error)
}
static gboolean
-teco_state_search_process(teco_machine_main_t *ctx, const teco_string_t *str, gsize new_chars, GError **error)
+teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new_chars, GError **error)
{
/* FIXME: Should G_REGEX_OPTIMIZE be added under certain circumstances? */
GRegexCompileFlags flags = G_REGEX_MULTILINE | G_REGEX_DOTALL;
@@ -741,10 +741,9 @@ teco_state_search_process(teco_machine_main_t *ctx, const teco_string_t *str, gs
qreg_machine = teco_machine_qregspec_new(TECO_QREG_REQUIRED, ctx->qreg_table_locals, FALSE);
g_autoptr(GRegex) re = NULL;
- teco_string_t pattern = *str;
g_autofree gchar *re_pattern;
/* NOTE: teco_pattern2regexp() modifies str pointer */
- re_pattern = teco_pattern2regexp(&pattern, qreg_machine,
+ re_pattern = teco_pattern2regexp(&str, qreg_machine,
ctx->expectstring.machine.codepage, FALSE, error);
if (!re_pattern)
return FALSE;
@@ -829,7 +828,7 @@ failure:
}
static teco_state_t *
-teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_search_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -837,14 +836,14 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
teco_qreg_t *search_reg = teco_qreg_table_find(&teco_qreg_table_globals, "_", 1);
g_assert(search_reg != NULL);
- if (str->len > 0) {
+ if (str.len > 0) {
/* workaround: preserve selection (also on rubout) */
gint anchor = teco_interface_ssm(SCI_GETANCHOR, 0, 0);
if (teco_current_doc_must_undo())
undo__teco_interface_ssm(SCI_SETANCHOR, anchor, 0);
if (!search_reg->vtable->undo_set_string(search_reg, error) ||
- !search_reg->vtable->set_string(search_reg, str->data, str->len,
+ !search_reg->vtable->set_string(search_reg, str.data, str.len,
teco_default_codepage(), error))
return NULL;
@@ -853,7 +852,7 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
g_auto(teco_string_t) search_str = {NULL, 0};
if (!search_reg->vtable->get_string(search_reg, &search_str.data, &search_str.len,
NULL, error) ||
- !teco_state_search_process(ctx, &search_str, search_str.len, error))
+ !teco_state_search_process(ctx, search_str, search_str.len, error))
return NULL;
}
@@ -882,7 +881,6 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
TECO_DEFINE_STATE_EXPECTSTRING(NAME, \
.initial_cb = (teco_state_initial_cb_t)teco_state_search_initial, \
.expectstring.process_cb = teco_state_search_process, \
- .expectstring.done_cb = NAME##_done, \
##__VA_ARGS__ \
)
@@ -956,7 +954,9 @@ teco_state_search_done(teco_machine_main_t *ctx, const teco_string_t *str, GErro
* Changing the <pattern> results in the search being reperformed
* from the beginning.
*/
-TECO_DEFINE_STATE_SEARCH(teco_state_search);
+TECO_DEFINE_STATE_SEARCH(teco_state_search,
+ .expectstring.done_cb = teco_state_search_done
+);
static gboolean
teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error)
@@ -1015,7 +1015,7 @@ teco_state_search_all_initial(teco_machine_main_t *ctx, GError **error)
}
static teco_state_t *
-teco_state_search_all_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_search_all_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -1074,11 +1074,12 @@ teco_state_search_all_done(teco_machine_main_t *ctx, const teco_string_t *str, G
* This is probably not very useful in practice, so it's not documented.
*/
TECO_DEFINE_STATE_SEARCH(teco_state_search_all,
- .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial
+ .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial,
+ .expectstring.done_cb = teco_state_search_all_done
);
static teco_state_t *
-teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_search_kill_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -1157,10 +1158,12 @@ teco_state_search_kill_done(teco_machine_main_t *ctx, const teco_string_t *str,
/*
* ::FK is possible but doesn't make much sense, so it's undocumented.
*/
-TECO_DEFINE_STATE_SEARCH(teco_state_search_kill);
+TECO_DEFINE_STATE_SEARCH(teco_state_search_kill,
+ .expectstring.done_cb = teco_state_search_kill_done
+);
static teco_state_t *
-teco_state_search_delete_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_search_delete_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -1200,37 +1203,50 @@ teco_state_search_delete_done(teco_machine_main_t *ctx, const teco_string_t *str
* Searches for <pattern> just like the regular search command
* (\fBS\fP) but when found deletes the entire occurrence.
*/
-TECO_DEFINE_STATE_SEARCH(teco_state_search_delete);
+TECO_DEFINE_STATE_SEARCH(teco_state_search_delete,
+ .expectstring.done_cb = teco_state_search_delete_done
+);
static gboolean
teco_state_replace_insert_initial(teco_machine_main_t *ctx, GError **error)
{
- if (ctx->flags.mode == TECO_MODE_NORMAL)
- teco_machine_stringbuilding_set_codepage(&ctx->expectstring.machine,
- teco_interface_get_codepage());
+ if (ctx->flags.mode > TECO_MODE_NORMAL)
+ return TRUE;
+
+ /*
+ * Overwrites teco_ranges set by the preceding search.
+ * FIXME: Wastes undo tokens in teco_do_search().
+ * Perhaps make this configurable in the state.
+ */
+ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos);
+ teco_undo_guint(teco_ranges_count) = 1;
+
+ /*
+ * Current document's encoding determines the behaviour of
+ * string building constructs.
+ */
+ teco_machine_stringbuilding_set_codepage(&ctx->expectstring.machine,
+ teco_interface_get_codepage());
return TRUE;
}
-/*
- * FIXME: Could be static
- */
-TECO_DEFINE_STATE_INSERT(teco_state_replace_insert,
+static TECO_DEFINE_STATE_INSERT(teco_state_replace_insert,
.initial_cb = (teco_state_initial_cb_t)teco_state_replace_insert_initial
);
static teco_state_t *
-teco_state_replace_ignore_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_ignore_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
return &teco_state_start;
}
-/*
- * FIXME: Could be static
- */
-TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_ignore);
+static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_ignore,
+ .expectstring.done_cb = teco_state_replace_ignore_done
+);
static teco_state_t *
-teco_state_replace_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_replace_ignore;
@@ -1270,16 +1286,12 @@ teco_state_replace_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr
* immediately and interactively.
*/
TECO_DEFINE_STATE_SEARCH(teco_state_replace,
- .expectstring.last = FALSE
+ .expectstring.last = FALSE,
+ .expectstring.done_cb = teco_state_replace_done
);
-/*
- * FIXME: TECO_DEFINE_STATE_INSERT() already defines a done_cb(),
- * so we had to name this differently.
- * Perhaps it simply shouldn't define it.
- */
static teco_state_t *
-teco_state_replace_default_insert_done_overwrite(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_default_insert_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -1287,55 +1299,53 @@ teco_state_replace_default_insert_done_overwrite(teco_machine_main_t *ctx, const
teco_qreg_t *replace_reg = teco_qreg_table_find(&teco_qreg_table_globals, "-", 1);
g_assert(replace_reg != NULL);
- if (str->len > 0) {
+ if (str.len > 0) {
if (!replace_reg->vtable->undo_set_string(replace_reg, error) ||
- !replace_reg->vtable->set_string(replace_reg, str->data, str->len,
+ !replace_reg->vtable->set_string(replace_reg, str.data, str.len,
teco_default_codepage(), error))
return NULL;
} else {
g_auto(teco_string_t) replace_str = {NULL, 0};
if (!replace_reg->vtable->get_string(replace_reg, &replace_str.data, &replace_str.len,
NULL, error) ||
- (replace_str.len > 0 && !teco_state_insert_process(ctx, &replace_str, replace_str.len, error)))
+ (replace_str.len > 0 && !teco_state_insert_process(ctx, replace_str, replace_str.len, error)))
return NULL;
}
+ sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+ teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos);
return &teco_state_start;
}
-/*
- * FIXME: Could be static
- */
-TECO_DEFINE_STATE_INSERT(teco_state_replace_default_insert,
- .initial_cb = NULL,
- .expectstring.done_cb = teco_state_replace_default_insert_done_overwrite
+static TECO_DEFINE_STATE_INSERT(teco_state_replace_default_insert,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_replace_insert_initial,
+ .expectstring.done_cb = teco_state_replace_default_insert_done
);
static teco_state_t *
-teco_state_replace_default_ignore_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_default_ignore_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL ||
- !str->len)
+ !str.len)
return &teco_state_start;
teco_qreg_t *replace_reg = teco_qreg_table_find(&teco_qreg_table_globals, "-", 1);
g_assert(replace_reg != NULL);
if (!replace_reg->vtable->undo_set_string(replace_reg, error) ||
- !replace_reg->vtable->set_string(replace_reg, str->data, str->len,
+ !replace_reg->vtable->set_string(replace_reg, str.data, str.len,
teco_default_codepage(), error))
return NULL;
return &teco_state_start;
}
-/*
- * FIXME: Could be static
- */
-TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_default_ignore);
+static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_replace_default_ignore,
+ .expectstring.done_cb = teco_state_replace_default_ignore_done
+);
static teco_state_t *
-teco_state_replace_default_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_default_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_replace_default_ignore;
@@ -1374,11 +1384,12 @@ teco_state_replace_default_done(teco_machine_main_t *ctx, const teco_string_t *s
* register is implied instead.
*/
TECO_DEFINE_STATE_SEARCH(teco_state_replace_default,
- .expectstring.last = FALSE
+ .expectstring.last = FALSE,
+ .expectstring.done_cb = teco_state_replace_default_done
);
static teco_state_t *
-teco_state_replace_default_all_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_replace_default_all_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_replace_default_ignore;
@@ -1417,6 +1428,7 @@ teco_state_replace_default_all_done(teco_machine_main_t *ctx, const teco_string_
* <to>.
*/
TECO_DEFINE_STATE_SEARCH(teco_state_replace_default_all,
+ .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial,
.expectstring.last = FALSE,
- .initial_cb = (teco_state_initial_cb_t)teco_state_search_all_initial
+ .expectstring.done_cb = teco_state_replace_default_all_done
);
diff --git a/src/search.h b/src/search.h
index 222ca6c..9bd62f7 100644
--- a/src/search.h
+++ b/src/search.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -22,10 +22,10 @@
void teco_state_control_search_mode(teco_machine_main_t *ctx, GError **error);
-TECO_DECLARE_STATE(teco_state_search);
-TECO_DECLARE_STATE(teco_state_search_all);
-TECO_DECLARE_STATE(teco_state_search_kill);
-TECO_DECLARE_STATE(teco_state_search_delete);
-TECO_DECLARE_STATE(teco_state_replace);
-TECO_DECLARE_STATE(teco_state_replace_default);
-TECO_DECLARE_STATE(teco_state_replace_default_all);
+extern teco_state_t teco_state_search;
+extern teco_state_t teco_state_search_all;
+extern teco_state_t teco_state_search_kill;
+extern teco_state_t teco_state_search_delete;
+extern teco_state_t teco_state_replace;
+extern teco_state_t teco_state_replace_default;
+extern teco_state_t teco_state_replace_default_all;
diff --git a/src/spawn.c b/src/spawn.c
index 73f389e..61718fd 100644
--- a/src/spawn.c
+++ b/src/spawn.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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,7 +129,7 @@ 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')) {
+ if (teco_string_contains(comspec, '\0')) {
teco_string_clear(&comspec);
teco_error_qregcontainsnull_set(error, "$COMSPEC", 8, FALSE);
return NULL;
@@ -150,7 +150,7 @@ 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')) {
+ if (teco_string_contains(shell, '\0')) {
teco_string_clear(&shell);
teco_error_qregcontainsnull_set(error, "$SHELL", 6, FALSE);
return NULL;
@@ -242,7 +242,7 @@ teco_state_execute_initial(teco_machine_main_t *ctx, GError **error)
}
static teco_state_t *
-teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_execute_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
/*
* NOTE: With G_SPAWN_LEAVE_DESCRIPTORS_OPEN and without G_SPAWN_SEARCH_PATH_FROM_ENVP,
@@ -285,13 +285,13 @@ teco_state_execute_done(teco_machine_main_t *ctx, const teco_string_t *str, GErr
}
#endif
- if (!str->len || teco_string_contains(str, '\0')) {
+ if (!str.len || teco_string_contains(str, '\0')) {
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Command line must not be empty or contain null-bytes");
goto gerror;
}
- argv = teco_parse_shell_command_line(str->data, error);
+ argv = teco_parse_shell_command_line(str.data, error);
if (!argv)
goto gerror;
@@ -605,7 +605,8 @@ gboolean teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_mach
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_execute,
.initial_cb = (teco_state_initial_cb_t)teco_state_execute_initial,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_execute_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_execute_process_edit_cmd,
+ .expectstring.done_cb = teco_state_execute_done
);
static teco_state_t *
@@ -647,7 +648,8 @@ teco_state_egcommand_got_register(teco_machine_main_t *ctx, teco_qreg_t *qreg,
* The register <q> is defined if it does not already exist.
*/
TECO_DEFINE_STATE_EXPECTQREG(teco_state_egcommand,
- .expectqreg.type = TECO_QREG_OPTIONAL_INIT
+ .expectqreg.type = TECO_QREG_OPTIONAL_INIT,
+ .expectqreg.got_register_cb = teco_state_egcommand_got_register
);
/*
diff --git a/src/spawn.h b/src/spawn.h
index ef210e9..09764bd 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -18,5 +18,5 @@
#include "parser.h"
-TECO_DECLARE_STATE(teco_state_execute);
-TECO_DECLARE_STATE(teco_state_egcommand);
+extern teco_state_t teco_state_execute;
+extern teco_state_t teco_state_egcommand;
diff --git a/src/stdio-commands.c b/src/stdio-commands.c
index 3a1b320..abb6566 100644
--- a/src/stdio-commands.c
+++ b/src/stdio-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -38,7 +38,7 @@ static inline gboolean
teco_cmdline_is_executing(teco_machine_main_t *ctx)
{
return ctx == &teco_cmdline.machine &&
- ctx->macro_pc == teco_cmdline.effective_len;
+ ctx->macro_pc == teco_cmdline_ssm(SCI_GETCURRENTPOS, 0, 0);
}
static gboolean is_executing = FALSE;
@@ -118,7 +118,7 @@ teco_print(teco_machine_main_t *ctx, guint radix, GError **error)
* then octal and finally in hexadecimal.
* This won't happen e.g. in a loop that is closed on the command-line.
*/
-TECO_DECLARE_STATE(teco_state_print_octal);
+static teco_state_t teco_state_print_octal;
static gboolean
teco_state_print_decimal_initial(teco_machine_main_t *ctx, GError **error)
@@ -168,6 +168,7 @@ teco_state_print_decimal_end_of_macro(teco_machine_main_t *ctx, GError **error)
TECO_DEFINE_STATE_START(teco_state_print_decimal,
.initial_cb = (teco_state_initial_cb_t)teco_state_print_decimal_initial,
+ .input_cb = (teco_state_input_cb_t)teco_state_print_decimal_input,
.end_of_macro_cb = (teco_state_end_of_macro_cb_t)
teco_state_print_decimal_end_of_macro
);
@@ -224,8 +225,9 @@ teco_state_print_octal_end_of_macro(teco_machine_main_t *ctx, GError **error)
return TRUE;
}
-TECO_DEFINE_STATE_START(teco_state_print_octal,
+static TECO_DEFINE_STATE_START(teco_state_print_octal,
.initial_cb = (teco_state_initial_cb_t)teco_state_print_octal_initial,
+ .input_cb = (teco_state_input_cb_t)teco_state_print_octal_input,
.end_of_macro_cb = (teco_state_end_of_macro_cb_t)
teco_state_print_octal_end_of_macro
);
@@ -251,9 +253,9 @@ teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error)
}
static teco_state_t *
-teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_print_string_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
- teco_interface_msg_literal(TECO_MSG_USER, str->data, str->len);
+ teco_interface_msg_literal(TECO_MSG_USER, str.data, str.len);
return &teco_state_start;
}
@@ -282,7 +284,8 @@ teco_state_print_string_done(teco_machine_main_t *ctx, const teco_string_t *str,
* ^EUq, ^E<...> and case folding.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_print_string,
- .initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial
+ .initial_cb = (teco_state_initial_cb_t)teco_state_print_string_initial,
+ .expectstring.done_cb = teco_state_print_string_done
);
/*$ T type typeout
diff --git a/src/stdio-commands.h b/src/stdio-commands.h
index 985d1ac..cf04b34 100644
--- a/src/stdio-commands.h
+++ b/src/stdio-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -26,5 +26,5 @@ void teco_state_control_typeout(teco_machine_main_t *ctx, GError **error);
/*
* Command states
*/
-TECO_DECLARE_STATE(teco_state_print_decimal);
-TECO_DECLARE_STATE(teco_state_print_string);
+extern teco_state_t teco_state_print_decimal;
+extern teco_state_t teco_state_print_string;
diff --git a/src/string-utils.c b/src/string-utils.c
index 10e34a8..e9dd148 100644
--- a/src/string-utils.c
+++ b/src/string-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -98,12 +98,12 @@ teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guin
* @memberof teco_string_t
*/
gsize
-teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len)
+teco_string_diff(teco_string_t a, const gchar *b, gsize b_len)
{
gsize len = 0;
- while (len < a->len && len < b_len &&
- a->data[len] == b[len])
+ while (len < a.len && len < b_len &&
+ a.data[len] == b[len])
len++;
return len;
@@ -124,12 +124,12 @@ teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len)
* @memberof teco_string_t
*/
gsize
-teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len)
+teco_string_casediff(teco_string_t a, const gchar *b, gsize b_len)
{
gsize len = 0;
- while (len < a->len && len < b_len) {
- gunichar a_chr = g_utf8_get_char(a->data+len);
+ while (len < a.len && len < b_len) {
+ gunichar a_chr = g_utf8_get_char(a.data+len);
gunichar b_chr = g_utf8_get_char(b+len);
if (g_unichar_tolower(a_chr) != g_unichar_tolower(b_chr))
break;
@@ -141,36 +141,36 @@ teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len)
/** @memberof teco_string_t */
gint
-teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len)
+teco_string_cmp(teco_string_t a, const gchar *b, gsize b_len)
{
- for (guint i = 0; i < a->len; i++) {
+ for (guint i = 0; i < a.len; i++) {
if (i == b_len)
/* b is a prefix of a */
return 1;
- gint ret = (gint)a->data[i] - (gint)b[i];
+ gint ret = (gint)a.data[i] - (gint)b[i];
if (ret != 0)
/* a and b have a common prefix of length i */
return ret;
}
- return a->len == b_len ? 0 : -1;
+ return a.len == b_len ? 0 : -1;
}
/** @memberof teco_string_t */
gint
-teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len)
+teco_string_casecmp(teco_string_t a, const gchar *b, gsize b_len)
{
- for (guint i = 0; i < a->len; i++) {
+ for (guint i = 0; i < a.len; i++) {
if (i == b_len)
/* b is a prefix of a */
return 1;
- gint ret = (gint)g_ascii_tolower(a->data[i]) - (gint)g_ascii_tolower(b[i]);
+ gint ret = (gint)g_ascii_tolower(a.data[i]) - (gint)g_ascii_tolower(b[i]);
if (ret != 0)
/* a and b have a common prefix of length i */
return ret;
}
- return a->len == b_len ? 0 : -1;
+ return a.len == b_len ? 0 : -1;
}
/**
@@ -184,22 +184,20 @@ teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len)
* @memberof teco_string_t
*/
const gchar *
-teco_string_last_occurrence(const teco_string_t *str, const gchar *chars)
+teco_string_last_occurrence(teco_string_t str, const gchar *chars)
{
- teco_string_t ret = *str;
-
- if (!ret.len)
+ if (!str.len)
return NULL;
do {
- gint i = teco_string_rindex(&ret, *chars);
+ gint i = teco_string_rindex(str, *chars);
if (i >= 0) {
- ret.data += i+1;
- ret.len -= i+1;
+ str.data += i+1;
+ str.len -= i+1;
}
} while (*chars++);
- return ret.data;
+ return str.data;
}
TECO_DEFINE_UNDO_CALL(teco_string_truncate, teco_string_t *, gsize);
diff --git a/src/string-utils.h b/src/string-utils.h
index 2491d07..a1eda4e 100644
--- a/src/string-utils.h
+++ b/src/string-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -63,6 +63,9 @@ teco_strv_remove(gchar **strv, guint i)
* to functions expecting traditional null-terminated C strings if you can
* guarantee that it contains no null-character other than the trailing one.
*
+ * Since string objects are just two words, they can and should be passed by
+ * value if the callee doesn't have to modify it.
+ *
* @warning For consistency with C idioms the underlying character type is
* `char`, which might be signed!
* Accessing individual characters may yield signed integers and that sign
@@ -102,7 +105,7 @@ teco_string_init(teco_string_t *target, const gchar *str, gsize len)
static inline void
teco_string_init_chunk(teco_string_t *target, const gchar *str, gssize len, GStringChunk *chunk)
{
- target->data = g_string_chunk_insert_len(chunk, str, len);
+ target->data = g_string_chunk_insert_len(chunk, str ? : "", len);
target->len = len;
}
@@ -164,19 +167,19 @@ gchar *teco_string_echo(const gchar *str, gsize len);
void teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guint *column);
-typedef gsize (*teco_string_diff_t)(const teco_string_t *a, const gchar *b, gsize b_len);
-gsize teco_string_diff(const teco_string_t *a, const gchar *b, gsize b_len);
-gsize teco_string_casediff(const teco_string_t *a, const gchar *b, gsize b_len);
+typedef gsize (*teco_string_diff_t)(teco_string_t a, const gchar *b, gsize b_len);
+gsize teco_string_diff(teco_string_t a, const gchar *b, gsize b_len);
+gsize teco_string_casediff(teco_string_t a, const gchar *b, gsize b_len);
-typedef gint (*teco_string_cmp_t)(const teco_string_t *a, const gchar *b, gsize b_len);
-gint teco_string_cmp(const teco_string_t *a, const gchar *b, gsize b_len);
-gint teco_string_casecmp(const teco_string_t *a, const gchar *b, gsize b_len);
+typedef gint (*teco_string_cmp_t)(teco_string_t a, const gchar *b, gsize b_len);
+gint teco_string_cmp(teco_string_t a, const gchar *b, gsize b_len);
+gint teco_string_casecmp(teco_string_t a, const gchar *b, gsize b_len);
/** @memberof teco_string_t */
static inline gboolean
-teco_string_contains(const teco_string_t *str, gchar chr)
+teco_string_contains(teco_string_t str, gchar chr)
{
- return str->data && memchr(str->data, chr, str->len);
+ return str.data && memchr(str.data, chr, str.len);
}
/**
@@ -188,26 +191,26 @@ teco_string_contains(const teco_string_t *str, gchar chr)
* @memberof teco_string_t
*/
static inline gint
-teco_string_rindex(const teco_string_t *str, gchar chr)
+teco_string_rindex(teco_string_t str, gchar chr)
{
gint i;
- for (i = str->len-1; i >= 0 && str->data[i] != chr; i--);
+ for (i = str.len-1; i >= 0 && str.data[i] != chr; i--);
return i;
}
-const gchar *teco_string_last_occurrence(const teco_string_t *str, const gchar *chars);
+const gchar *teco_string_last_occurrence(teco_string_t str, const gchar *chars);
/**
* Validate whether string consists exclusively of valid UTF-8, but accept null bytes.
* @note there is g_utf8_validate_len() in Glib 2.60
*/
static inline gboolean
-teco_string_validate_utf8(const teco_string_t *str)
+teco_string_validate_utf8(teco_string_t str)
{
- const gchar *p = str->data;
- while (!g_utf8_validate(p, str->len - (p - str->data), &p) && !*p)
+ const gchar *p = str.data;
+ while (!g_utf8_validate(p, str.len - (p - str.data), &p) && !*p)
p++;
- return p - str->data == str->len;
+ return p - str.data == str.len;
}
/** @memberof teco_string_t */
diff --git a/src/symbols.c b/src/symbols.c
index b5600e8..dd5856e 100644
--- a/src/symbols.c
+++ b/src/symbols.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -38,6 +38,7 @@
#include "undo.h"
#include "expressions.h"
#include "interface.h"
+#include "cmdline.h"
#include "symbols.h"
teco_symbol_list_t teco_symbol_list_scintilla = {NULL, 0};
@@ -138,7 +139,7 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec
glist_str.data = (gchar *)glist->data + symbol_len;
glist_str.len = strlen(glist_str.data);
- gsize len = teco_string_casediff(&glist_str, (gchar *)entry->data + symbol_len,
+ gsize len = teco_string_casediff(glist_str, (gchar *)entry->data + symbol_len,
strlen(entry->data) - symbol_len);
if (!prefix_len || len < prefix_len)
prefix_len = len;
@@ -166,13 +167,17 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec
* Command states
*/
-/*
- * FIXME: This state could be static.
- */
-TECO_DECLARE_STATE(teco_state_scintilla_lparam);
+static inline sptr_t
+teco_scintilla_ssm(unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return teco_view_ssm(teco_ed & TECO_ED_MINIBUF_SSM ? teco_cmdline.view : teco_interface_current_view,
+ iMessage, wParam, lParam);
+}
+
+static teco_state_t teco_state_scintilla_lparam;
static gboolean
-teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_string_t *str, GError **error)
+teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, teco_string_t str, GError **error)
{
if (teco_string_contains(str, '\0')) {
g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED,
@@ -180,7 +185,7 @@ teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_str
return FALSE;
}
- g_auto(GStrv) symbols = g_strsplit(str->data, ",", -1);
+ g_auto(GStrv) symbols = g_strsplit(str.data, ",", -1);
if (!symbols[0])
return TRUE;
@@ -212,7 +217,7 @@ teco_scintilla_parse_symbols(teco_machine_scintilla_t *scintilla, const teco_str
}
static teco_state_t *
-teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_scintilla_lparam;
@@ -226,7 +231,7 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t
teco_undo_scintilla_message(ctx->scintilla);
memset(&ctx->scintilla, 0, sizeof(ctx->scintilla));
- if ((str->len > 0 && !teco_scintilla_parse_symbols(&ctx->scintilla, str, error)) ||
+ if ((str.len > 0 && !teco_scintilla_parse_symbols(&ctx->scintilla, str, error)) ||
!teco_expressions_eval(FALSE, error))
return NULL;
@@ -246,7 +251,7 @@ 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_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, teco_string_t str,
GError **error);
/*$ ES scintilla message
@@ -316,6 +321,13 @@ gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx
* second string argument of \fBES\fP, i.e. it allows you
* to look up style ids by name.
*
+ * By default Scintilla messages are sent to the current buffer's
+ * view or the Q-register view \(em there is only one view for
+ * all Q-registers.
+ * If bit 11 is set in the \fBED\fP flags, the messages will be
+ * sent to the command-line view instead, which allows you to
+ * set up \*(ST syntax highlighting and other styles.
+ *
* .BR Warning :
* Almost all Scintilla messages may be dispatched using
* this command.
@@ -340,17 +352,18 @@ gboolean teco_state_scintilla_symbols_insert_completion(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
+ .expectstring.last = FALSE,
+ .expectstring.done_cb = teco_state_scintilla_symbols_done
);
#ifdef HAVE_LEXILLA
static gpointer
-teco_create_lexer(const teco_string_t *str, GError **error)
+teco_create_lexer(teco_string_t str, GError **error)
{
CreateLexerFn CreateLexerFn = CreateLexer;
- const gchar *lexer = memchr(str->data ? : "", '\0', str->len);
+ const gchar *lexer = memchr(str.data ? : "", '\0', str.len);
if (lexer) {
/* external lexer */
lexer++;
@@ -359,7 +372,7 @@ teco_create_lexer(const teco_string_t *str, GError **error)
* NOTE: The same module can be opened multiple times.
* They are internally reference counted.
*/
- GModule *module = g_module_open(str->data, G_MODULE_BIND_LAZY);
+ GModule *module = g_module_open(str.data, G_MODULE_BIND_LAZY);
if (!module) {
teco_error_module_set(error, "Error opening lexer module");
return NULL;
@@ -381,7 +394,7 @@ teco_create_lexer(const teco_string_t *str, GError **error)
*
* FIXME: In Scintillua distributions, the lexers are usually contained in the
* same directory as the prebuilt shared libraries.
- * Perhaps we should default scintillua.lexers to the dirname in str->data?
+ * Perhaps we should default scintillua.lexers to the dirname in str.data?
*/
teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_SCINTILLUA_LEXERS", 26);
if (reg) {
@@ -393,7 +406,7 @@ teco_create_lexer(const teco_string_t *str, GError **error)
}
} else {
/* Lexilla lexer */
- lexer = str->data ? : "";
+ lexer = str.data ? : "";
}
ILexer5 *ret = CreateLexerFn(lexer);
@@ -409,9 +422,9 @@ teco_create_lexer(const teco_string_t *str, GError **error)
#else /* !HAVE_LEXILLA */
static gpointer
-teco_create_lexer(const teco_string_t *str, GError **error)
+teco_create_lexer(teco_string_t str, GError **error)
{
- g_autofree gchar *str_printable = teco_string_echo(str->data, str->len);
+ g_autofree gchar *str_printable = teco_string_echo(str.data, str.len);
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
"Cannot load lexer \"%s\": Lexilla disabled", str_printable);
return NULL;
@@ -420,7 +433,7 @@ teco_create_lexer(const teco_string_t *str, GError **error)
#endif
static teco_state_t *
-teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, teco_string_t str, GError **error)
{
if (ctx->flags.mode > TECO_MODE_NORMAL)
return &teco_state_start;
@@ -442,10 +455,10 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *
/*
* FIXME: Should we cache the name to style id?
*/
- guint count = teco_interface_ssm(SCI_GETNAMEDSTYLES, 0, 0);
+ guint count = teco_scintilla_ssm(SCI_GETNAMEDSTYLES, 0, 0);
for (guint id = 0; id < count; id++) {
gchar style[128] = "";
- teco_interface_ssm(SCI_NAMEOFSTYLE, id, (sptr_t)style);
+ teco_scintilla_ssm(SCI_NAMEOFSTYLE, id, (sptr_t)style);
if (!teco_string_cmp(str, style, strlen(style))) {
teco_expressions_push(id);
return &teco_state_start;
@@ -453,13 +466,13 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *
}
g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Style name \"%s\" not found.", str->data ? : "");
+ "Style name \"%s\" not found.", str.data ? : "");
return NULL;
} else if (ctx->scintilla.iMessage == SCI_SETILEXER) {
lParam = (sptr_t)teco_create_lexer(str, error);
if (!lParam)
return NULL;
- } else if (str->len > 0) {
+ } else if (str.len > 0) {
/*
* Theoretically, Scintilla could use null bytes from the string specified.
* This could only be the case for messages where the string length is
@@ -469,13 +482,13 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *
* which unlocks useful messages like
* SCI_SETREPRESENTATIONS and SCI_SETPROPERTY.
*/
- const gchar *p = memchr(str->data, '\0', str->len);
+ const gchar *p = memchr(str.data, '\0', str.len);
if (p) {
- ctx->scintilla.wParam = (uptr_t)str->data;
- if (str->len > p - str->data + 1)
+ ctx->scintilla.wParam = (uptr_t)str.data;
+ if (str.len > p - str.data + 1)
lParam = (sptr_t)(p+1);
} else {
- lParam = (sptr_t)str->data;
+ lParam = (sptr_t)str.data;
}
}
@@ -491,10 +504,12 @@ teco_state_scintilla_lparam_done(teco_machine_main_t *ctx, const teco_string_t *
lParam = v;
}
- teco_expressions_push(teco_interface_ssm(ctx->scintilla.iMessage,
+ teco_expressions_push(teco_scintilla_ssm(ctx->scintilla.iMessage,
ctx->scintilla.wParam, lParam));
return &teco_state_start;
}
-TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_lparam);
+static TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_lparam,
+ .expectstring.done_cb = teco_state_scintilla_lparam_done
+);
diff --git a/src/symbols.h b/src/symbols.h
index 1d0af12..c7db610 100644
--- a/src/symbols.h
+++ b/src/symbols.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -61,4 +61,4 @@ extern teco_symbol_list_t teco_symbol_list_scilexer;
* Command states
*/
-TECO_DECLARE_STATE(teco_state_scintilla_symbols);
+extern teco_state_t teco_state_scintilla_symbols;
diff --git a/src/undo.c b/src/undo.c
index 2048af3..18f90c5 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 c236970..476ca45 100644
--- a/src/undo.h
+++ b/src/undo.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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 790f832..a522d1c 100644
--- a/src/view.c
+++ b/src/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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
@@ -145,7 +145,14 @@ teco_view_setup(teco_view_t *ctx)
TECO_DEFINE_UNDO_CALL(teco_view_ssm, teco_view_t *, unsigned int, uptr_t, sptr_t);
-/** @memberof teco_view_t */
+/**
+ * Configure typical TECO representations for control characters.
+ *
+ * You may have to SCI_SETVIEWEOL(TRUE) to see the CR and LF characters.
+ * In order to see the TAB character use SCI_SETTABDRAWMODE(SCTD_CONTROLCHAR).
+ *
+ * @memberof teco_view_t
+ */
void
teco_view_set_representations(teco_view_t *ctx)
{
@@ -459,13 +466,10 @@ teco_undo_restore_savepoint_push(gchar *savepoint, const gchar *filename)
static void
teco_make_savepoint(const gchar *filename)
{
- gchar savepoint_basename[FILENAME_MAX];
-
g_autofree gchar *basename = g_path_get_basename(filename);
- g_snprintf(savepoint_basename, sizeof(savepoint_basename),
- ".teco-%d-%s~", savepoint_id, basename);
g_autofree gchar *dirname = g_path_get_dirname(filename);
- gchar *savepoint = g_build_filename(dirname, savepoint_basename, NULL);
+ gchar *savepoint = g_strdup_printf("%s%c.teco-%d-%s~", dirname, G_DIR_SEPARATOR,
+ savepoint_id, basename);
if (g_rename(filename, savepoint)) {
teco_interface_msg(TECO_MSG_WARNING,
@@ -504,6 +508,13 @@ teco_undo_remove_file_push(const gchar *filename)
strcpy(ctx, filename);
}
+/**
+ * Save the view's document to the given IO channel.
+ *
+ * @note This must not emit undo tokens as it is also used by teco_ring_dump_recovery().
+ *
+ * @memberof teco_view_t
+ */
gboolean
teco_view_save_to_channel(teco_view_t *ctx, GIOChannel *channel, GError **error)
{
@@ -543,16 +554,16 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
file_stat.st_gid = -1;
#endif
teco_file_attributes_t attributes = TECO_FILE_INVALID_ATTRIBUTES;
+ gboolean undo_remove_file = FALSE;
if (teco_undo_enabled) {
- if (g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ undo_remove_file = !g_file_test(filename, G_FILE_TEST_IS_REGULAR);
+ if (!undo_remove_file) {
#ifdef G_OS_UNIX
g_stat(filename, &file_stat);
#endif
attributes = teco_file_get_attributes(filename);
teco_make_savepoint(filename);
- } else {
- teco_undo_remove_file_push(filename);
}
}
@@ -561,6 +572,18 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!channel)
return FALSE;
+ if (undo_remove_file) {
+ /*
+ * The file is new, so has to be removed on undo.
+ * If `filename` is a symlink, it's crucial to resolve it now,
+ * since early canonicalization may have failed (for non-existent
+ * path segments).
+ * Now, `filename` is guaranteed to exist.
+ */
+ g_autofree gchar *filename_canon = teco_file_get_absolute_path(filename);
+ teco_undo_remove_file_push(filename_canon);
+ }
+
/*
* teco_view_save_to_channel() expects a buffered and blocking channel
*/
@@ -569,6 +592,7 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error)
if (!teco_view_save_to_channel(ctx, channel, error)) {
g_prefix_error(error, "Error writing file \"%s\": ", filename);
+ /* file might also be removed (in interactive mode) */
return FALSE;
}
diff --git a/src/view.h b/src/view.h
index 82a5c99..4c57eb8 100644
--- a/src/view.h
+++ b/src/view.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2025 Robin Haberkorn
+ * Copyright (C) 2012-2026 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/tests/atlocal.in b/tests/atlocal.in
index b2ceda1..81dc1fb 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -40,4 +40,4 @@ esac
# Default stack size on Linux (8M).
# Some platforms allow very large stack sizes, making it hard to test
# against potential stack overflows.
-ulimit -s 8192
+ulimit -s 8192 || true
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 38035a8..f34eee3 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -291,6 +291,8 @@ AT_SETUP([Search and insertion ranges])
TE_CHECK([[@I/XXYYZZ/^SC ."N(0/0)' C @S/YY/ HK ^YU1U0 Q0-2"N(0/0)' Q1-4"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[@I/XXYYZZ/J @S/XX^E[^EMY]/ 1^YXa :Qa-2"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[@I/XXYYZZ/J @FD/^EMZ/ ^S+2"N(0/0)']], 0, ignore, ignore)
+TE_CHECK([[@I/ABCDEF/J @^U-/1234/ @FR/ABC// ^S+4"N(0/0)']], 0, ignore, ignore)
+TE_CHECK([[@I/ABCDEF/J @FS/ABC/1234/ ^S+4"N(0/0)']], 0, ignore, ignore)
TE_CHECK([[@^Ua/XYZ/ Ga ^S+3"N(0/0)']], 0, ignore, ignore)
# NOTE: EN currently inserts another trailing linefeed.
TE_CHECK([[@EN/*/XYZ/ ^S+4"N(0/0)']], 0, ignore, ignore)
@@ -378,7 +380,7 @@ AT_CLEANUP
AT_SETUP([Memory limiting])
# FIXME: Requires too much time and memory at least when running in a CI job on my fmsbw.de server
# (.fmsbw/10-freebsd14-sciteco).
-AT_SKIP_IF([test $at_arg_valgrind != false -a "$FMSBW_CI" = 1])
+AT_SKIP_IF([test $at_arg_valgrind != false])
TE_CHECK([[50*1000*1000,2EJ <[a> !]!]], 1, ignore, ignore)
AT_CLEANUP
@@ -458,6 +460,13 @@ AT_CHECK([[$SCITECO -e "@EI'^EQ[\$SCITECOPATH]/opener.tes' M[opener] EF .-11\"N(
AT_CHECK([[$SCITECO -e "@EI'^EQ[\$SCITECOPATH]/opener.tes' M[opener] EF EJ-2\"N(0/0)'" -S +1 data.123]], 0, ignore, ignore)
AT_CLEANUP
+# FIXME: Does not work on OBS runners, perhaps because of Xvfb?
+# It's not reproducible on FreeBSD.
+#AT_SETUP([Detaching from terminal])
+#AT_SKIP_IF([$SCITECO --help | $GREP -qvz -e '--detach'])
+#AT_CHECK([[timeout 10 $SHELL -c "$SCITECO --detach -e '23= @EW/test.txt/' && while [ ! -f test.txt ]; do sleep 1; done"]], 0, [], [])
+#AT_CLEANUP
+
AT_BANNER([Regression Tests])
AT_SETUP([Glob patterns with character classes])
@@ -500,7 +509,7 @@ AT_CLEANUP
AT_SETUP([Memory limiting during spawning])
# FIXME: Requires too much time and memory at least when running in a CI job on my fmsbw.de server
# (.fmsbw/10-freebsd14-sciteco).
-AT_SKIP_IF([test $at_arg_valgrind != false -a "$FMSBW_CI" = 1])
+AT_SKIP_IF([test $at_arg_valgrind != false])
# This might result in an OOM if memory limiting is not working
TE_CHECK([[50*1000*1000,2EJ 0,128ED @EC'dd if=/dev/zero']], 1, ignore, ignore)
AT_CLEANUP