aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/irc.yml33
-rw-r--r--.github/workflows/nightly.yml71
-rwxr-xr-xAppImage/curses.yml2
-rwxr-xr-xAppImage/gtk.yml2
-rw-r--r--AppImage/sciteco-curses.appdata.xml27
-rw-r--r--AppImage/sciteco-gtk.appdata.xml27
-rw-r--r--INSTALL2
-rw-r--r--NEWS8
-rw-r--r--README6
-rw-r--r--TODO76
-rw-r--r--configure.ac9
m---------contrib/scinterm0
-rw-r--r--debian/control2
-rw-r--r--debian/copyright2
-rwxr-xr-xdebian/rules4
-rw-r--r--distribute.mk.in14
-rw-r--r--doc/cheat-sheet.mm127
-rw-r--r--doc/sciteco.1.in52
-rw-r--r--doc/sciteco.7.template57
-rw-r--r--freebsd/pkg-plist1
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/color.tes66
-rw-r--r--lib/fnkeys.tes68
-rw-r--r--lib/opener.tes37
-rw-r--r--sample.teco_ini17
-rw-r--r--src/cmdline.c151
-rw-r--r--src/cmdline.h2
-rw-r--r--src/core-commands.c195
-rw-r--r--src/core-commands.h2
-rw-r--r--src/doc.c2
-rw-r--r--src/doc.h2
-rw-r--r--src/eol.c2
-rw-r--r--src/eol.h2
-rw-r--r--src/error.c2
-rw-r--r--src/error.h2
-rw-r--r--src/expressions.c2
-rw-r--r--src/expressions.h2
-rw-r--r--src/file-utils.c80
-rw-r--r--src/file-utils.h2
-rw-r--r--src/glob.c2
-rw-r--r--src/glob.h5
-rw-r--r--src/goto-commands.c28
-rw-r--r--src/goto-commands.h2
-rw-r--r--src/goto.c121
-rw-r--r--src/goto.h8
-rw-r--r--src/help.c12
-rw-r--r--src/help.h2
-rw-r--r--src/interface-curses/curses-icons.c2
-rw-r--r--src/interface-curses/curses-icons.h2
-rw-r--r--src/interface-curses/curses-info-popup.c74
-rw-r--r--src/interface-curses/curses-info-popup.h20
-rw-r--r--src/interface-curses/curses-utils.c2
-rw-r--r--src/interface-curses/curses-utils.h2
-rw-r--r--src/interface-curses/interface.c437
-rw-r--r--src/interface-gtk/Makefile.am2
-rw-r--r--src/interface-gtk/gtk-info-popup.c57
-rw-r--r--src/interface-gtk/gtk-info-popup.h2
-rw-r--r--src/interface-gtk/gtk-label.c8
-rw-r--r--src/interface-gtk/gtk-label.h3
-rw-r--r--src/interface-gtk/interface.c329
-rw-r--r--src/interface-gtk/view.c126
-rw-r--r--src/interface.c5
-rw-r--r--src/interface.h28
-rw-r--r--src/lexer.c2
-rw-r--r--src/lexer.h2
-rw-r--r--src/list.h2
-rw-r--r--src/main.c47
-rw-r--r--src/memory.c2
-rw-r--r--src/memory.h2
-rw-r--r--src/parser.c7
-rw-r--r--src/parser.h34
-rw-r--r--src/qreg-commands.c2
-rw-r--r--src/qreg-commands.h9
-rw-r--r--src/qreg.c15
-rw-r--r--src/qreg.h2
-rw-r--r--src/rb3str.c4
-rw-r--r--src/rb3str.h2
-rw-r--r--src/ring.c66
-rw-r--r--src/ring.h4
-rw-r--r--src/sciteco.h4
-rw-r--r--src/search.c2
-rw-r--r--src/search.h2
-rw-r--r--src/spawn.c18
-rw-r--r--src/spawn.h2
-rw-r--r--src/string-utils.c2
-rw-r--r--src/string-utils.h2
-rw-r--r--src/symbols.c10
-rw-r--r--src/symbols.h2
-rw-r--r--src/undo.c2
-rw-r--r--src/undo.h2
-rw-r--r--src/view.c2
-rw-r--r--src/view.h2
-rw-r--r--tests/testsuite.at8
-rw-r--r--www/screenshots.md4
94 files changed, 2096 insertions, 611 deletions
diff --git a/.github/workflows/irc.yml b/.github/workflows/irc.yml
new file mode 100644
index 0000000..a44774c
--- /dev/null
+++ b/.github/workflows/irc.yml
@@ -0,0 +1,33 @@
+# After every push with successful CI,
+# post the commits since the last successful CI into the IRC channel.
+name: IRC Post
+
+on:
+ workflow_run:
+ workflows: ['Continuous Integration']
+ types: [completed]
+ branches: master
+
+jobs:
+ irc:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
+ steps:
+ - name: Git Clone
+ uses: actions/checkout@v4.1.6
+ - name: Install Build Dependencies
+ run: sudo apt-get install -y ncat
+ - name: Get last successful commit
+ uses: nrwl/last-successful-commit-action@v1
+ id: last_successful_commit
+ with:
+ branch: 'master'
+ workflow_id: 'ci.yml'
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ - name: IRC Connection
+ run: |
+ (echo "NICK git-bot"
+ echo "USER git-bot 8 * : git-bot"
+ echo "JOIN #sciteco"
+ git log --pretty="format:PRIVMSG #sciteco %h %s" --reverse ${{ steps.last_successful_commit.outputs.commit_hash }}..HEAD
+ echo "QUIT") | ncat --ssl irc.libera.chat 6697
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 11be950..af16dbe 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -50,10 +50,12 @@ jobs:
- name: Build AppImages
# Should always be on the oldest supported Ubuntu
if: matrix.os == 'ubuntu-20.04'
+ env:
+ GH_TOKEN: ${{ github.token }}
run: |
cd AppImage
- wget -O pkg2appimage.AppImage \
- "https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage--x86_64.AppImage"
+ gh release download -R AppImageCommunity/pkg2appimage -O pkg2appimage.AppImage \
+ -p 'pkg2appimage-*-x86_64.AppImage' continuous
chmod +x pkg2appimage.AppImage
./pkg2appimage.AppImage curses.yml
mv out/*.AppImage ../sciteco-curses_nightly_x86_64.AppImage
@@ -110,9 +112,12 @@ jobs:
#PKG_CONFIG_PATH: /usr/local/opt/ncurses/lib/pkgconfig
# NOTE: This will not result in a fully statically-linked binary,
# but the more we get rid off, the better.
+ # NOTE: Making the binary relocatable means it can be installed into non-root directories
+ # with `installer -pkg -target`.
run: |
autoreconf -i
- ./configure --with-interface=ncurses --enable-static-executables --enable-html-manual
+ ./configure --with-interface=ncurses --enable-static-executables --enable-html-manual \
+ --with-scitecodatadir=../share/sciteco
- name: make
run: make -j 2
@@ -170,14 +175,14 @@ jobs:
git commit -a -m "update ${{ github.sha }}"
git push -u origin +HEAD:gh-pages
- win32-curses:
+ win64-curses:
runs-on: windows-2019
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
- MSYSTEM: MINGW32
+ MSYSTEM: MINGW64
CHERE_INVOKING: 1
steps:
@@ -194,13 +199,13 @@ jobs:
- name: Install Build Dependencies
run: >
pacman -S --noconfirm --needed
- base-devel mingw-w64-i686-autotools mingw-w64-i686-toolchain
- mingw-w64-i686-glib2 mingw-w64-i686-pdcurses
+ base-devel mingw-w64-x86_64-autotools mingw-w64-x86_64-toolchain
+ mingw-w64-x86_64-glib2 mingw-w64-x86_64-pdcurses
groff
- name: Configure Build
env:
- PDCURSES_CFLAGS: -I/mingw32/include/pdcurses/
+ PDCURSES_CFLAGS: -I/mingw64/include/pdcurses/
# FIXME: glib on MinGW supports static linking but the gspawn
# helper binaries are still linked dynamically, forcing us to ship
# all DLLs anyway. Therefore it makes little sense to link SciTECO
@@ -242,20 +247,20 @@ jobs:
- name: Prepare Distribution Directory
env:
- MINGW_BUNDLEDLLS_SEARCH_PATH: /mingw32/bin
+ MINGW_BUNDLEDLLS_SEARCH_PATH: /mingw64/bin
run: |
mkdir temp-bin/
cd temp-bin/
- cp -r /mingw32/bin/{gsciteco.exe,sciteco.exe,grosciteco.tes,tedoc.tes} ./
+ cp -r /mingw64/bin/{gsciteco.exe,sciteco.exe,grosciteco.tes,tedoc.tes} ./
# datadir is relative to bindir
- cp -r /mingw32/bin/{lib,*.tmac} ./
- cp /mingw32/bin/sample.teco_ini .teco_ini
- cp -r /mingw32/share/doc/sciteco/* ./
+ cp -r /mingw64/bin/{lib,*.tmac} ./
+ cp /mingw64/bin/sample.teco_ini .teco_ini
+ cp -r /mingw64/share/doc/sciteco/* ./
cp ../COPYING ../ChangeLog ./
- cp /mingw32/bin/gspawn-win32-helper*.exe ./
+ cp /mingw64/bin/gspawn-win64-helper*.exe ./
# Collect DLLs for all included binaries
for f in *.exe; do ../contrib/mingw-bundledlls --copy $f; done
- zip -9 -r ../sciteco-pdcurses_nightly_win32.zip .
+ zip -9 -r ../sciteco-pdcurses_nightly_win64.zip .
- name: Archive Windows Distribution (PDCurses)
uses: pyTooling/Actions/releaser/composite@v1.0.5
with:
@@ -267,14 +272,14 @@ jobs:
# However the Curses version may be linked statically, while Gtk+3 cannot be
# linked statically on Windows (at least MSYS does not provide
# static libraries) and would draw in libglib, libintl, libiconv etc. anyway.
- win32-gtk:
+ win64-gtk:
runs-on: windows-2019
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
- MSYSTEM: MINGW32
+ MSYSTEM: MINGW64
CHERE_INVOKING: 1
steps:
@@ -291,8 +296,8 @@ jobs:
- name: Install Build Dependencies
run: >
pacman -S --noconfirm --needed
- base-devel mingw-w64-i686-autotools mingw-w64-i686-toolchain
- mingw-w64-i686-glib2 mingw-w64-i686-gtk3 mingw-w64-i686-librsvg
+ base-devel mingw-w64-x86_64-autotools mingw-w64-x86_64-toolchain
+ mingw-w64-x86_64-glib2 mingw-w64-x86_64-gtk3 mingw-w64-x86_64-librsvg
groff
- name: Configure Build
@@ -316,44 +321,44 @@ jobs:
- name: Prepare Distribution Directory
env:
- MINGW_BUNDLEDLLS_SEARCH_PATH: /mingw32/bin
+ MINGW_BUNDLEDLLS_SEARCH_PATH: /mingw64/bin
run: |
mkdir temp-bin
cd temp-bin
- cp /mingw32/bin/{sciteco.exe,grosciteco.tes,tedoc.tes} ./
+ cp /mingw64/bin/{sciteco.exe,grosciteco.tes,tedoc.tes} ./
# datadir is relative to bindir
- cp -r /mingw32/bin/{lib,*.tmac} ./
+ cp -r /mingw64/bin/{lib,*.tmac} ./
# FIXME: Maybe there should be a separate win32/.teco_ini with
# a few pre-enabled settings?
- cp /mingw32/bin/sample.teco_ini .teco_ini
- cp /mingw32/bin/fallback.css .teco_css
- cp -r /mingw32/share/doc/sciteco/* ./
+ cp /mingw64/bin/sample.teco_ini .teco_ini
+ cp /mingw64/bin/fallback.css .teco_css
+ cp -r /mingw64/share/doc/sciteco/* ./
cp ../COPYING ../ChangeLog ./
- cp /mingw32/bin/gspawn-win32-helper*.exe ./
+ cp /mingw64/bin/gspawn-win64-helper*.exe ./
# Collect DLLs for all included binaries
for f in *.exe; do ../contrib/mingw-bundledlls --copy $f; done
#mkdir share
- #cp /mingw32/share/loader.cache share/
- #glib-compile-schemas /mingw32/share/glib-2.0/schemas
+ #cp /mingw64/share/loader.cache share/
+ #glib-compile-schemas /mingw64/share/glib-2.0/schemas
#mkdir -p share/glib-2.0
- #cp /mingw32/share/glib-2.0/gschemas.compiled share/glib-2.0/
+ #cp /mingw64/share/glib-2.0/gschemas.compiled share/glib-2.0/
mkdir -p share/icons/Adwaita
# FIXME: It should be sufficient to package the SVG icons,
# but I cannot get it to work. Perhaps index.theme would have to be tweaked.
# We could also try to include a pure scalable icon theme.
- #cp -r /mingw32/share/icons/Adwaita/{scalable*,index.theme} share/icons/Adwaita/
- cp -r /mingw32/share/icons/Adwaita/* share/icons/Adwaita/
+ #cp -r /mingw64/share/icons/Adwaita/{scalable*,index.theme} share/icons/Adwaita/
+ cp -r /mingw64/share/icons/Adwaita/* share/icons/Adwaita/
gtk-update-icon-cache-3.0 share/icons/Adwaita/
# FIXME: It's possible to change the location of loaders.cache via $GDK_PIXBUF_MODULE_FILE.
# If we did that, we could avoid "reusing" the lib/ directory.
# This is important when somebody changes $SCITECOPATH.
- cp /mingw32/lib/gdk-pixbuf-2.0/2.10.0/loaders/{pixbufloader_svg.dll,libpixbufloader-png.dll} .
+ cp /mingw64/lib/gdk-pixbuf-2.0/2.10.0/loaders/{pixbufloader_svg.dll,libpixbufloader-png.dll} .
# Collect DLLs for all pixbuf loaders into the root directory
for f in *pixbufloader*.dll; do ../contrib/mingw-bundledlls --copy $f; done
mkdir -p lib/gdk-pixbuf-2.0/2.10.0/loaders/
mv *pixbufloader*.dll lib/gdk-pixbuf-2.0/2.10.0/loaders/
cp ../win32/loaders.cache lib/gdk-pixbuf-2.0/2.10.0/
- zip -9 -r ../sciteco-gtk3_nightly_win32.zip .
+ zip -9 -r ../sciteco-gtk3_nightly_win64.zip .
- name: Archive Windows Distribution (GTK+ 3)
uses: pyTooling/Actions/releaser/composite@v1.0.5
with:
diff --git a/AppImage/curses.yml b/AppImage/curses.yml
index edf9fd0..f682cf1 100755
--- a/AppImage/curses.yml
+++ b/AppImage/curses.yml
@@ -21,6 +21,8 @@ script:
- sed -i -e 's@gsciteco@sciteco@g' sciteco-curses.desktop
- echo 'Terminal=true' >>sciteco-curses.desktop
- wget -O sciteco.png -c "https://raw.githubusercontent.com/rhaberkorn/sciteco/master/ico/sciteco-256.png"
+ - mkdir -p ./usr/share/metainfo/
+ - wget -O ./usr/share/metainfo/sciteco-curses.appdata.xml -c "https://raw.githubusercontent.com/rhaberkorn/sciteco/master/AppImage/sciteco-curses.appdata.xml"
# Thinning: These documentation files are pointless.
# SciTECO comes with its own online help system.
- rm -rf ./usr/share/doc ./usr/share/man
diff --git a/AppImage/gtk.yml b/AppImage/gtk.yml
index ea233db..a8f19ba 100755
--- a/AppImage/gtk.yml
+++ b/AppImage/gtk.yml
@@ -23,6 +23,8 @@ script:
- mv ./usr/share/applications/sciteco.desktop ./sciteco-gtk.desktop
- cp ./usr/share/icons/hicolor/256x256/apps/sciteco.png ./sciteco.png
- rm -rf ./usr/share/icons
+ - mkdir -p ./usr/share/metainfo/
+ - wget -O ./usr/share/metainfo/sciteco-gtk.appdata.xml -c "https://raw.githubusercontent.com/rhaberkorn/sciteco/master/AppImage/sciteco-gtk.appdata.xml"
# Thinning: These documentation files are pointless.
# SciTECO comes with its own online help system.
- rm -rf ./usr/share/doc ./usr/share/man
diff --git a/AppImage/sciteco-curses.appdata.xml b/AppImage/sciteco-curses.appdata.xml
new file mode 100644
index 0000000..fb114fe
--- /dev/null
+++ b/AppImage/sciteco-curses.appdata.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>sciteco-curses</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0+</project_license>
+ <name>SciTECO</name>
+ <summary>Scintilla-based Text Editor and Corrector (Curses UI)</summary>
+ <description><p>
+ SciTECO is an interactive TECO dialect, similar to Video TECO.
+ It also adds features from classic Standard TECO-11,
+ and incorporates many unique new ideas.
+ It is geared towards UNIX-like operating systems but also natively
+ supports Microsoft Windows NT.
+ </p><p>
+ This app contains the curses (terminal) version of SciTECO.
+ </p></description>
+ <launchable type="desktop-id">sciteco-curses.desktop</launchable>
+ <url type="homepage">https://rhaberkorn.github.io/sciteco/</url>
+ <screenshots>
+ <screenshot type="default">
+ <image>https://sciteco.sf.net/screenshots/v2.1.0-freebsd-ncurses.png</image>
+ </screenshot>
+ </screenshots>
+ <provides>
+ <id>sciteco-curses.desktop</id>
+ </provides>
+</component> \ No newline at end of file
diff --git a/AppImage/sciteco-gtk.appdata.xml b/AppImage/sciteco-gtk.appdata.xml
new file mode 100644
index 0000000..59e17d7
--- /dev/null
+++ b/AppImage/sciteco-gtk.appdata.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>sciteco-gtk</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0+</project_license>
+ <name>SciTECO</name>
+ <summary>Scintilla-based Text Editor and Corrector (GTK 3 UI)</summary>
+ <description><p>
+ SciTECO is an interactive TECO dialect, similar to Video TECO.
+ It also adds features from classic Standard TECO-11,
+ and incorporates many unique new ideas.
+ It is geared towards UNIX-like operating systems but also natively
+ supports Microsoft Windows NT.
+ </p><p>
+ This app contains the GTK+ 3 (graphical) version of SciTECO.
+ </p></description>
+ <launchable type="desktop-id">sciteco-gtk.desktop</launchable>
+ <url type="homepage">https://rhaberkorn.github.io/sciteco/</url>
+ <screenshots>
+ <screenshot type="default">
+ <image>https://sciteco.sf.net/screenshots/v2.3.0-freebsd-gtk.png</image>
+ </screenshot>
+ </screenshots>
+ <provides>
+ <id>sciteco-gtk.desktop</id>
+ </provides>
+</component> \ No newline at end of file
diff --git a/INSTALL b/INSTALL
index 0b497eb..9a46b30 100644
--- a/INSTALL
+++ b/INSTALL
@@ -3,7 +3,7 @@ Installation Instructions
Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
2006, 2007, 2008, 2009 Free Software Foundation, Inc.
-Copyright (C) 2013-2024 Robin Haberkorn
+Copyright (C) 2013-2025 Robin Haberkorn
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
diff --git a/NEWS b/NEWS
index 4a69a86..ca67bb2 100644
--- a/NEWS
+++ b/NEWS
@@ -2,9 +2,11 @@ News
====
<span class="nf nf-md-new_box"></span>
+There is now an [Alpine Linux package](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/sciteco)
+thanks to user Celeste.
+
+<span class="nf nf-md-new_box"></span>
FreeBSD is the first operating system that adds an official [port/package for SciTECO](https://www.freshports.org/editors/sciteco/).
-But you will probably have to wait a few months until you can just `pkg install sciteco-curses`
-if you are following the quarterly branches.
<span class="nf nf-md-new_box"></span>
-Join our new [IRC chatroom](https://web.libera.chat/#sciteco): #sciteco at irc.libera.chat
+Join our [IRC chatroom](https://web.libera.chat/#sciteco): #sciteco at irc.libera.chat
diff --git a/README b/README
index 696ce5d..0676430 100644
--- a/README
+++ b/README
@@ -44,19 +44,22 @@ master branch and between releases until version 3.0 is released.__
Features
========
-* Supports most of the [Video TECO](http://www.copters.com/teco.html) commands
+* Supports most of the [Video TECO](http://www.copters.com/teco.html) commands.
* Improved parser compared to classic TECOs, making SciTECO
more similar to other imperative languages.
* Operator precedence in arithmetic expressions and an argument stack that may be modified
by the user (commands may have more than two arguments)
* Extended Q-Register namespace (arbitrary strings): Can be used to build libraries and
can be abused as a data structure.
+* Quick access to the system clipboard via special `~` registers, even on ncurses!
* Make use of your keyboard's function keys by assigning them to strings inserted into
the command stream.
This also enables navigating with function keys (e.g. cursor keys) as demonstrated
by the standard library `fnkeys.tes`.
In fact, all keys with printable representation and control keys can be remapped using
key macros - and they can be context-sensitive as well!
+* There is scriptable mouse support via the key macro mechanism (see also `fnkeys.tes`).
+ Autocompletion popups can also be scrolled and clicked.
* Many TECO-11 features, like that most commands have a colon-modified form, string-building
characters, exotic match characters...
* Interactivity: Immediate searching (similar to search-as-you-type) and
@@ -118,6 +121,7 @@ There are prebuilt binary packages and source bundles for your convenience:
* [FreeBSD port](https://www.freshports.org/editors/sciteco/)
* [Ubuntu PPA](https://launchpad.net/~robin-haberkorn/+archive/sciteco)
* [Arch User Repository](https://aur.archlinux.org/packages/sciteco-git)
+* [Alpine Linux package](https://pkgs.alpinelinux.org/package/edge/testing/x86_64/sciteco)
* [Chocolatey package](https://community.chocolatey.org/packages/SciTECO)
for Windows users.
* Yocto/OpenEmbedded users should try the
diff --git a/TODO b/TODO
index 6801ee6..08b195c 100644
--- a/TODO
+++ b/TODO
@@ -4,9 +4,6 @@ Tasks:
"edit" hook.
Known Bugs:
- * Solaris/OmniOS: There are groff build errors.
- * Gtk sometimes allows scrolling with the mouse when it shouldn't.
- All mouse events should currently be blocked.
* PDCurses/WinGUI: There is still some flickering, but it got better
since key macros update the command line only once.
Could already be fixed upstream, see:
@@ -42,6 +39,11 @@ Known Bugs:
We need something based on a non-backtracking Thompson's NFA with Unicode (UTF-8), see
https://swtch.com/~rsc/regexp/
Basically only RE2 would check all the boxes.
+ RE2 doesn't have a native C API, so we would also have to import the
+ https://github.com/marcomaggi/cre2/ wrapper.
+ re2 should be an optional dependency, so we can still build against the
+ glib APIs.
+ Optionally, I could build a PCRE-compatible wrapper for Rust's regex crate.
* It is still possible to crash SciTECO using recursive functions,
since they map to the C program's call stack.
It is perhaps best to use another stack of
@@ -137,7 +139,12 @@ Known Bugs:
commands for ALL of the Scintilla messages involved.
Automatically running EF EB...$ in the "save" hook could
also have unwanted side effects.
+ * Solaris/OmniOS: There are groff build errors.
* session.vcs does not properly work in MSYS2 environments.
+ * The Windows GTK version no longer works under Wine:
+ "Failed to translate keypress (keycode: 88) for group 0 (00000409) because we could not load the layout."
+ Also, all Windows builds have problems executing ECdir$ (under Wine!).
+ See also https://github.com/fontforge/fontforge/issues/5031#issuecomment-1143098230
* At least the GTK version with --xembed is prone to unexpected
crashes. Interestingly, while this does leave orphaned savepoint
files around, it does not produce a core dump.
@@ -230,12 +237,6 @@ Features:
keys and Alt and Ctrl modifiers.
See also https://stackoverflow.com/questions/31379824/how-to-get-control-characters-for-ctrlleft-from-terminfo-in-zsh
https://gist.github.com/rkumar/1237091
- * Mouse support. Not that hard to implement. Mouse events
- use a pseudo key macro as in Curses.
- Using some special command, macros can query the current
- mouse state (this maps to an Interface method).
- This should be configurable via an ED flag as it changes
- the behavior of the terminal.
* Support loading from stdin (--stdin) and writing to
the current buffer to stdout on exit (--stdout).
This will make it easy to write command line filters,
@@ -341,7 +342,7 @@ Features:
Perhaps blinking or invisible?
The problem is, this won't work so easily once we use a Scintilla
minibuffer everywhere.
- Gtk could at the very least use the hourglass cursor.
+ Gtk already sets the "wait" cursor when busy.
* Command to free Q-Register (remove from table).
e.g. FQ (free Q). :FQ could free by QRegister prefix name for
the common use case of Q-Register subtables and lists.
@@ -453,30 +454,20 @@ Features:
special. Even if we always increased the nest_level, that
variable does not discern Ifs and Whiles.
* Possible Nightly Build improvements (and therefore releases):
- * Build 32-bit Ubuntu packages
* Push nightly builds into the Ubuntu PPA.
We should probably create a new PPA sciteco-nightly.
A new private key has already been registered on Launchpad and
Github. We just need to integrate with CI.
See also https://github.com/marketplace/actions/import-gpg
- * 64-bit Windows builds
* Mac OS Arm64 builds either separately or via universal binary.
See https://codetinkering.com/switch-homebrew-arm-x86/
Target flag: `-target arm64-apple-macos11`
- * Get into appimage.github.io and AppImageHub.
- See https://github.com/AppImage/appimage.github.io/pull/3402
+ * A pkgsrc port could be based on the FreeBSD port and would benefit
+ NetBSD, Mac OS, but can also be used on practically all other
+ UNIX-like platforms.
* Get into mentors.debian.net. First step to being adopted
into the Debian repositories.
* Get meta-rhaberkorn into https://layers.openembedded.org
- * sample.teco_ini: Support opening files on certain lines
- (filename:line).
- Theoretically, this could also be added to the <EB> syntax,
- although the colon character is allowed in filenames under Windows.
- In principe there is little need for that in interactive mode,
- but it would ease opening filenames copied from compiler errors
- or grep -n results.
- Other editors use the +line[,col] syntax (see nano).
- * <:EF> for saving and closing a buffer, similar to <:EX>.
* Bash completions.
* FreeBSD: rctl(8) theoretically allows setting up per-process actions
when exceeding the memory limit.
@@ -484,9 +475,13 @@ Features:
* Auto-completions customization via external programs.
This among other things could be used to integrate LSPs-driven
autocompletions.
- * Whereever we take buffer positions (nJ; n,mD; nQ...),
+ * Wherever we take buffer positions (nJ; n,mD; nQ...),
negative numbers could refer to the end of the buffer or
Q-Register string.
+ * Wherever we take a buffer range (e.g. n,mD), we could relax
+ the requirement that n < m and automatically sort the indexes.
+ In this case, right-click+drag would no longer have to sort the buffer
+ pointers.
* Support extended operators like in TECO-64:
https://github.com/fpjohnston/TECO-64/blob/master/doc/oper.md
However, instead of introducing a separate parser state, better
@@ -541,6 +536,7 @@ Features:
There are two ways this could be implemented:
* Either all sorts of commands automatically iconv
from/to the configured encoding.
+ This would be very difficult and inefficient.
* Or we iconv once to UTF-8 when loading the file
and iconv back when saving.
This is probably easier but means, you have to
@@ -549,6 +545,10 @@ Features:
We could say that nEB...$ specifies the code page
if the string argument is nonempty.
On the other hand, iconv uses symbolic identifiers.
+ Perhaps there should be FBfilename$codepage$ and EEcodepage$ commands
+ or an "EE" Q-Register.
+ Unfortunately, glib or POSIX iconv() doesn't return a list of
+ supported codepages, that could be used for auto-completions.
* Perhaps the Unicode "icons" should be configurable via TECO.
In the easiest case there could simply be 2 Q-Reg namespaces:
^F... for filenames and ^E... for extensions.
@@ -584,9 +584,20 @@ Features:
syntax highlighted.
I am not sure however how that could be done without a special
ED hook.
+ * SciTECO syntax highlighting improvements:
+ * The { } escapes should be styled as SCE_SCITECO_OPERATOR.
+ * There should be two SCE_SCITECO_STRING[12] styles and second
+ string arguments could be styled SCE_SCITECO_STRING2.
+ * Alternatively, string building constructs could be styled with the
+ alternate string style.
+ * Erroneous constructs could be highlighted up to the previous
+ start state. E.g. when redefining labels, the entire label would be
+ highlighted.
* Instead of defaulting to nothing in the absence of ~/.teco_ini,
we should load the installed sample.teco_ini, which
gives a more user-friendly experience.
+ Or perhaps even simpler, should the profile be missing, just log
+ a warning on startup.
* NLS (Native Language Support). I could at least add German and Russian.
There aren't many localizable strings in SciTECO.
Should be optional.
@@ -649,6 +660,11 @@ Optimizations:
On the other hand, this imports tons of sh*t into the
repository and chains us to Autotools.
* Does it make sense to import glib-2.0.m4?
+ * MinGW might now support weak symbols which would be useful
+ in interface.c to provide some default functions and avoid
+ a little bit of preprocessor madness in the implementations.
+ However, weak symbols might still be unsupported on other
+ potential targets.
* According to ChatGPT (sic) the glibc and jemalloc malloc_usable_size()
do not change during the lifetime of an object,
although this is an implementation detail.
@@ -661,6 +677,18 @@ Optimizations:
If this turns out to be useful, perhaps we can automatically
upload builds via CI?
https://scan.coverity.com/projects/rhaberkorn-sciteco
+ * <1;> and similar commands could be sped up if we cached the loop
+ end PC in teco_loop_context_t.
+ * Instead of introducing a streaming byte code compiler with all
+ of its consequences and complications, we could translate
+ macros into special strings where all whitespace characters,
+ comments and labels are stripped.
+ Every parser input byte has a second byte/word which stores
+ the length of the symbol (including stripped bytes) in the source
+ macro. This information is used to retrieve source macro locations
+ in case of errors.
+ Also, this "hint" word could cache PCs of <|> and <'>.
+ On the downside, this scheme does not allow all kinds of optimizations.
Documentation:
* Doxygen docs could be deployed on Github pages
diff --git a/configure.ac b/configure.ac
index ad4d805..69e98e8 100644
--- a/configure.ac
+++ b/configure.ac
@@ -173,7 +173,7 @@ 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 fchown dup dup2 getpid open read kill mmap], , [
+ AC_CHECK_FUNCS([realpath readlink pathconf fchown dup dup2 getpid open read kill mmap popen pclose], , [
AC_MSG_ERROR([Missing libc function])
])
AC_SEARCH_LIBS(dladdr, [dl], , [
@@ -335,6 +335,10 @@ case $INTERFACE in
AC_DEFINE(PDC_FORCE_UTF8, 1, [PDCursesMod forces use of UTF8])
])
+ AC_CHECK_FUNC([has_mouse], [
+ AC_DEFINE(PDC_NCMOUSE, 1, [PDCurses built with ncurses mouse API])
+ ])
+
# This is detectable at runtime on PDCursesMod using PDC_get_version().
# "Classic" PDCurses however does not allow runtime or compile-time checks for
# discerning e.g. WinCon from SDL.
@@ -356,8 +360,7 @@ case $INTERFACE in
;;
gtk)
- # gmodule is required by Scintilla.
- PKG_CHECK_MODULES(LIBGTK, [gtk+-3.0 >= 3.12 gmodule-2.0], [
+ PKG_CHECK_MODULES(LIBGTK, [gtk+-3.0 >= 3.12], [
CFLAGS="$CFLAGS $LIBGTK_CFLAGS"
CXXFLAGS="$CXXFLAGS $LIBGTK_CFLAGS"
LIBS="$LIBS $LIBGTK_LIBS"
diff --git a/contrib/scinterm b/contrib/scinterm
-Subproject ba8c35f9b9f58e394d3ece79c464f6cd020c765
+Subproject 430822df945b51ce1ed3026047fe8967a18d1ff
diff --git a/debian/control b/debian/control
index a5aeca0..e1d67f8 100644
--- a/debian/control
+++ b/debian/control
@@ -7,7 +7,7 @@ Build-Depends: debhelper (>= 10), dh-exec, g++ (>= 4:5.0), libglib2.0-dev (>= 2.
libgtk-3-dev (>= 3.12), xvfb,
groff-base
Standards-Version: 4.5.0
-Homepage: http://sciteco.sf.net/
+Homepage: https://rhaberkorn.github.io/sciteco/
Vcs-Browser: https://github.com/rhaberkorn/sciteco
Vcs-Git: git://github.com/rhaberkorn/sciteco.git
diff --git a/debian/copyright b/debian/copyright
index b03b1b0..83c46ac 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -4,7 +4,7 @@ Upstream-Contact: robin.haberkorn@googlemail.com
Source: https://github.com/rhaberkorn/sciteco
Files: *
-Copyright: Copyright 2012-2024 Robin Haberkorn <robin.haberkorn@googlemail.com>
+Copyright: Copyright 2012-2025 Robin Haberkorn <robin.haberkorn@googlemail.com>
License: GPL-3
/usr/share/common-licenses/GPL-3
diff --git a/debian/rules b/debian/rules
index 8b8ad11..1808b15 100755
--- a/debian/rules
+++ b/debian/rules
@@ -41,7 +41,7 @@ build-curses-stamp:
--with-interface=ncurses \
--with-scitecodatadir=../share/sciteco
dh_auto_build -Bbuild-curses
- dh_auto_test -Bbuild-curses
+ dh_auto_test -Bbuild-curses --no-parallel
touch $@
# NOTE: This does not depend on install-curses-stamp and uses
@@ -57,7 +57,7 @@ build-gtk-stamp:
# we need an XServer which may be missing on the build server.
# That's why we use xvfb.
xvfb-run -a dh_auto_build -Bbuild-gtk
- xvfb-run -a dh_auto_test -Bbuild-gtk
+ xvfb-run -a dh_auto_test -Bbuild-gtk --no-parallel
touch $@
install:
diff --git a/distribute.mk.in b/distribute.mk.in
index 4eb9b91..0a82b2c 100644
--- a/distribute.mk.in
+++ b/distribute.mk.in
@@ -85,6 +85,20 @@ ppa : debian-source
dput ppa:robin-haberkorn/sciteco *.changes; \
)
+# This is for testing FreeBSD port builds with Poudriere.
+# We cannot build binary packages this way and there is little
+# sense in distributing them.
+#
+# NOTE: This assumes that a fitting jail and ports tree has already
+# been set up.
+# We do nothing to sync the ports tree with our mirror of
+# the FreeBSD port's Makefile (freebsd/Makefile) or to supply Poudriere
+# with a recent tarball of SciTECO.
+POUDRIERE_JAIL ?= 141amd64
+poudriere:
+ poudriere testport -j $(POUDRIERE_JAIL) -o editors/sciteco@curses
+ poudriere testport -j $(POUDRIERE_JAIL) -o editors/sciteco@gtk
+
# Create Windows release.
# Assumes a correctly installed glib (with pkgconfig script)
# and static linking of all dependant libraries (no DLLs are
diff --git a/doc/cheat-sheet.mm b/doc/cheat-sheet.mm
index 28ecad9..ab404e3 100644
--- a/doc/cheat-sheet.mm
+++ b/doc/cheat-sheet.mm
@@ -31,6 +31,9 @@ A full language description can be found in
.\" The $ is added to standardize the height of all boxes
.\" (as it stretches above and below the base line).
.\" NOTE: This does not work in arguments to .TD!
+.\" NOTE: It would ne nice to round the corners (as in Scintilla/Gtk's
+.\" rendition of character representations), but there are no filled
+.\" rounded polygons in Groff.
.ds FILLSTR \
\R!@wd \w'\\$3$'-\w'$'!\
\h'.1m'\
@@ -102,7 +105,7 @@ They are case-insensitive.
. I n
. TD
Some integer, often optional (1 or 0 by default).
-You can write \fC-\fP instead of \fC-1\fP.
+You can write \fC\-\fP instead of \fC\-1\fP.
. TR bgc=grey90
. TD
. I text
@@ -126,7 +129,7 @@ Line Feed, i.e. Enter/Return key
. TR bgc=grey90
. TD colspan=4
For instance:
-\fC-C\fP \(== \fC-1C\fP \(== \fCR\fP \(DI
+\fC\-C\fP \(== \fC\-1C\fP \(== \fCR\fP \(DI
\fCQa\fP \(== \fCQ[A]\fP \(DI
\fCQ#ab\fP \(== \fCQ[AB]\fP \(DI
Ctrl+I \(== \*[CTRL ^I] \(== \*[CTRL TAB] \(DI
@@ -155,7 +158,7 @@ Redo (Rubin): First \*[CTRL ^G], then Backspace, \*[CTRL ^W]...
Exit, but only if no buffer is \(lqdirty\(rq (unsaved)
. TRX
. TD
-\fC-EX\fP\*($$
+\fC\-EX\fP\*($$
. TD
Exit even if buffer is \(lqdirty\(rq, i.e. discarding all unsaved changes.
. TRX
@@ -168,10 +171,10 @@ Exit, saving all \(lqdirty\(rq buffers.
.TBLX "Files" width='30% 70%'
. TRX
. TD
-\fCEB\fIfile\fR\*$
+\fCEB\fI\^file\^\fR\*$
. TD
Edit buffer or open new \fIfile\fP (glob pattern).
-Files, that did not exist on disk, will not be created until you save them.
+\#Files, that did not exist on disk, will not be created until you save them.
. TRX
. TD
\fCEB*.c\*$
@@ -187,7 +190,7 @@ Edit the unnamed buffer.
. CI 0EB
. TD
Show buffer ring/list.
-You can specify a filename afterwards to open a file.
+You can type or click a filename afterwards.
. TRX
. TD
\fIn\fCEB\fR\*$
@@ -202,9 +205,9 @@ Select \fIn\fP-th buffer in ring.
Select next buffer in ring.
. TRX
. TD
-\fC-%*\*$
+\fC\-%*\*$
. TD
-Select pevious buffer in ring.
+Select previous buffer in ring.
. TRX
. TD
. CI EJU*
@@ -218,7 +221,7 @@ Write (save) current buffer under its current name.
Does not work on the unnamed buffer.
. TRX
. TD
-\fCEW\fIfile\fR\*$
+\fCEW\fI\^file\^\*$
. TD
Save current buffer under new name \fIfile\fP (Save As).
. TRX
@@ -228,12 +231,17 @@ Save current buffer under new name \fIfile\fP (Save As).
Finish (close) current buffer.
. TRX
. TD
-. CI -EF
+. CI \-EF
. TD
Finish (close) current buffer, discarding all unsaved changes.
. TRX
. TD
-\fCFG\fIpath\fR\*$
+. CI :EF
+. TD
+Finish (close) current buffer, saving it if it is \(lqdirty\(rq.
+. TRX
+. TD
+\fCFG\fI\^path\^\fR\*$
. TD
Go to folder \fIpath\fP, i.e. change working directory.
. TRX
@@ -243,7 +251,7 @@ Go to folder \fIpath\fP, i.e. change working directory.
Set single byte ASCII mode.
. TRX
. TD colspan=2
-\fBTip:\fP You can use the Tab-key for autocompleting filenames and paths.
+\fBTip:\fP You can use the Tab-key and mouse for autocompleting filenames and paths.
.ETB
.
.NCOL
@@ -251,7 +259,7 @@ Set single byte ASCII mode.
.TBLX "Text Insertion" width='30% 70%'
. TRX
. TD
-\fCI\fItext\*$
+\fCI\fItext\^\*$
. TD
Insert \fItext\fP into buffer.
. TRX
@@ -266,11 +274,11 @@ Insert single caret (\fC^\fP).
Insert \*$ (ASCII 27).
. TRX
. TD
-\*[CTRL TAB]\fItext\*$
+\*[CTRL TAB]\fI\^text\^\*$
. TD
Insert \fItext\fP with leading tab/indentation.
See also
-.pdfhref W -D https://github.com/rhaberkorn/sciteco/wiki/Useful-Macros#indent-code-block -A . \fIn\fCM#it\fP
+.pdfhref W -D https://github.com/rhaberkorn/sciteco/wiki/Useful-Macros#indent-code-block -A . \fIn\^\fCM#it\fP
.ETB
.
.TBLX "Text Deletion" width='30% 70%'
@@ -353,7 +361,7 @@ Copy next \fIn\fP lines into Q-Register \fIq\fP.
Append next \fIn\fP lines to Q-Register \fIq\fP.
. TRX
. TD
-. CI "" n X q\|n K
+. CI "" n @X q
. TD
Cut next \fIn\fP lines into Q-Register \fIq\fP.
. TRX
@@ -365,8 +373,14 @@ Copy whole buffer into Q-Register \fIq\fP.
. TD
. CI X\(ti
. TD
-Copy line into clipboard. See also
-.pdfhref W -D https://github.com/rhaberkorn/sciteco/wiki/Useful-Macros#copypaste-from-clipboard -A . \fCM#xc\fP
+Copy line into clipboard.
+.ig END
+. TRX
+. TD
+. CI G\(ti
+. TD
+Paste from clipboard.
+.END
. TRX
. TD
. CI G q
@@ -374,19 +388,19 @@ Copy line into clipboard. See also
Get (paste) Q-Register \fIq\fP at current position.
. TRX
. TD
-\fCI\fI...\*[CTRL ^E]\fCQ\fIq
+\fCI\fI...\*[CTRL ^E]\fCQ\fI\^q
. TD
Paste Q-Register \fIq\fP while inserting text.
.ig END
. TRX
. TD
-\fCE%\fIq\|file\*$
+\fCE%\fIq\|file\^\*$
. TD
Save Q-Register \fIq\fP into \fIfile\fP.
.END
. TRX
. TD
-\fCEQ\fIq\*$
+\fCEQ\fI\^q\^\*$
. TD
Edit Q-Register \fIq\fP as a text buffer.
.ig END
@@ -469,23 +483,23 @@ Jump to end of buffer.
. TRX
. TD colspan=2
\fBTip:\fP Enable the \fCfnkeys.tes\fP module in \fC.teco_ini\fP
-to move around with cursor keys!
+to move around with cursor keys and mouse!
.ETB
.
.TBLX "External Programs" width='42% 58%'
. TRX
. TD
-\fCEC\fIcommand\*$
+\fCEC\fI\^command\^\*$
. TD
Insert output of \fIcommand\fP.
. TRX
. TD
-\fIn\fCEC\fIcommand\*$
+\fIn\fCEC\fI\^command\^\*$
. TD
Filter next \fIn\fP lines through \fIcommand\fP.
. TRX
. TD
-\fCHEC\fIcommand\*$
+\fCHEC\fI\^command\^\*$
. TD
Filter whole buffer through \fIcommand\fP.
. TRX
@@ -498,12 +512,12 @@ Sort next \fIn\fP lines (UNIX).
.TBLX "Macros" width='42% 58%'
. TRX
. TD
-\fC@\*[CTRL ^U]\fIq\fP{\fImacro\fP}
+\fC@\*[CTRL ^U]\fI\^q\fP{\fImacro\fP}
. TD
Define \fImacro\fP in Q-Register \fIq\fP.
. TRX
. TD
-. CI M q
+. CI M \^q
. TD
Call macro in Q-Register \fIq\fP.
. TRX
@@ -520,20 +534,25 @@ Discard command-line, storing it in \fIq\fP.
.
.MC (u;(\nW-0.5c)/3) 0.25c \" 3 columns
.
-.TBLX "Search & Replace" width='45% 55%'
+.TBLX "Search & Replace" width='47% 53%'
. TRX
. TD
-\fCS\fItext\*$
+\fCS\fItext\^\*$
. TD
Search for next occurrence of \fItext\fP.
. TRX
. TD
-\fC-S\fItext\*$
+\fCS\fItext\^\*[CTRL $^S]\fCC
+. TD
+Search for beginning of \fItext\fP.
+. TRX
+. TD
+\fC\-S\fItext\^\*$
. TD
Search for previous occurrence of \fItext\fP.
. TRX
. TD
-\fIn\fCS\fItext\*$
+\fIn\fCS\fItext\^\*$
. TD
Search for \fIn\fP-th occurrence of \fItext\fP.
. TRX
@@ -543,37 +562,37 @@ Search for \fIn\fP-th occurrence of \fItext\fP.
Repeat last search (pattern from Q-Register \fC_\fP).
. TRX
. TD
-\fCN\fItext\*$
+\fCN\fItext\^\*$
. TD
Search for next occurrence of \fItext\fP across all buffers.
. TRX
. TD
-\fCFR\fIfrom\*$\fIto\*$
+\fCFR\fI\^from\^\*$\fI\^to\^\*$
. TD
Find next occurrence of \fIfrom\fP and replace it with \fIto\fP.
. TRX
. TD
\fCFR\*($$
. TD
-Repeat the last search-replace operation (\fC_\fP and \fC-\fP).
+Repeat the last search-replace operation (\fC_\fP and \fC\-\fP).
. TRX
. TD
-\fC<FR\fIfrom\*$\fIto\*$\fC;>\fP
+\fC<FR\fI\^from\^\*$\fI\^to\^\*$\fC;>\fP
. TD
Find and replace all occurrences in buffer beginning at current position.
. TRX
. TD
-\fCFK\fItext\*$
+\fCFK\fItext\^\*$
. TD
Find and kill (delete) up to first occurrence of \fItext\fP.
. TRX
. TD
-\fCFD\fItext\*$
+\fCFD\fItext\^\*$
. TD
Find and delete first occurrence of \fItext\fP.
.ETB
.
-.TBLX "Control Flow" width='45% 55%'
+.TBLX "Control Flow" width='47% 53%'
. TRX
. TD
. CI < commands >
@@ -595,10 +614,10 @@ For instance, to add \fC#\fP in front of the next 10 lines:
\fC0L10<I#\*$L>\fP
.ETB
.
-.TBLX "Help" width='45% 55%'
+.TBLX "Help" width='47% 53%'
. TRX
. TD
-\fC?\fItopic\*$
+\fC?\fItopic\^\*$
. TD
Search help for \fItopic\fP (may be command).
.ig END \" not yet supported
@@ -610,7 +629,7 @@ Search help by word at current position in buffer.
.END
. TRX
. TD colspan=2
-\fBTip:\fP You can use the Tab-key for autocompleting topics.
+\fBTip:\fP You can use the Tab-key and mouse for autocompleting topics.
.ETB
.
.NCOL
@@ -638,17 +657,17 @@ Matches any alphabetic characters.
Matches any digit.
. TRX
. TD
-\*[CTRL ^N]\fIclass\fP
+\*[CTRL ^N]\fI\^class\fP
. TD
Matches any character not in \fIclass\fP.
. TRX
. TD
-\*[CTRL ^E]\fCM\fIpattern\fP
+\*[CTRL ^E]\fCM\fI\^pattern\fP
. TD
Matches many occurrences of \fIpattern\fP.
. TRX
. TD
-\*[CTRL ^E]\fCG\fIq\fP
+\*[CTRL ^E]\fCG\fI\^q\fP
. TD
Matches any character in Q-Register \fIq\fP.
. TRX
@@ -660,13 +679,13 @@ Matches \fIp\*<1\*>\fP or \fIp\*<2\*>\fP.
. TD colspan=2
To remove all trailing whitespace characters, you could type:
.br
-\fCJ<FR\*[CTRL LF^E]M\*[CTRL ^E][\0,\*[CTRL TAB]]\*[CTRL $LF$];>
+\fCJ<FR\*[CTRL ^E]M\*[CTRL ^E][\0,\*[CTRL TAB]]\*[CTRL LF$LF$];>
.ETB
.
.TBLX "String Building" width='40% 60%'
. TRX
. TD
-\*[CTRL ^E]\fCQ\fIq
+\*[CTRL ^E]\fCQ\fI\^q
. TD
Expand to string contents of Q-Register \fIq\fP.
. TRX
@@ -681,7 +700,7 @@ Expand to integer contents of Q-Register \fIq\fP.
Expand to character represented by codepoint in Q-Register \fIq\fP.
. TRX
. TD
-\*[CTRL ^Q]\fIx
+\*[CTRL ^Q]\fI\^x
. TD
Quote (escape) the following character \fIx\fP.
. TRX
@@ -742,12 +761,12 @@ Also try Shift+Delete if \fCfnkeys.tes\fP is loaded.
Assign number \fIn\fP to Q-Register \fIq\fP.
. TRX
. TD
-. CI -U q
+. CI \-U q
. TD
Assign -1 to Q-Register \fIq\fP.
. TRX
. TD
-. CI Q q
+. CI Q \^q
. TD
Query (get) integer from Q-Register \fIq\fP.
. TRX
@@ -762,12 +781,12 @@ Add \fIn\fP to Q-Register \fIq\fP and return new value.
Increase Q-Register \fIq\fP and return new value.
. TRX
. TD
-. CI -% q
+. CI \-% q
. TD
Decrease Q-Register \fIq\fP and return new value.
. TRX
. TD
-\*[CTRL ^^]\fIx
+\*[CTRL ^^]\fI\^x
. TD
Codepoint of character \fIx\fP.
. TRX
@@ -792,7 +811,7 @@ Insert integer \fIn\fP into buffer at current position.
Add \fIn\fP to number at current position in buffer.
. TRX
. TD
-\fIn\*[CTRL ^_]
+\fIn\^\*[CTRL ^_]
. TD
Binary negate \fIn\fP \(em negate TECO boolean.
. TRX
@@ -807,7 +826,7 @@ These are independent.
Setting a number does not change the string part!
.ETB
.
-.TBLX "Syntax Highlighting (lexers.tes)" width='60% 40%'
+.TBLX "Syntax Highlighting (lexers.tes)" width='61% 39%'
. TRX
. TD
. CI M[lexer.set. name ]
@@ -815,7 +834,7 @@ Setting a number does not change the string part!
Set lexer (syntax highlighting) for language \fIname\fP.
. TRX
. TD colspan=2
-\fBTip:\fP You can use the Tab-key for autocompleting long Q-Register
+\fBTip:\fP You can use the Tab-key and mouse for autocompleting long Q-Register
names (and therefore Lexer names).
.ETB
\# EOF \ No newline at end of file
diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in
index 2b3d3bf..b7084c5 100644
--- a/doc/sciteco.1.in
+++ b/doc/sciteco.1.in
@@ -21,7 +21,7 @@ Scintilla-based \fBT\fPext \fBE\fPditor and \fBCO\fPrrector
.OP "--no-profile"
.OP "-8|--8bit"
.RI [ "UI option .\|.\|." ]
-.OP "--"
+.OP "--|-S"
.RI [ script ]
.RI [ "argument .\|.\|." ]
.YS
@@ -86,7 +86,9 @@ In any case the current buffer position (called
.IR dot )
is left at the beginning of the buffer.
Optionally \(lq\-\-\(rq might be used to explicitly separate \*(ST options and
-macro arguments.
+macro arguments, but is never passed down as a macro argument.
+Since it's sometimes useful to pass down \(rq\-\-\(rq to the profile macro,
+you can use \(lq\-S\(rq, which is equivalent to \(lq\-\- \-\-\(rq.
.
.LP
If the munged macro does not request program termination using the
@@ -155,7 +157,14 @@ the first non-option argument.
The profile will usually set up various Scintilla and \*(ST options,
configure syntax highlighting,
define commonly used macros and open files specified as arguments to \*(ST.
-It will usually leave the editor in interactive mode.
+The recommended command-line processor from \(lqopener.tes\(rq allows
+opening files on specific lines or lines and columns
+(\fB+\fIline\fR[\fB,\fIcolumn\fR] \fIfilename\fP or
+\fIfilename\fB:\fIline\fR[\fB:\fIcolumn\fR][\fB:\fR]).
+This special syntax can be inhibited by prefixing the files names with the
+special \(lq\-S\(rq separator.
+.LP
+The profile macro will usually leave the editor in interactive mode.
Profile execution can be suppressed with the
.B \-\-no\-profile
option.
@@ -289,12 +298,6 @@ on Windows.
On all other platforms (including UNIX/Linux) this variable
defaults to the standard library installation path at
.BR "@scitecolibdir@" .
-.TP
-.SCITECO_TOPIC "$SCITECO_SCINTILLUA_LEXERS"
-.B SCITECO_SCINTILLUA_LEXERS
-The Scintillua \(lqlexers/\(rq directory.
-This is passed as the \(lqscintillua.lexers\(rq library property when
-loading a Scintillua lexer via the \fBSCI_SETILEXER\fP Scintilla message.
.
.LP
The \fBHOME\fP, \fBSCITECOCONFIG\fP and \fBSCITECOPATH\fP environment
@@ -321,9 +324,40 @@ environment before initializing Curses, so these variables
can be modified in the profile macro.
.
.LP
+.SCITECO_TOPIC "$SCITECO_CLIPBOARD_SET" "$SCITECO_CLIPBOARD_GET"
+On ncurses, in addition to the OSC-52 protocol, you can use external
+processes to drive the built-in clipboard Q-Registers (\(lq~\(rq and so on).
+For that you can set the \fBSCITECO_CLIPBOARD_SET\fP and \fBSCITECO_CLIPBOARD_GET\fP
+environment variables or their corresponding Q-Registers to shell commands,
+that receive the clipboard contents on stdin or output the requested clipboard on stdout.
+In the configured commands, the string \(lq{}\(rq is replaced with a single
+letter code of the clipboard to set:
+\(lqc\(rq, \(lqp\(rq or \(lqs\(rq as in the clipboard register names.
+The given commands will always be executed by \fB/bin/sh\fP, regardless of
+the \fBSHELL\fP environment variable or
+the value of bit 8 (128) in the \fBED\fP flags.
+The spawned processes also do not currently inherit the environment from the
+Q-Register environment variables, i.e. you cannot change the process environment
+via \*(ST code.
+\# That would only be possible by rewriting everything with GSpawn.
+.SCITECO_TOPIC xclip
+See
+.B @scitecodatadir@/sample.teco_ini
+for an example of how to integrate the X11 clipboard via
+.BR xclip (1).
+Integrating with Wayland and the Mac OS clipboards is of course also possible.
+.
+.LP
+.SCITECO_TOPIC "$GTK_CSD"
On GTK+, you may turn off the infamous client-side window decorations
by setting the environment variable \fBGTK_CSD\fP to \(lq0\(rq.
.
+.LP
+.SCITECO_TOPIC "$SCITECO_SCINTILLUA_LEXERS"
+The \fBSCITECO_SCINTILLUA_LEXERS\fP environment variable specifies
+the Scintillua \(lqlexers/\(rq directory.
+This is passed as the \(lqscintillua.lexers\(rq library property when
+loading a Scintillua lexer via the \fBSCI_SETILEXER\fP Scintilla message.
.
.SH SIGNALS
.
diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template
index f9cad80..b274715 100644
--- a/doc/sciteco.7.template
+++ b/doc/sciteco.7.template
@@ -300,6 +300,15 @@ The default action is \fBnot\fP performed when
\(lq^KCLOSE\(rq has merely been masked out in the
current parser state (see below).
.TP
+.SCITECO_TOPIC ^KMOUSE
+.B ^KMOUSE
+Mouse event occurred.
+This will not be delivered on Curses unless bit 7 (64) is set in the
+\fBED\fP flags.
+You can use \fBEJ\fP with negative keys to retrieve
+the event type, mouse coordinates and other information
+about the last mouse event.
+.TP
.BI ^K x
Any other key with printable representation and all control codes
are looked up with a \(lq^K\(rq prefix.
@@ -746,10 +755,12 @@ Global and local Q-Registers are not affected by command line termination.
.SS Auto Completion
.
.SCITECO_TOPIC autocomplete
-The immediate editing commands that perform auto-completions, do
+The immediate editing commands, that perform auto-completions, do
so in a manner similar to Posix shells.
Upon first invocation they try to fully or partially complete the file
name (or token).
+If the token can be fully completed, the current command or Q-Register
+specification will also usually be terminated automatically.
If no completion can be performed, the invocation will display a
list of file names (or tokens) that begin with the token to complete
in \*(ST's popup area.
@@ -761,6 +772,15 @@ of file names or tokens is displayed in the popup area.
I.e. it is possible to cycle through long lists of possible
auto-completions.
.LP
+You can also scroll through the popup area with the mouse wheel.
+Furthermore, you can click on entries in the popup area with the
+mouse in order to fully complete them.
+Often this will also automatically terminate the current command or
+Q-Register specification, just like an unambiguous completion via
+immediate editing commands.
+On Curses, mouse events are not processed unless bit 7 (64) is set
+in the \fBED\fP flags.
+.LP
When completing file names, hidden files are not considered for
completion unless a prefix of the hidden file's name has already
been typed.
@@ -780,6 +800,13 @@ file names with \(lq./\(rq.
This is useful for writing cross-platform \*(ST macros (see
.BR "FILE NAMES AND DIRECTORIES" ).
.LP
+File name completions are case-sensitive or insensitive depending
+on operating system defaults.
+On some operating systems \*(ST can determine the case-sensitivity
+of individual directories as well.
+\# Should be supported on Mac OS and newer versions of Windows,
+\# but it's still untested.
+.LP
Note that completions take place after string building and
tilde-expansion is also performed by file name completions,
so for instance both \(lq~/foo\(rq and \(lq^EQ[$HOME]/foo\(rq
@@ -1553,10 +1580,10 @@ The existence of a clipboard register can thus be checked
in macros to determine whether getting and modifying that
particular clipboard is supported natively.
.br
+\*(ST supports two ways of driving the clipboard on ncurses.
.SCITECO_TOPIC OSC-52 xterm
-\*(ST does \fBnot\fP generally support clipboards on ncurses,
-but has special support for OSC-52 escape sequences, as were
-introduced by sufficiently recent versions of
+First of all, there is built-in support for OSC-52 escape sequences,
+as were introduced by sufficiently recent versions of
.BR xterm (1)
and have since been adopted by several other terminal emulators.
Since the operability of OSC-52 clipboards cannot be tested
@@ -1566,6 +1593,7 @@ configured.
.BR xterm (1)
for instance must be configured for allowing
the \fISetSelection\fP and \fIGetSelection\fP window operations.
+It is nevertheless observed to be a very buggy and unreliable feature.
If running under
.BR xterm (1),
\*(ST will still check whether the XTerm version is sufficient.
@@ -1574,10 +1602,16 @@ Other terminal emulators like Kitty may ask for permission to read the
clipboard (\fBread-clipboard-ask\fP).
This is not supported by \*(ST and must be disabled
(use \fBread-clipboard\fP instead).
-.SCITECO_TOPIC xclip
-If native clipboard support is unavailable, users may
-still fall back to using external tools like \fBxclip\fP(1)
-with the \fBEC\fP command.
+.br
+Alternatively, if OSC-52 clipboards are disabled, you can set the
+\fB$SCITECO_CLIPBOARD_SET\fP and \fB$SCITECO_CLIPBOARD_GET\fP
+environment variables (or corresponding Q-Registers) to shell
+commands, that receive the clipboard
+contents on stdin and output the requested clipboard on stdout.
+This allows integrating with various windowing environments.
+See \fBENVIRONMENT\fP in
+.BR sciteco (1)
+for more details.
.br
Setting the string part of a clipboard register will set that
clipboard. \*(ST will perform automatic EOL-translation according
@@ -2078,7 +2112,7 @@ The most basic flow control command in \*(ST is the Go-to command.
Since it is really an ordinary command, exceptional only in setting
the program counter and influencing parsing, it is described in this
document's command reference.
-\*(ST may do simple unconditional and computed gotos.
+\*(ST can perform simple unconditional and computed gotos.
.LP
.SCITECO_TOPIC label
Labels are symbolic and are defined with the following syntax:
@@ -2093,9 +2127,14 @@ When a label is encountered, it is cached in a macro-invocation level
specific goto table if it is not in there already.
Therefore every macro invocation has its own label namespace and gotos
to a label have constant complexity once a label has been parsed.
+\# The table lookup is not constant of course.
Terminating a macro execution (or command line) fails if a label that
is jumped to has not been defined.
Labels are also historically used as comments in TECO code.
+That's why \*(ST allows several identical labels in the same scope \(em
+goto commands will always jump to the first occurrance of the label.
+In case of label redefinition, a warning will be printed, so you are
+encouraged to use \(lqtrue\(rq comments as described below.
.
.SS Comments
.SCITECO_TOPIC comment
diff --git a/freebsd/pkg-plist b/freebsd/pkg-plist
index c129643..48a4b61 100644
--- a/freebsd/pkg-plist
+++ b/freebsd/pkg-plist
@@ -91,6 +91,7 @@ share/man/man7/%%PROGRAM_PREFIX%%sciteco.7.gz
%%LEXILLA%%%%DATADIR%%/lib/lexers/xml.tes
%%LEXILLA%%%%DATADIR%%/lib/lexers/yaml.tes
%%DATADIR%%/lib/lexers/woman.tes
+%%DATADIR%%/lib/opener.tes
%%DATADIR%%/lib/session.tes
%%DATADIR%%/lib/string.tes
%%DATADIR%%/lib/women/grosciteco.tes.1.woman
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 8bf80be..70f39b7 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -1,6 +1,6 @@
-dist_scitecolib_DATA = color.tes lexer.tes session.tes fnkeys.tes
-dist_scitecolib_DATA += string.tes getopt.tes
+dist_scitecolib_DATA = color.tes lexer.tes session.tes opener.tes \
+ fnkeys.tes string.tes getopt.tes
# Helper script for creating lexer definitions
EXTRA_DIST = scite2co.lua
diff --git a/lib/color.tes b/lib/color.tes
index eaa0baa..f732848 100644
--- a/lib/color.tes
+++ b/lib/color.tes
@@ -4,25 +4,55 @@
(Q.r # Q.g*256 # Q.b*256*256)
}
-!* These 8 colors should be available on every system *!
-000,000,000:M[color.rgb]U[color.black]
-128,000,000:M[color.rgb]U[color.red]
-000,128,000:M[color.rgb]U[color.green]
-128,128,000:M[color.rgb]U[color.yellow]
-000,000,128:M[color.rgb]U[color.blue]
-128,000,128:M[color.rgb]U[color.magenta]
-000,128,128:M[color.rgb]U[color.cyan]
-192,192,192:M[color.rgb]U[color.white]
+0EJ-1"=
+ !*
+ * The Curses RGB values are hardcoded by Scinterm
+ * and do not necessarily correspond to the renditions by
+ * terminal emulators.
+ *!
+ !* These 8 colors should be available on every system *!
+ 000,000,000:M[color.rgb]U[color.black]
+ 128,000,000:M[color.rgb]U[color.red]
+ 000,128,000:M[color.rgb]U[color.green]
+ 128,128,000:M[color.rgb]U[color.yellow]
+ 000,000,128:M[color.rgb]U[color.blue]
+ 128,000,128:M[color.rgb]U[color.magenta]
+ 000,128,128:M[color.rgb]U[color.cyan]
+ 192,192,192:M[color.rgb]U[color.white]
-!* Light color variants, might not be available on every terminal *!
-064,064,064:M[color.rgb]U[color.lblack]
-255,000,000:M[color.rgb]U[color.lred]
-000,255,000:M[color.rgb]U[color.lgreen]
-255,255,000:M[color.rgb]U[color.lyellow]
-000,000,255:M[color.rgb]U[color.lblue]
-255,000,255:M[color.rgb]U[color.lmagenta]
-000,255,255:M[color.rgb]U[color.lcyan]
-255,255,255:M[color.rgb]U[color.lwhite]
+ !* Light color variants, might not be available on every terminal *!
+ 064,064,064:M[color.rgb]U[color.lblack]
+ 255,000,000:M[color.rgb]U[color.lred]
+ 000,255,000:M[color.rgb]U[color.lgreen]
+ 255,255,000:M[color.rgb]U[color.lyellow]
+ 000,000,255:M[color.rgb]U[color.lblue]
+ 255,000,255:M[color.rgb]U[color.lmagenta]
+ 000,255,255:M[color.rgb]U[color.lcyan]
+ 255,255,255:M[color.rgb]U[color.lwhite]
+|
+ !*
+ * The GTK RGB values are the ones used by common terminal
+ * emulators. This should ensure that color schemes based
+ * on the 16 default colors look similar on Curses and GTK.
+ *!
+ 000,000,000:M[color.rgb]U[color.black]
+ 205,000,000:M[color.rgb]U[color.red]
+ 000,205,000:M[color.rgb]U[color.green]
+ 205,205,000:M[color.rgb]U[color.yellow]
+ 000,000,238:M[color.rgb]U[color.blue]
+ 205,000,205:M[color.rgb]U[color.magenta]
+ 000,205,205:M[color.rgb]U[color.cyan]
+ 229,229,229:M[color.rgb]U[color.white]
+
+ 127,127,127:M[color.rgb]U[color.lblack]
+ 255,000,000:M[color.rgb]U[color.lred]
+ 000,255,000:M[color.rgb]U[color.lgreen]
+ 255,255,000:M[color.rgb]U[color.lyellow]
+ 092,092,255:M[color.rgb]U[color.lblue]
+ 255,000,255:M[color.rgb]U[color.lmagenta]
+ 000,255,255:M[color.rgb]U[color.lcyan]
+ 255,255,255:M[color.rgb]U[color.lwhite]
+'
!* <[[flags,]bg,]fg,style>M[color.set] *!
@[color.set]{
diff --git a/lib/fnkeys.tes b/lib/fnkeys.tes
index ab78025..857c249 100644
--- a/lib/fnkeys.tes
+++ b/lib/fnkeys.tes
@@ -124,3 +124,71 @@
1U[ F9]
@[ F10]{(ESZOOMIN{-12D}}
1U[ F10]
+
+!*
+ * Mouse integration.
+ * Might be disabled unless bit 7 (64) is enabled in ED.
+ *
+ * Left click: Edit command line to jump to position.
+ * Ctrl+left click: Jump to beginning of line.
+ * Right click: Insert position or position range (when dragging).
+ * Double right click: insert range for word under cursor
+ * Ctrl+right click: Insertion beginning of line
+ * Scroll wheel: scrolls (faster with shift)
+ * Ctrl+scroll wheel: zoom (GTK-only)
+ *!
+@[MOUSE]{
+ -2EJESCHARPOSITIONFROMPOINTU.p
+ -4EJ&2"N Q.pESLINEFROMPOSITIONESPOSITIONFROMLINEU.p '
+ 1,Q.pESWORDSTARTPOSITION:U.#ws
+ 1,Q.pESWORDENDPOSITION:U.#we
+ Q.p:U.p
+
+ -EJOpressed,released,scrollup,scrolldown
+ !pressed!
+ !* left click *!
+ -3EJ-1"= Q.p-.M#c !* not reached *! '
+ { -9D
+ !* right click *!
+ -3EJ-3"= Q.p"U I(\.p ' '
+ !* middle click *!
+ -3EJ-2"= :Q~"U I(g~) ' '
+ }
+ !released!
+ { -9D
+ -3EJ-3"= Q.p"U
+ !* right click *!
+ <-A-("=1;'R> \U.o
+ Q.o-Q.p"=
+ .-1"> -2A-)"=
+ R <-A-("=1;'R>
+ \-Q.p"=
+ !* double right-click *!
+ .,ZD I\.#ws,\.#we
+ '
+ ' '
+ ZJ I)
+ |
+ Q.o-Q.p"> Q.o,Q.p U.oU.p '
+ !* right drag *!
+ .,ZD I\.o,\.p)
+ '
+ ' '
+ }
+ !scrollup!
+ -4EJ&2"=
+ -4EJ&1"=-1|-2',0ESLINESCROLL
+ |
+ ESZOOMIN
+ '
+ {-9D}
+ !scrolldown!
+ -4EJ&2"=
+ -4EJ&1"=1|2',0ESLINESCROLL
+ |
+ ESZOOMOUT
+ '
+ {-9D}
+}
+@[ MOUSE]{(M[MOUSE]}
+1U[ MOUSE]
diff --git a/lib/opener.tes b/lib/opener.tes
new file mode 100644
index 0000000..6a57317
--- /dev/null
+++ b/lib/opener.tes
@@ -0,0 +1,37 @@
+!*$
+ * M[opener] -- Open a number of files from the current buffer
+ *
+ * This is usually the unnamed buffer as initialized from the command line.
+ * It supports both the +line[,column] and filename:line[:column] syntaxes.
+ * Since this may make it hard to open files with certain file names, all
+ * filenames after "--" are interpreted verbatim.
+ *!
+@[opener]{
+ <.-Z;
+ !* --/-S stops processing of special arguments *!
+ 0A--"= 1A--"= 2A-10"=
+ L <:L;R 0X.f [* EBN.f ]* L> 1;
+ '''
+
+ 1U.l 1U.c
+ !* +line[,column] *!
+ 0A-+"=
+ C 0A"D \U.l W 0A-,"= C \U.c ' 0A-10"=L' '
+ '
+
+ !* filename:line[:column][:] *!
+ LR -A-:"=R' <-A"DR|1;'> -A-:"=
+ \U.a R <-A"DR|1;'> -A-:"=
+ \U.l Q.aU.c R
+ |
+ Q.aU.l 1U.c
+ '
+ |
+ LR
+ '
+
+ 0X.f [*
+ EBN.f Q.c-1,Q.l-1ESFINDCOLUMN:J
+ ]*
+ L>
+}
diff --git a/sample.teco_ini b/sample.teco_ini
index 02c8bcb..13ab76b 100644
--- a/sample.teco_ini
+++ b/sample.teco_ini
@@ -7,6 +7,7 @@ EMQ[$SCITECOPATH]/color.tes
!* Load lexer and buffer session libraries *!
EMQ[$SCITECOPATH]/lexer.tes
EMQ[$SCITECOPATH]/session.tes
+EMQ[$SCITECOPATH]/opener.tes
!* Automatic lexing and session management using ED hooks *!
@#ED{
@@ -49,7 +50,7 @@ EMQ[$SCITECOPATH]/session.tes
!* Uncomment to enable automatic case folding *!
!!0,8ED
-!*
+!*
* Tweak the default font name and size.
* The size unit is 1pt/100
*!
@@ -58,9 +59,16 @@ EMQ[$SCITECOPATH]/session.tes
!* Enable default function key macros *!
EMQ[$SCITECOPATH]/fnkeys.tes
-!* Uncomment if terminal supports OSC-52 clipboards *!
+!* Comment out to disable mouse interaction on Curses *!
+0,64ED
+
+!* Uncomment if terminal emulator supports OSC-52 clipboards *!
!!0,256ED
+!* For integrating with xclip on ncurses *!
+[$SCITECO_CLIPBOARD_SET]xclip -in -selection {}
+[$SCITECO_CLIPBOARD_GET]xclip -out -selection {} || true
+
!* Uncomment to enable Unicode icons in the Curses UI *!
!!0,512ED
@@ -81,8 +89,5 @@ Z"=
M[session.load]
|
[session.path] !* disables session saving *!
- [.f
- <:L;R 0X.f [* EBN.f ]* L>
- ].f
- -EF
+ M[opener] -EF
'
diff --git a/src/cmdline.c b/src/cmdline.c
index 816816c..dde096d 100644
--- a/src/cmdline.c
+++ b/src/cmdline.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -614,7 +614,7 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
*/
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -645,6 +645,16 @@ teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *
}
gboolean
+teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error)
+{
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(ctx, str->data, str->len, &str_escaped);
+ if (!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1]))
+ teco_string_append_c(&str_escaped, ' ');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_stringbuilding_escaped_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error)
{
@@ -684,6 +694,14 @@ teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_
}
gboolean
+teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+ teco_state_t *stringbuilding_current = stringbuilding_ctx->parent.current;
+ return stringbuilding_current->insert_completion_cb(&stringbuilding_ctx->parent, str, error);
+}
+
+gboolean
teco_state_insert_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -778,7 +796,7 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -801,6 +819,20 @@ teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) &&
+ ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -820,7 +852,7 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -855,6 +887,21 @@ teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_autofree gchar *pattern_escaped = teco_globber_escape_pattern(str->data);
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, pattern_escaped, strlen(pattern_escaped), &str_escaped);
+ if ((!str->len || !G_IS_DIR_SEPARATOR(str->data[str->len-1])) &&
+ ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -874,7 +921,7 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -898,6 +945,19 @@ teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *
}
gboolean
+teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ /*
+ * FIXME: We might terminate the command in case of leaf directories.
+ */
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
g_assert(ctx->expectqreg != NULL);
@@ -910,6 +970,18 @@ teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t
}
gboolean
+teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ g_assert(ctx->expectqreg != NULL);
+ /*
+ * NOTE: teco_machine_qregspec_t is private, so we downcast to teco_machine_t.
+ * Otherwise, we'd have to move this callback into qreg.c.
+ */
+ teco_state_t *expectqreg_current = ((teco_machine_t *)ctx->expectqreg)->current;
+ return expectqreg_current->insert_completion_cb((teco_machine_t *)ctx->expectqreg, str, error);
+}
+
+gboolean
teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
switch (key) {
@@ -919,7 +991,7 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -945,6 +1017,12 @@ teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_
}
gboolean
+teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error)
+{
+ return teco_cmdline_insert(str->data, str->len, error);
+}
+
+gboolean
teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = teco_machine_qregspec_get_stringbuilding(ctx);
@@ -967,7 +1045,7 @@ teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_m
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -985,6 +1063,17 @@ teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_m
}
gboolean
+teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = teco_machine_qregspec_get_stringbuilding(ctx);
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ teco_string_append_c(&str_escaped, ']');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1003,14 +1092,14 @@ teco_state_execute_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *pa
break;
/*
- * In the EC command, <TAB> completes files just like ^T
+ * In the EC command, <TAB> completes files just like ^G<TAB>.
*
* TODO: Implement shell-command completion by iterating
* executables in $PATH
*/
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1049,7 +1138,7 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1075,6 +1164,17 @@ teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_mac
}
gboolean
+teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ teco_string_append_c(&str_escaped, ',');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1094,7 +1194,7 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1122,6 +1222,20 @@ teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
}
gboolean
+teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ /*
+ * FIXME: This does not escape `,`. Cannot be escaped via ^Q currently?
+ */
+ teco_string_append_c(&str_escaped, ',');
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
+gboolean
teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error)
{
teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
@@ -1141,7 +1255,7 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
if (teco_interface_popup_is_shown()) {
/* cycle through popup pages */
- teco_interface_popup_show();
+ teco_interface_popup_scroll();
return TRUE;
}
@@ -1163,6 +1277,19 @@ teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *paren
return stringbuilding_current->process_edit_cmd_cb(&stringbuilding_ctx->parent, &ctx->parent, key, error);
}
+gboolean
+teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error)
+{
+ teco_machine_stringbuilding_t *stringbuilding_ctx = &ctx->expectstring.machine;
+
+ g_auto(teco_string_t) str_escaped;
+ teco_machine_stringbuilding_escape(stringbuilding_ctx, str->data, str->len, &str_escaped);
+ if (ctx->expectstring.nesting == 1)
+ teco_string_append_wc(&str_escaped,
+ ctx->expectstring.machine.escape_char == '{' ? '}' : ctx->expectstring.machine.escape_char);
+ return teco_cmdline_insert(str_escaped.data, str_escaped.len, error);
+}
+
/*
* Command states
*/
diff --git a/src/cmdline.h b/src/cmdline.h
index f4b84e4..ebdf1e1 100644
--- a/src/cmdline.h
+++ b/src/cmdline.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/core-commands.c b/src/core-commands.c
index 4ee0c5c..0d23adb 100644
--- a/src/core-commands.c
+++ b/src/core-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -2202,51 +2202,6 @@ TECO_DEFINE_STATE_CASEINSENSITIVE(teco_state_escape,
.style = SCE_SCITECO_COMMAND
);
-/*$ EF close
- * [bool]EF -- Remove buffer from ring
- * -EF
- *
- * Removes buffer from buffer ring, effectively
- * closing it.
- * If the buffer is dirty (modified), EF will yield
- * an error.
- * <bool> may be a specified to enforce closing dirty
- * buffers.
- * If it is a Failure condition boolean (negative),
- * the buffer will be closed unconditionally.
- * If <bool> is absent, the sign prefix (1 or -1) will
- * be implied, so \(lq-EF\(rq will always close the buffer.
- *
- * It is noteworthy that EF will be executed immediately in
- * interactive mode but can be rubbed out at a later time
- * to reopen the file.
- * Closed files are kept in memory until the command line
- * is terminated.
- */
-static void
-teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
-{
- if (teco_qreg_current) {
- const teco_string_t *name = &teco_qreg_current->head.name;
- g_autofree gchar *name_printable = teco_string_echo(name->data, name->len);
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Q-Register \"%s\" currently edited", name_printable);
- return;
- }
-
- teco_int_t v;
- if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
- return;
- if (teco_is_failure(v) && teco_ring_current->dirty) {
- g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
- "Buffer \"%s\" is dirty",
- teco_ring_current->filename ? : "(Unnamed)");
- return;
- }
-
- teco_ring_close(error);
-}
-
/*$ ED flags
* flags ED -- Set and get ED-flags
* [off,]on ED
@@ -2266,37 +2221,50 @@ teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
* Without any argument ED returns the current flags.
*
* Currently, the following flags are used by \*(ST:
- * - 4: If enabled, prefer raw single-byte ANSI encoding
- * for all new buffers and registers.
- * This does not change the encoding of any existing
- * buffers and any initialized default register when set via
- * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
- * - 8: Enable/disable automatic folding of case-insensitive
- * command characters during interactive key translation.
- * The case of letter keys is inverted, so one or two
- * character commands will typically be inserted upper-case,
- * but you can still press Shift to insert lower-case letters.
- * Case-insensitive Q-Register specifications are not
- * case folded.
- * This is thought to improve the readability of the command
- * line macro.
- * - 16: Enable/disable automatic translation of end of
- * line sequences to and from line feed.
- * Disabling this flag allows 8-bit clean loading and saving
- * of files.
- * - 32: Enable/Disable buffer editing hooks
- * (via execution of macro in global Q-Register \(lqED\(rq)
- * - 128: Enable/Disable enforcement of UNIX98
- * \(lq/bin/sh\(rq emulation for operating system command
- * executions
- * - 256: Enable/Disable OSC-52 clipboard support.
- * Must only be enabled if the terminal emulator is configured
- * properly.
- * - 512: Enable/Disable Unicode icons in the Curses UI.
- * This requires a capable font, like the ones provided
- * by the \(lqNerd Fonts\(rq project.
- * Changes to this flag in interactive mode may not become
- * effective immediately.
+ * .IP 4: 5
+ * If enabled, prefer raw single-byte ANSI encoding
+ * for all new buffers and registers.
+ * This does not change the encoding of any existing
+ * buffers and any initialized default register when set via
+ * \fBED\fP, so you might want to launch \*(ST with \fB--8bit\fP.
+ * .IP 8:
+ * Enable/disable automatic folding of case-insensitive
+ * command characters during interactive key translation.
+ * The case of letter keys is inverted, so one or two
+ * character commands will typically be inserted upper-case,
+ * but you can still press Shift to insert lower-case letters.
+ * Case-insensitive Q-Register specifications are not
+ * case folded.
+ * This is thought to improve the readability of the command
+ * line macro.
+ * .IP 16:
+ * Enable/disable automatic translation of end of
+ * line sequences to and from line feed.
+ * Disabling this flag allows 8-bit clean loading and saving
+ * of files.
+ * .IP 32:
+ * Enable/Disable buffer editing hooks
+ * (via execution of macro in global Q-Register \(lqED\(rq)
+ * .IP 64:
+ * .SCITECO_TOPIC mouse
+ * Enable/Disable processing and delivery of mouse events in
+ * the Curses UI.
+ * If enabled, the terminal emulator's default mouse behavior
+ * may be inhibited.
+ * .IP 128:
+ * Enable/Disable enforcement of UNIX98
+ * \(lq/bin/sh\(rq emulation for operating system command
+ * executions
+ * .IP 256:
+ * Enable/Disable OSC-52 clipboard support.
+ * Must only be enabled if the terminal emulator is configured
+ * properly.
+ * .IP 512:
+ * Enable/Disable Unicode icons in the Curses UI.
+ * This requires a capable font, like the ones provided
+ * by the \(lqNerd Fonts\(rq project.
+ * Changes to this flag in interactive mode may not become
+ * effective immediately.
*
* The features controlled thus are discribed in other sections
* of this manual.
@@ -2322,9 +2290,10 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
/*$ EJ properties
* [key]EJ -> value -- Get and set system properties
- * -EJ -> value
* value,keyEJ
* rgb,color,3EJ
+ * -EJ -> event
+ * -2EJ -> y, x
*
* This command may be used to get and set system
* properties.
@@ -2338,16 +2307,16 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* write-only or read-only.
*
* The following property keys are defined:
- * .IP 0 4
+ * .IP 0: 4
* The current user interface: 1 for Curses, 2 for GTK
* (\fBread-only\fP)
- * .IP 1
+ * .IP 1:
* The current numbfer of buffers: Also the numeric id
* of the last buffer in the ring. This is implied if
* no argument is given, so \(lqEJ\(rq returns the number
* of buffers in the ring.
* (\fBread-only\fP)
- * .IP 2
+ * .IP 2:
* The current memory limit in bytes.
* This limit helps to prevent dangerous out-of-memory
* conditions (e.g. resulting from infinite loops) by
@@ -2373,7 +2342,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* this happens you may have to clear your command-line
* first.
* Memory limiting is enabled by default.
- * .IP 3
+ * .IP 3:
* This \fBwrite-only\fP property allows redefining the
* first 16 entries of the terminal color palette \(em a
* feature required by some
@@ -2414,17 +2383,60 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error)
* on exit the author is aware of is \fBxterm\fP(1) and
* the Linux console driver.
* You have been warned. Good luck.
- * .IP 4
+ * .IP 4:
* The column after the last horizontal movement.
* This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal
* setting \fBSCI_CHOOSECARETX\fP.
* Unless most other settings, this is on purpose not restored on rubout,
* so it "survives" command line replacements.
+ * .
+ * .IP -1:
+ * Type of the last mouse event (\fBread-only\fP).
+ * One of the following values will be returned:
+ * .RS
+ * . IP 1: 4
+ * Some button has been pressed
+ * . IP 2:
+ * Some button has been released
+ * . IP 3:
+ * Scroll up
+ * . IP 4:
+ * Scroll down
+ * .RE
+ * .IP -2:
+ * Coordinates of the mouse pointer relative to the Scintilla view
+ * at the time of the last mouse event.
+ * This is in pixels or cells depending on the UI.
+ * First the Y coordinate is pushed, followed by the X coordinate,
+ * allowing you to pass them on directly to the \fBSCI_POSITIONFROMPOINT\fP
+ * and similar Scintilla messages using the \fBES\fP command.
+ * (\fBread-only\fP)
+ * .IP -3:
+ * Number of the mouse button involved in the last mouse event, beginning with 1.
+ * Can be -1 if the button cannot be determined or is irrelevant.
+ * (\fBread-only\fP)
+ * .IP -4:
+ * Bit mask describing the key modifiers at the time of the last
+ * mouse event (\fBread-only\fP).
+ * Currently, the following flags are used:
+ * .RS
+ * . IP 1: 4
+ * Shift key
+ * . IP 2:
+ * Control key (CTRL)
+ * . IP 4:
+ * Alt key
+ * .RE
*/
static void
teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
{
enum {
+ EJ_MOUSE_MODS = -4,
+ EJ_MOUSE_BUTTON,
+ EJ_MOUSE_COORD,
+ EJ_MOUSE_TYPE,
+
EJ_USER_INTERFACE = 0,
EJ_BUFFERS,
EJ_MEMORY_LIMIT,
@@ -2487,6 +2499,21 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error)
* Get property
*/
switch (property) {
+ case EJ_MOUSE_TYPE:
+ teco_expressions_push(teco_mouse.type);
+ break;
+ case EJ_MOUSE_COORD:
+ /* can be passed down to @ES/POSITIONFROMPOINT// */
+ teco_expressions_push(teco_mouse.y);
+ teco_expressions_push(teco_mouse.x);
+ break;
+ case EJ_MOUSE_BUTTON:
+ teco_expressions_push(teco_mouse.button);
+ break;
+ case EJ_MOUSE_MODS:
+ teco_expressions_push(teco_mouse.mods);
+ break;
+
case EJ_USER_INTERFACE:
/*
* FIXME: Replace INTERFACE_* macros with
@@ -2767,7 +2794,7 @@ teco_state_ecommand_encoding(teco_machine_main_t *ctx, GError **error)
}
}
- teco_int_t dot_glyphs;
+ teco_int_t dot_glyphs = 0;
if (colon_modified) {
sptr_t dot_bytes = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
dot_glyphs = teco_interface_bytes2glyphs(dot_bytes);
diff --git a/src/core-commands.h b/src/core-commands.h
index fbb67fa..8ce7be7 100644
--- a/src/core-commands.h
+++ b/src/core-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/doc.c b/src/doc.c
index 019603a..4ac96c8 100644
--- a/src/doc.c
+++ b/src/doc.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/doc.h b/src/doc.h
index 1218c35..4ecfd21 100644
--- a/src/doc.h
+++ b/src/doc.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/eol.c b/src/eol.c
index 9e80b96..e5cab2c 100644
--- a/src/eol.c
+++ b/src/eol.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/eol.h b/src/eol.h
index 26418e5..0bcb28c 100644
--- a/src/eol.h
+++ b/src/eol.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/error.c b/src/error.c
index afa2ac1..6326984 100644
--- a/src/error.c
+++ b/src/error.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/error.h b/src/error.h
index 021f759..b672024 100644
--- a/src/error.h
+++ b/src/error.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/expressions.c b/src/expressions.c
index 63d3b2f..c48e7b0 100644
--- a/src/expressions.c
+++ b/src/expressions.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/expressions.h b/src/expressions.h
index caea1d7..631c867 100644
--- a/src/expressions.h
+++ b/src/expressions.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/file-utils.c b/src/file-utils.c
index 7839a38..75bcb48 100644
--- a/src/file-utils.c
+++ b/src/file-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -241,6 +241,60 @@ teco_file_get_program_path(void)
#endif
+#ifdef G_OS_WIN32
+
+/*
+ * Definitions from the DDK's ntifs.h.
+ */
+#define FileCaseSensitiveInformation 71
+
+static gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ g_autofree gunichar2 *path_utf16 = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
+ HANDLE hnd = CreateFileW(path_utf16, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+ if (hnd == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ /*
+ * NOTE: This requires Windows 10, version 1803 or later.
+ * FIXME: But even then, this is relying on undocumented behavior!
+ * If unavailable we just assume the platform-default case-insensitivity.
+ */
+ FILE_CASE_SENSITIVE_INFORMATION info = {0};
+ GetFileInformationByHandleEx(hnd, FileCaseSensitiveInformation, &info, sizeof(info));
+ CloseHandle(hnd);
+ return info.Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR;
+}
+
+#elif defined(G_OS_UNIX) && defined(_PC_CASE_SENSITIVE)
+
+/*
+ * This is supported at least on Mac OS.
+ *
+ * NOTE: If the selector is not supported, -1 is returned and we also assume case-sensitivity.
+ */
+static inline gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ return pathconf(path, _PC_CASE_SENSITIVE);
+}
+
+#else /* !G_OS_WIN32 && (!G_OS_UNIX || !_PC_CASE_SENSITIVE) */
+
+/*
+ * FIXME: The only way to query this on Linux and FreeBSD would be to
+ * hardcode "case-insensitive" file systems.
+ */
+static inline gboolean
+teco_file_is_case_sensitive(const gchar *path)
+{
+ return TRUE;
+}
+
+#endif
+
/**
* Get the datadir.
*
@@ -334,11 +388,16 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
gsize dirname_len = teco_file_get_dirname_len(filename_expanded);
g_autofree gchar *dirname = g_strndup(filename_expanded, dirname_len);
gchar *basename = filename_expanded + dirname_len;
+ gsize basename_len = strlen(basename);
g_autoptr(GDir) dir = g_dir_open(dirname_len ? dirname : ".", 0, NULL);
if (!dir)
return FALSE;
+ /* Whether the directory has case-sensitive entries */
+ gboolean case_sensitive = teco_file_is_case_sensitive(dirname_len ? dirname : ".");
+ teco_string_diff_t string_diff = case_sensitive ? teco_string_diff : teco_string_casediff;
+
/*
* On Windows, both forward and backslash
* directory separators are allowed in directory
@@ -356,9 +415,12 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
guint files_len = 0;
gsize prefix_len = 0;
- const gchar *cur_basename;
- while ((cur_basename = g_dir_read_name(dir))) {
- if (!g_str_has_prefix(cur_basename, basename))
+ teco_string_t cur_basename;
+ while ((cur_basename.data = (gchar *)g_dir_read_name(dir))) {
+ cur_basename.len = strlen(cur_basename.data);
+
+ if (string_diff(&cur_basename, basename, basename_len) != basename_len)
+ /* basename is not a prefix of cur_basename */
continue;
/*
@@ -366,8 +428,8 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
* Reserving one byte at the end of the filename ensures we can easily
* append the directory separator without reallocations.
*/
- gchar *cur_filename = g_malloc(strlen(dirname)+strlen(cur_basename)+2);
- strcat(strcpy(cur_filename, dirname), cur_basename);
+ gchar *cur_filename = g_malloc(strlen(dirname)+cur_basename.len+2);
+ strcat(strcpy(cur_filename, dirname), cur_basename.data);
/*
* NOTE: This avoids g_file_test() for G_FILE_TEST_EXISTS
@@ -391,8 +453,8 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
other_file.data = (gchar *)g_slist_next(files)->data + filename_len;
other_file.len = strlen(other_file.data);
- gsize len = teco_string_diff(&other_file, cur_filename + filename_len,
- strlen(cur_filename) - filename_len);
+ gsize len = string_diff(&other_file, cur_filename + filename_len,
+ strlen(cur_filename) - filename_len);
if (len < prefix_len)
prefix_len = len;
} else {
@@ -421,7 +483,7 @@ teco_file_auto_complete(const gchar *filename, GFileTest file_test, teco_string_
strlen((gchar *)file->data), is_buffer);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(filename_len);
}
/*
diff --git a/src/file-utils.h b/src/file-utils.h
index e974e2f..12a9b83 100644
--- a/src/file-utils.h
+++ b/src/file-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/glob.c b/src/glob.c
index e338975..fe73065 100644
--- a/src/glob.c
+++ b/src/glob.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/glob.h b/src/glob.h
index 8f03d38..d11fbce 100644
--- a/src/glob.h
+++ b/src/glob.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,6 +48,7 @@ GRegex *teco_globber_compile_pattern(const gchar *pattern);
/* in cmdline.c */
gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectglob_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTGLOB
@@ -58,6 +59,8 @@ gboolean teco_state_expectglob_process_edit_cmd(teco_machine_main_t *ctx, teco_m
TECO_DEFINE_STATE_EXPECTFILE(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectglob_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectglob_insert_completion, \
##__VA_ARGS__ \
)
diff --git a/src/goto-commands.c b/src/goto-commands.c
index 2035277..99288c1 100644
--- a/src/goto-commands.c
+++ b/src/goto-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,6 +30,7 @@
#include "lexer.h"
#include "core-commands.h"
#include "undo.h"
+#include "interface.h"
#include "goto.h"
#include "goto-commands.h"
@@ -62,15 +63,20 @@ teco_state_label_input(teco_machine_main_t *ctx, gunichar chr, GError **error)
}
if (chr == '!') {
- /*
- * NOTE: If the label already existed, its PC will be restored
- * on rubout.
- * Otherwise, the label will be removed (PC == -1).
- */
gssize existing_pc = teco_goto_table_set(&ctx->goto_table, ctx->goto_label.data,
ctx->goto_label.len, ctx->macro_pc);
+ if (existing_pc == ctx->macro_pc)
+ /* encountered the same label again */
+ return &teco_state_start;
+ if (existing_pc >= 0) {
+ g_autofree gchar *label_printable = teco_string_echo(ctx->goto_label.data,
+ ctx->goto_label.len);
+ teco_interface_msg(TECO_MSG_WARNING, "Ignoring goto label \"%s\" redefinition",
+ label_printable);
+ return &teco_state_start;
+ }
if (ctx->parent.must_undo)
- teco_goto_table_undo_set(&ctx->goto_table, ctx->goto_label.data, ctx->goto_label.len, existing_pc);
+ teco_goto_table_undo_remove(&ctx->goto_table, ctx->goto_label.data, ctx->goto_label.len);
if (teco_goto_skip_label.len > 0 &&
!teco_string_cmp(&ctx->goto_label, teco_goto_skip_label.data, teco_goto_skip_label.len)) {
@@ -151,7 +157,10 @@ teco_state_goto_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
}
/* in cmdline.c */
-gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error);
+gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar chr, GError **error);
+gboolean teco_state_goto_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ O
* Olabel$ -- Go to label
@@ -180,7 +189,8 @@ gboolean teco_state_goto_process_edit_cmd(teco_machine_main_t *ctx, teco_machine
* terminate the command-line.
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_goto,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_goto_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_goto_insert_completion
);
/*
diff --git a/src/goto-commands.h b/src/goto-commands.h
index 03773c0..f4f52d5 100644
--- a/src/goto-commands.h
+++ b/src/goto-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/goto.c b/src/goto.c
index 65ee3ca..855c9f9 100644
--- a/src/goto.c
+++ b/src/goto.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -78,53 +78,26 @@ teco_goto_table_dump(teco_goto_table_t *ctx)
}
#endif
-/** @memberof teco_goto_table_t */
-gssize
+/**
+ * Remove label from goto table.
+ *
+ * @param ctx Goto table
+ * @param name Label name
+ * @param len Length of label name
+ * @return TRUE if the label existed and was removed
+ *
+ * @memberof teco_goto_table_t
+ */
+gboolean
teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len)
{
- gssize existing_pc = -1;
-
teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- if (label) {
- existing_pc = label->pc;
- rb3_unlink_and_rebalance(&label->head.head);
- teco_goto_label_free(label);
- }
+ if (!label)
+ return FALSE;
- return existing_pc;
-}
-
-/** @memberof teco_goto_table_t */
-gssize
-teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len)
-{
- teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- return label ? label->pc : -1;
-}
-
-/** @memberof teco_goto_table_t */
-gssize
-teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc)
-{
- if (pc < 0)
- return teco_goto_table_remove(ctx, name, len);
-
- gssize existing_pc = -1;
-
- teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
- if (label) {
- existing_pc = label->pc;
- label->pc = pc;
- } else {
- label = teco_goto_label_new(name, len, pc);
- teco_rb3str_insert(&ctx->tree, TRUE, &label->head);
- }
-
-#ifdef DEBUG
- teco_goto_table_dump(ctx);
-#endif
-
- return existing_pc;
+ rb3_unlink_and_rebalance(&label->head.head);
+ teco_goto_label_free(label);
+ return TRUE;
}
/*
@@ -135,35 +108,35 @@ teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize
*/
typedef struct {
teco_goto_table_t *table;
- gssize pc;
gsize len;
gchar name[];
-} teco_goto_table_undo_set_t;
+} teco_goto_table_undo_remove_t;
static void
-teco_goto_table_undo_set_action(teco_goto_table_undo_set_t *ctx, gboolean run)
+teco_goto_table_undo_remove_action(teco_goto_table_undo_remove_t *ctx, gboolean run)
{
- if (run) {
- teco_goto_table_set(ctx->table, ctx->name, ctx->len, ctx->pc);
+ if (!run)
+ return;
+
+ G_GNUC_UNUSED gboolean removed = teco_goto_table_remove(ctx->table, ctx->name, ctx->len);
+ g_assert(removed == TRUE);
#ifdef DEBUG
- teco_goto_table_dump(ctx->table);
+ teco_goto_table_dump(ctx->table);
#endif
- }
}
/** @memberof teco_goto_table_t */
void
-teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc)
+teco_goto_table_undo_remove(teco_goto_table_t *ctx, const gchar *name, gsize len)
{
if (!ctx->must_undo)
return;
- teco_goto_table_undo_set_t *token;
- token = teco_undo_push_size((teco_undo_action_t)teco_goto_table_undo_set_action,
+ teco_goto_table_undo_remove_t *token;
+ token = teco_undo_push_size((teco_undo_action_t)teco_goto_table_undo_remove_action,
sizeof(*token) + len);
if (token) {
token->table = ctx;
- token->pc = pc;
token->len = len;
if (name)
memcpy(token->name, name, len);
@@ -171,6 +144,44 @@ teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, g
}
/** @memberof teco_goto_table_t */
+gssize
+teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len)
+{
+ teco_goto_label_t *label = (teco_goto_label_t *)teco_rb3str_find(&ctx->tree, TRUE, name, len);
+ return label ? label->pc : -1;
+}
+
+/**
+ * Insert label into goto table.
+ *
+ * @param ctx Goto table
+ * @param name Label name
+ * @param len Length of label name
+ * @param pc Program counter of the new label
+ * @return The program counter of any label of the same name
+ * or -1. The label is inserted only if there is no label in the
+ * table already.
+ *
+ * @memberof teco_goto_table_t
+ */
+gssize
+teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gsize pc)
+{
+ gssize existing_pc = teco_goto_table_find(ctx, name, len);
+ if (existing_pc >= 0)
+ return existing_pc;
+
+ teco_goto_label_t *label = teco_goto_label_new(name, len, pc);
+ teco_rb3str_insert(&ctx->tree, TRUE, &label->head);
+
+#ifdef DEBUG
+ teco_goto_table_dump(ctx);
+#endif
+
+ return -1;
+}
+
+/** @memberof teco_goto_table_t */
void
teco_goto_table_clear(teco_goto_table_t *ctx)
{
diff --git a/src/goto.h b/src/goto.h
index 01f55ac..eda823f 100644
--- a/src/goto.h
+++ b/src/goto.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -40,12 +40,12 @@ teco_goto_table_init(teco_goto_table_t *ctx, gboolean must_undo)
ctx->must_undo = must_undo;
}
-gssize teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
+gboolean teco_goto_table_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
+void teco_goto_table_undo_remove(teco_goto_table_t *ctx, const gchar *name, gsize len);
gssize teco_goto_table_find(teco_goto_table_t *ctx, const gchar *name, gsize len);
-gssize teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc);
-void teco_goto_table_undo_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gssize pc);
+gssize teco_goto_table_set(teco_goto_table_t *ctx, const gchar *name, gsize len, gsize pc);
/** @memberof teco_goto_table_t */
static inline gboolean
diff --git a/src/help.c b/src/help.c
index 0f88646..07acb86 100644
--- a/src/help.c
+++ b/src/help.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -289,13 +289,13 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
return NULL;
}
- teco_ring_undo_edit();
/*
* ED hooks with the default lexer framework
* will usually load the styling SciTECO script
* when editing the buffer for the first time.
*/
- if (!teco_ring_edit(topic->filename, error))
+ if (!teco_current_doc_undo_edit(error) ||
+ !teco_ring_edit(topic->filename, error))
return NULL;
/*
@@ -314,7 +314,10 @@ teco_state_help_done(teco_machine_main_t *ctx, const teco_string_t *str, GError
}
/* in cmdline.c */
-gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar chr, GError **error);
+gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar chr, GError **error);
+gboolean teco_state_help_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ "?" help
* ?[topic]$ -- Get help for topic
@@ -384,5 +387,6 @@ gboolean teco_state_help_process_edit_cmd(teco_machine_main_t *ctx, teco_machine
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_help,
.initial_cb = (teco_state_initial_cb_t)teco_state_help_initial,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_help_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_help_insert_completion,
.expectstring.string_building = FALSE
);
diff --git a/src/help.h b/src/help.h
index 6400399..3148e01 100644
--- a/src/help.h
+++ b/src/help.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-icons.c b/src/interface-curses/curses-icons.c
index e2e4256..3e63d02 100644
--- a/src/interface-curses/curses-icons.c
+++ b/src/interface-curses/curses-icons.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-icons.h b/src/interface-curses/curses-icons.h
index c1be06f..933241d 100644
--- a/src/interface-curses/curses-icons.h
+++ b/src/interface-curses/curses-icons.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c
index e6e1549..dffbcf8 100644
--- a/src/interface-curses/curses-info-popup.c
+++ b/src/interface-curses/curses-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -200,6 +200,65 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
wmove(ctx->window, bar_y, cols-1);
wattron(ctx->window, A_REVERSE);
wvline(ctx->window, ' ', bar_height);
+}
+
+/**
+ * Find the entry at the given character coordinates.
+ *
+ * @param ctx The popup widget to look up
+ * @param y The pointer's Y position, relative to the popup's window
+ * @param x The pointer's X position, relative to the popup's window
+ * @return Pointer to the entry's string under the pointer or NULL.
+ * This string is owned by the popup and is only valid until the
+ * popup is cleared.
+ *
+ * @note This must match the calculations in teco_curses_info_popup_init_pad().
+ * But we could perhaps also cache these values.
+ */
+const teco_string_t *
+teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x)
+{
+ int cols = getmaxx(stdscr); /**! screen width */
+ gint pad_cols; /**! entry columns */
+ gint pad_colwidth; /**! width per entry column */
+
+ if (y == 0)
+ return NULL;
+
+ /*
+ * With Unicode icons enabled, we reserve 2 characters at the beginning and one
+ * after the filename/directory.
+ * Otherwise 2 characters after the entry.
+ */
+ gint reserve = teco_ed & TECO_ED_ICONS ? 2+1 : 2;
+ pad_colwidth = MIN(ctx->longest + reserve, cols - 2);
+
+ /* pad_cols = floor((cols - 2) / pad_colwidth) */
+ pad_cols = (cols - 2) / pad_colwidth;
+
+ gint cur_col = 0;
+ for (teco_stailq_entry_t *cur = ctx->list.first; cur != NULL; cur = cur->next) {
+ teco_popup_entry_t *entry = (teco_popup_entry_t *)cur;
+ gint cur_line = cur_col/pad_cols + 1;
+
+ if (cur_line > ctx->pad_first_line+y)
+ break;
+ if (cur_line == ctx->pad_first_line+y &&
+ x > (cur_col % pad_cols)*pad_colwidth && x <= ((cur_col % pad_cols)+1)*pad_colwidth)
+ return &entry->name;
+
+ cur_col++;
+ }
+
+ return NULL;
+}
+
+void
+teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx)
+{
+ gint lines = getmaxy(stdscr);
+ gint pad_lines = getmaxy(ctx->pad);
+ gint popup_lines = MIN(pad_lines + 1, lines - 1);
/* progress scroll position */
ctx->pad_first_line += popup_lines - 1;
@@ -211,6 +270,19 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr)
}
void
+teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta)
+{
+ gint lines = getmaxy(stdscr);
+ gint pad_lines = getmaxy(ctx->pad);
+ gint popup_lines = MIN(pad_lines + 1, lines - 1);
+
+ ctx->pad_first_line = MAX(ctx->pad_first_line+delta, 0);
+ if (pad_lines - ctx->pad_first_line < popup_lines - 1)
+ /* show last page */
+ ctx->pad_first_line = pad_lines - (popup_lines - 1);
+}
+
+void
teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx)
{
if (ctx->window)
diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h
index a6c28a5..d845b29 100644
--- a/src/interface-curses/curses-info-popup.h
+++ b/src/interface-curses/curses-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,6 +23,7 @@
#include <curses.h>
#include "list.h"
+#include "string-utils.h"
#include "interface.h"
typedef struct {
@@ -49,6 +50,10 @@ void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_
const gchar *name, gsize name_len, gboolean highlight);
void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr);
+const teco_string_t *teco_curses_info_popup_getentry(teco_curses_info_popup_t *ctx, gint y, gint x);
+void teco_curses_info_popup_scroll_page(teco_curses_info_popup_t *ctx);
+void teco_curses_info_popup_scroll(teco_curses_info_popup_t *ctx, gint delta);
+
static inline bool
teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx)
{
@@ -58,8 +63,17 @@ teco_curses_info_popup_is_shown(teco_curses_info_popup_t *ctx)
static inline void
teco_curses_info_popup_noutrefresh(teco_curses_info_popup_t *ctx)
{
- if (ctx->window)
- wnoutrefresh(ctx->window);
+ if (!ctx->window)
+ return;
+ /*
+ * NOTE: Scinterm always redraws its window, which is
+ * equivalent to touching it, even if it didn't change.
+ * Consequently, wnoutrefresh() will always copy it to newscr.
+ * We must therefore always redraw the popup as well, so it
+ * will still overlap the Scintilla view.
+ */
+ touchwin(ctx->window);
+ wnoutrefresh(ctx->window);
}
void teco_curses_info_popup_clear(teco_curses_info_popup_t *ctx);
diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c
index c751afd..f362424 100644
--- a/src/interface-curses/curses-utils.c
+++ b/src/interface-curses/curses-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h
index 2c819ee..9f2e8f3 100644
--- a/src/interface-curses/curses-utils.h
+++ b/src/interface-curses/curses-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c
index f713bc1..42ffdc6 100644
--- a/src/interface-curses/interface.c
+++ b/src/interface-curses/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -44,6 +44,10 @@
#include <glib/gprintf.h>
#include <glib/gstdio.h>
+#ifdef G_OS_UNIX
+#include <sys/wait.h>
+#endif
+
#include <curses.h>
#ifdef HAVE_TIGETSTR
@@ -53,6 +57,7 @@
* Some macros in term.h interfere with our code.
*/
#undef lines
+#undef buttons
#endif
#include <Scintilla.h>
@@ -355,6 +360,7 @@ static struct {
GQueue *input_queue;
teco_curses_info_popup_t popup;
+ gsize popup_prefix_len;
/**
* GError "thrown" by teco_interface_event_loop_iter().
@@ -688,6 +694,15 @@ teco_interface_init_interactive(GError **error)
#endif
/*
+ * Disables click-detection.
+ * If we'd want to discern PRESSED and CLICKED events,
+ * we'd have to emulate the same feature on GTK.
+ */
+#if NCURSES_MOUSE_VERSION >= 2
+ mouseinterval(0);
+#endif
+
+ /*
* We always have a CTRL handler on Windows, but doing it
* here again, ensures that we have a higher precedence
* than the one installed by PDCurses.
@@ -700,6 +715,11 @@ teco_interface_init_interactive(GError **error)
noecho();
/* Scintilla draws its own cursor */
curs_set(0);
+ /*
+ * This has also been observed to reduce flickering
+ * in teco_interface_refresh().
+ */
+ leaveok(stdscr, TRUE);
teco_interface.info_window = newwin(1, 0, 0, 0);
teco_interface.msg_window = newwin(1, 0, LINES - 2, 0);
@@ -781,11 +801,11 @@ teco_interface_restore_batch(void)
*/
#ifdef CURSES_TTY
if (teco_interface.stdout_orig >= 0) {
- int fd = dup2(teco_interface.stdout_orig, 1);
+ G_GNUC_UNUSED int fd = dup2(teco_interface.stdout_orig, 1);
g_assert(fd == 1);
}
if (teco_interface.stderr_orig >= 0) {
- int fd = dup2(teco_interface.stderr_orig, 2);
+ G_GNUC_UNUSED int fd = dup2(teco_interface.stderr_orig, 2);
g_assert(fd == 2);
}
#endif
@@ -1247,39 +1267,7 @@ teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError
return TRUE;
}
-#elif defined(CURSES_TTY)
-
-static void
-teco_interface_init_clipboard(void)
-{
- /*
- * At least on XTerm, there are escape sequences
- * for modifying the clipboard (OSC-52).
- * This is not standardized in terminfo, so we add special
- * XTerm support here. Unfortunately, it is pretty hard to find out
- * whether clipboard operations will actually work.
- * XTerm must be at least at v203 and the corresponding window operations
- * must be enabled.
- * There is no way to find out if they are but we must
- * not register the clipboard registers if they aren't.
- * Still, XTerm clipboards are broken with Unicode characters.
- * Also, there are other terminal emulators supporting OSC-52,
- * so the XTerm version is only checked if the terminal identifies as XTerm.
- * Also, a special clipboard ED flag must be set by the user.
- *
- * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
- * not worth using it since it won't always be set and even if set, does not
- * tell you whether the terminal will actually answer to the escape sequence or not.
- */
- if (!(teco_ed & TECO_ED_OSC52) ||
- (teco_xterm_version() >= 0 && teco_xterm_version() < 203))
- return;
-
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
- teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
-}
+#elif defined(G_OS_UNIX) && defined(CURSES_TTY)
static inline gchar
get_selection_by_name(const gchar *name)
@@ -1294,9 +1282,48 @@ get_selection_by_name(const gchar *name)
return g_ascii_tolower(*name) ? : 'c';
}
-gboolean
-teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
- GError **error)
+/*
+ * OSC-52 clipboard implementation.
+ *
+ * At least on XTerm, there are escape sequences
+ * for modifying the clipboard (OSC-52).
+ * This is not standardized in terminfo, so we add special
+ * XTerm support here. Unfortunately, it is pretty hard to find out
+ * whether clipboard operations will actually work.
+ * XTerm must be at least at v203 and the corresponding window operations
+ * must be enabled.
+ * There is no way to find out if they are but we must
+ * not register the clipboard registers if they aren't.
+ * Still, XTerm clipboards are broken with Unicode characters.
+ * Also, there are other terminal emulators supporting OSC-52,
+ * so the XTerm version is only checked if the terminal identifies as XTerm.
+ * Also, a special clipboard ED flag must be set by the user.
+ *
+ * NOTE: Apparently there is also a terminfo entry Ms, but it's probably
+ * not worth using it since it won't always be set and even if set, does not
+ * tell you whether the terminal will actually answer to the escape sequence or not.
+ *
+ * This is a rarely used feature and could theoretically also be handled
+ * by the $SCITECO_CLIPBOARD_SET/GET feature.
+ * Unfortunately, there is no readily available command-line utility allowing both
+ * copying and pasting via OSC-52.
+ * That's really the only reason we keep built-in OSC-52 clipboard support.
+ *
+ * FIXME: This is the only thing here requiring CURSES_TTY.
+ * On the other hand, there is hardly any non-PDCurses on UNIX, which is not
+ * on a TTY, so we shouldn't be loosing much by requiring both.
+ */
+
+static inline gboolean
+teco_interface_osc52_is_enabled(void)
+{
+ return teco_ed & TECO_ED_OSC52 &&
+ (teco_xterm_version() < 0 || teco_xterm_version() >= 203);
+}
+
+static gboolean
+teco_interface_osc52_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
{
fputs("\e]52;", teco_interface.screen_tty);
fputc(get_selection_by_name(name), teco_interface.screen_tty);
@@ -1335,8 +1362,8 @@ teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
return TRUE;
}
-gboolean
-teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+static gboolean
+teco_interface_osc52_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
{
gboolean ret = TRUE;
@@ -1430,7 +1457,167 @@ cleanup:
return ret;
}
-#else /* !PDCURSES && !CURSES_TTY */
+/*
+ * Implementation using external processes.
+ *
+ * NOTE: This could be done with the portable GSpawn API as well,
+ * but this implementation is much simpler.
+ * We don't really need it on Windows anyway as long as we are using
+ * only PDCurses.
+ * This might only be of interest on Windows if building for the Win32 version
+ * of ncurses.
+ * As a downside, compared to GSpawn, this cannot inherit the environment
+ * variables from the global Q-Register table.
+ */
+
+static void
+teco_interface_init_clipboard(void)
+{
+ if (!teco_interface_osc52_is_enabled() &&
+ (!teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_SET", 22) ||
+ !teco_qreg_table_find(&teco_qreg_table_globals, "$SCITECO_CLIPBOARD_GET", 22)))
+ return;
+
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new(""));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("P"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("S"));
+ teco_qreg_table_insert(&teco_qreg_table_globals, teco_qreg_clipboard_new("C"));
+}
+
+gboolean
+teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len,
+ GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_set_clipboard(name, str, str_len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_SET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot set clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "w");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ size_t len = fwrite(str, 1, str_len, pipe);
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ if (len < str_len) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error writing to process from %s", reg_name);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+teco_interface_get_clipboard(const gchar *name, gchar **str, gsize *len, GError **error)
+{
+ if (teco_interface_osc52_is_enabled())
+ return teco_interface_osc52_get_clipboard(name, str, len, error);
+
+ static const gchar *reg_name = "$SCITECO_CLIPBOARD_GET";
+
+ teco_qreg_t *reg = teco_qreg_table_find(&teco_qreg_table_globals, reg_name, strlen(reg_name));
+ if (!reg) {
+ /* Q-Register could have been removed in the meantime */
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot get clipboard. %s is undefined.", reg_name);
+ return FALSE;
+ }
+
+ g_auto(teco_string_t) command;
+ if (!reg->vtable->get_string(reg, &command.data, &command.len, NULL, error))
+ return FALSE;
+ if (teco_string_contains(&command, '\0')) {
+ teco_error_qregcontainsnull_set(error, reg_name, strlen(reg_name), FALSE);
+ return FALSE;
+ }
+
+ gchar *sel = g_strstr_len(command.data, command.len, "{}");
+ if (sel) {
+ *sel++ = ' ';
+ *sel = get_selection_by_name(name);
+ }
+
+ FILE *pipe = popen(command.data, "r");
+ if (!pipe) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Cannot spawn process from %s", reg_name);
+ return FALSE;
+ }
+
+ gchar buffer[1024];
+ size_t read_len;
+
+ g_auto(teco_string_t) ret = {NULL, 0};
+
+ do {
+ read_len = fread(buffer, 1, sizeof(buffer), pipe);
+ teco_string_append(&ret, buffer, read_len);
+ } while (read_len == sizeof(buffer));
+
+ int status = pclose(pipe);
+ if (status < 0 || !WIFEXITED(status)) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Error reaping process from %s", reg_name);
+ return FALSE;
+ }
+ /*
+ * You may have to add a `|| true` for instance to xclip if it
+ * could fail for empty selections.
+ */
+ if (WEXITSTATUS(status) != 0) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Process from %s returned with exit code %d",
+ reg_name, WEXITSTATUS(status));
+ return FALSE;
+ }
+
+ *str = ret.data;
+ *len = ret.len;
+ memset(&ret, 0, sizeof(ret));
+
+ return TRUE;
+}
+
+#else /* !PDCURSES && !G_OS_UNIX && !CURSES_TTY */
static void
teco_interface_init_clipboard(void)
@@ -1470,7 +1657,7 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
}
void
-teco_interface_popup_show(void)
+teco_interface_popup_show(gsize prefix_len)
{
if (!teco_interface.cmdline_window)
/* batch mode */
@@ -1479,9 +1666,21 @@ teco_interface_popup_show(void)
short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ teco_interface.popup_prefix_len = prefix_len;
teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg));
}
+void
+teco_interface_popup_scroll(void)
+{
+ if (!teco_interface.cmdline_window)
+ /* batch mode */
+ return;
+
+ teco_curses_info_popup_scroll_page(&teco_interface.popup);
+ teco_interface_popup_show(teco_interface.popup_prefix_len);
+}
+
gboolean
teco_interface_popup_is_shown(void)
{
@@ -1496,8 +1695,7 @@ teco_interface_popup_clear(void)
* PDCurses will not redraw all windows that may be
* overlapped by the popup window correctly - at least
* not the info window.
- * The Scintilla window is apparently always touched by
- * scintilla_noutrefresh().
+ * The Scintilla window is always touched by scintilla_noutrefresh().
* Actually we would expect this to be necessary on any curses,
* but ncurses doesn't require this.
*/
@@ -1563,13 +1761,6 @@ static void
teco_interface_refresh(void)
{
/*
- * Scintilla has been patched to avoid any automatic scrolling since that
- * has been benchmarked to be a very costly operation.
- * Instead we do it only once after every keypress.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
-
- /*
* Info window is updated very often which is very
* costly, especially when using PDC_set_title(),
* so we redraw it here, where the overhead does
@@ -1584,9 +1775,131 @@ teco_interface_refresh(void)
doupdate();
}
+#if NCURSES_MOUSE_VERSION >= 2
+
+#define BUTTON_NUM(X) \
+ (BUTTON##X##_PRESSED | BUTTON##X##_RELEASED | \
+ BUTTON##X##_CLICKED | BUTTON##X##_DOUBLE_CLICKED | BUTTON##X##_TRIPLE_CLICKED)
+#define BUTTON_EVENT(X) \
+ (BUTTON1_##X | BUTTON2_##X | BUTTON3_##X | BUTTON4_##X | BUTTON5_##X)
+
+static gboolean
+teco_interface_getmouse(GError **error)
+{
+ MEVENT event;
+
+ if (getmouse(&event) != OK)
+ return TRUE;
+
+ if (teco_curses_info_popup_is_shown(&teco_interface.popup) &&
+ wmouse_trafo(teco_interface.popup.window, &event.y, &event.x, FALSE)) {
+ /*
+ * NOTE: Not all curses variants report the RELEASED event,
+ * but may also return REPORT_MOUSE_POSITION.
+ * So we might react to all button presses as well.
+ */
+ if (event.bstate & (BUTTON1_RELEASED | REPORT_MOUSE_POSITION)) {
+ teco_machine_t *machine = &teco_cmdline.machine.parent;
+ const teco_string_t *insert = teco_curses_info_popup_getentry(&teco_interface.popup, event.y, event.x);
+
+ if (insert && machine->current->insert_completion_cb) {
+ /* successfully clicked popup item */
+ const teco_string_t insert_suffix = {insert->data + teco_interface.popup_prefix_len,
+ insert->len - teco_interface.popup_prefix_len};
+ if (!machine->current->insert_completion_cb(machine, &insert_suffix, error))
+ return FALSE;
+
+ teco_interface_popup_clear();
+ teco_interface_msg_clear();
+ teco_interface_cmdline_update(&teco_cmdline);
+ }
+
+ return TRUE;
+ }
+ if (event.bstate & BUTTON_NUM(4))
+ teco_curses_info_popup_scroll(&teco_interface.popup, -1);
+ else if (event.bstate & BUTTON_NUM(5))
+ teco_curses_info_popup_scroll(&teco_interface.popup, +1);
+
+ short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0));
+ short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0));
+ teco_curses_info_popup_show(&teco_interface.popup, SCI_COLOR_ATTR(fg, bg));
+
+ return TRUE;
+ }
+
+ /*
+ * Return mouse coordinates relative to the view.
+ * They will be in characters, but that's what SCI_POSITIONFROMPOINT
+ * expects on Scinterm anyway.
+ */
+ WINDOW *current = teco_view_get_window(teco_interface_current_view);
+ if (!wmouse_trafo(current, &event.y, &event.x, FALSE))
+ /* no event inside of current view */
+ return TRUE;
+
+ /*
+ * NOTE: There will only be one of the button bits
+ * set in bstate, so we don't loose information translating
+ * them to enums.
+ *
+ * At least on ncurses, we don't always get a RELEASED event.
+ * It instead sends only REPORT_MOUSE_POSITION,
+ * so make sure not to overwrite teco_mouse.button in this case.
+ */
+ if (event.bstate & BUTTON_NUM(4))
+ /* scroll up - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLUP;
+ else if (event.bstate & BUTTON_NUM(5))
+ /* scroll down - there will be no RELEASED event */
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ else if (event.bstate & BUTTON_EVENT(RELEASED))
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ else if (event.bstate & BUTTON_EVENT(PRESSED))
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ else
+ /* can also be REPORT_MOUSE_POSITION */
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+
+ teco_mouse.x = event.x;
+ teco_mouse.y = event.y;
+
+ if (event.bstate & BUTTON_NUM(1))
+ teco_mouse.button = 1;
+ else if (event.bstate & BUTTON_NUM(2))
+ teco_mouse.button = 2;
+ else if (event.bstate & BUTTON_NUM(3))
+ teco_mouse.button = 3;
+ else if (!(event.bstate & REPORT_MOUSE_POSITION))
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event.bstate & BUTTON_SHIFT)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event.bstate & BUTTON_CTRL)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ if (event.bstate & BUTTON_ALT)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
+#endif /* NCURSES_MOUSE_VERSION >= 2 */
+
static gint
teco_interface_blocking_getch(void)
{
+#if NCURSES_MOUSE_VERSION >= 2
+ /*
+ * FIXME: REPORT_MOUSE_POSITION is necessary at least on
+ * ncurses, so that BUTTONX_RELEASED events are reported.
+ * It does NOT report every cursor movement, though.
+ * What does PDCurses do?
+ */
+ mousemask(teco_ed & TECO_ED_MOUSEKEY
+ ? ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION : 0, NULL);
+#endif
+
/* no special <CTRL/C> handling */
raw();
nodelay(teco_interface.input_pad, FALSE);
@@ -1630,6 +1943,9 @@ teco_interface_event_loop_iter(void)
? teco_interface_blocking_getch()
: GPOINTER_TO_INT(g_queue_pop_head(teco_interface.input_queue));
+ const teco_view_t *last_view = teco_interface_current_view;
+ sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+
switch (key) {
case ERR:
/* shouldn't really happen */
@@ -1697,6 +2013,14 @@ teco_interface_event_loop_iter(void)
#undef FNS
#undef FN
+#if NCURSES_MOUSE_VERSION >= 2
+ case KEY_MOUSE:
+ /* ANY of the mouse events */
+ if (!teco_interface_getmouse(error))
+ return;
+ break;
+#endif
+
/*
* Control keys and keys with printable representation
*/
@@ -1740,6 +2064,14 @@ teco_interface_event_loop_iter(void)
}
}
+ /*
+ * Scintilla has been patched to avoid any automatic scrolling since that
+ * has been benchmarked to be a very costly operation.
+ * Instead we do it only once after every keypress.
+ */
+ if (teco_interface_current_view != last_view ||
+ last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0))
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
teco_interface_refresh();
}
@@ -1755,6 +2087,7 @@ teco_interface_event_loop(GError **error)
static const teco_cmdline_t empty_cmdline; // FIXME
teco_interface_cmdline_update(&empty_cmdline);
teco_interface_msg_clear();
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
teco_interface_refresh();
#ifdef EMCURSES
diff --git a/src/interface-gtk/Makefile.am b/src/interface-gtk/Makefile.am
index 50e5311..e731a21 100644
--- a/src/interface-gtk/Makefile.am
+++ b/src/interface-gtk/Makefile.am
@@ -4,7 +4,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/contrib/rb3ptr \
AM_CFLAGS = -std=gnu11 -Wall -Wno-initializer-overrides -Wno-unused-value
noinst_LTLIBRARIES = libsciteco-interface.la
-libsciteco_interface_la_SOURCES = interface.c \
+libsciteco_interface_la_SOURCES = view.c interface.c \
gtk-info-popup.c gtk-info-popup.h \
gtk-label.c gtk-label.h
diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c
index 4e25224..aaa0a65 100644
--- a/src/interface-gtk/gtk-info-popup.c
+++ b/src/interface-gtk/gtk-info-popup.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -47,12 +47,15 @@ struct _TecoGtkInfoPopup {
GtkAdjustment *hadjustment, *vadjustment;
GtkWidget *flow_box;
+ GdkCursor *cursor; /*< pointer/hand cursor */
GStringChunk *chunk;
teco_stailq_head_t list;
guint idle_id;
gboolean frozen;
};
+static guint teco_gtk_info_popup_clicked_signal;
+
static gboolean teco_gtk_info_popup_scroll_event(GtkWidget *widget, GdkEventScroll *event);
static void teco_gtk_info_popup_show(GtkWidget *widget);
static void teco_gtk_info_popup_vadjustment_changed(GtkAdjustment *vadjustment, GtkWidget *scrollbar);
@@ -72,6 +75,9 @@ teco_gtk_info_popup_finalize(GObject *obj_self)
while ((entry = teco_stailq_remove_head(&self->list)))
g_free(entry);
+ if (self->cursor)
+ g_object_unref(self->cursor);
+
/* chain up to parent class */
G_OBJECT_CLASS(teco_gtk_info_popup_parent_class)->finalize(obj_self);
}
@@ -82,6 +88,31 @@ teco_gtk_info_popup_class_init(TecoGtkInfoPopupClass *klass)
GTK_WIDGET_CLASS(klass)->scroll_event = teco_gtk_info_popup_scroll_event;
GTK_WIDGET_CLASS(klass)->show = teco_gtk_info_popup_show;
G_OBJECT_CLASS(klass)->finalize = teco_gtk_info_popup_finalize;
+
+ teco_gtk_info_popup_clicked_signal =
+ g_signal_new("clicked", G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_ULONG);
+}
+
+static void
+teco_gtk_info_popup_activated_cb(GtkFlowBox *box, GtkFlowBoxChild *child, gpointer user_data)
+{
+ TecoGtkInfoPopup *popup = TECO_GTK_INFO_POPUP(user_data);
+
+ /*
+ * Find the TecoGtkLabel in the flow box child.
+ */
+ GtkWidget *hbox = gtk_bin_get_child(GTK_BIN(child));
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(hbox));
+ GList *entry;
+ for (entry = child_list; entry != NULL && !TECO_IS_GTK_LABEL(entry->data); entry = g_list_next(entry));
+ g_assert(entry != NULL);
+ const teco_string_t *str = teco_gtk_label_get_text(TECO_GTK_LABEL(entry->data));
+
+ g_signal_emit(popup, teco_gtk_info_popup_clicked_signal, 0,
+ str->data, (gulong)str->len);
}
static void
@@ -106,6 +137,8 @@ teco_gtk_info_popup_init(TecoGtkInfoPopup *self)
G_CALLBACK(teco_gtk_info_popup_vadjustment_changed), scrollbar);
self->flow_box = gtk_flow_box_new();
+ g_signal_connect(self->flow_box, "child-activated",
+ G_CALLBACK(teco_gtk_info_popup_activated_cb), self);
/* take as little height as necessary */
gtk_orientable_set_orientation(GTK_ORIENTABLE(self->flow_box),
GTK_ORIENTATION_HORIZONTAL);
@@ -311,12 +344,6 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_set_halign(label, GTK_ALIGN_START);
gtk_widget_set_valign(label, GTK_ALIGN_CENTER);
- /*
- * FIXME: This makes little sense once we've got mouse support.
- * But for the time being, it's a useful setting.
- */
- gtk_label_set_selectable(GTK_LABEL(label), TRUE);
-
switch (type) {
case TECO_POPUP_PLAIN:
gtk_label_set_ellipsize(GTK_LABEL(label), PANGO_ELLIPSIZE_START);
@@ -331,6 +358,16 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ
gtk_widget_show_all(hbox);
gtk_container_add(GTK_CONTAINER(self->flow_box), hbox);
+
+ GtkWidget *flow_box_child = gtk_widget_get_parent(hbox);
+ g_assert(GTK_IS_FLOW_BOX_CHILD(flow_box_child));
+ GdkWindow *window = gtk_widget_get_window(flow_box_child);
+ g_assert(window != NULL);
+
+ if (G_UNLIKELY(!self->cursor))
+ /* we only initialize it now after guaranteed widget realization */
+ self->cursor = gdk_cursor_new_from_name(gdk_window_get_display(window), "pointer");
+ gdk_window_set_cursor(window, self->cursor);
}
static gboolean
@@ -417,12 +454,10 @@ teco_gtk_info_popup_scroll_page(TecoGtkInfoPopup *self)
* Adjust this so only complete entries are shown.
* Effectively, this rounds down to the line height.
*/
- GList *child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
- if (child_list) {
+ g_autoptr(GList) child_list = gtk_container_get_children(GTK_CONTAINER(self->flow_box));
+ if (child_list)
new_value -= (gint)new_value %
gtk_widget_get_allocated_height(GTK_WIDGET(child_list->data));
- g_list_free(child_list);
- }
/* clip to the maximum possible value */
new_value = MIN(new_value, gtk_adjustment_get_upper(adj));
diff --git a/src/interface-gtk/gtk-info-popup.h b/src/interface-gtk/gtk-info-popup.h
index c3a62ec..ad79b84 100644
--- a/src/interface-gtk/gtk-info-popup.h
+++ b/src/interface-gtk/gtk-info-popup.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c
index 50cd345..ef370a2 100644
--- a/src/interface-gtk/gtk-label.c
+++ b/src/interface-gtk/gtk-label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -269,3 +269,9 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len)
gtk_label_set_text(GTK_LABEL(self), plaintext);
}
+
+const teco_string_t *
+teco_gtk_label_get_text(TecoGtkLabel *self)
+{
+ return &self->string;
+}
diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h
index bed6642..c52d073 100644
--- a/src/interface-gtk/gtk-label.h
+++ b/src/interface-gtk/gtk-label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,6 +27,7 @@ G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel)
GtkWidget *teco_gtk_label_new(const gchar *str, gssize len);
void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len);
+const teco_string_t *teco_gtk_label_get_text(TecoGtkLabel *self);
void teco_gtk_label_parse_string(const gchar *str, gssize len,
PangoColor *fg, guint16 fg_alpha,
diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c
index 0dbd2ba..7f58c45 100644
--- a/src/interface-gtk/interface.c
+++ b/src/interface-gtk/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -60,16 +60,17 @@
//#define DEBUG
+static gboolean teco_interface_busy_timeout_cb(gpointer user_data);
+static void teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data);
static void teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
GdkRectangle *allocation,
gpointer user_data);
static void teco_interface_cmdline_commit_cb(GtkIMContext *context, gchar *str,
gpointer user_data);
-static void teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation,
+static gboolean teco_interface_input_cb(GtkWidget *widget, GdkEvent *event,
+ gpointer user_data);
+static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len,
gpointer user_data);
-static gboolean teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event,
- gpointer user_data);
static gboolean teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event,
gpointer user_data);
static gboolean teco_interface_sigterm_handler(gpointer user_data) G_GNUC_UNUSED;
@@ -102,73 +103,6 @@ teco_bgr2rgb(guint32 bgr)
return GUINT32_SWAP_LE_BE(bgr) >> 8;
}
-/*
- * NOTE: The teco_view_t pointer is reused to directly
- * point to the ScintillaObject.
- * This saves one heap object per view.
- */
-
-static void
-teco_view_scintilla_notify(ScintillaObject *sci, gint iMessage,
- SCNotification *notify, gpointer user_data)
-{
- teco_view_process_notify((teco_view_t *)sci, notify);
-}
-
-teco_view_t *
-teco_view_new(void)
-{
- ScintillaObject *sci = SCINTILLA(scintilla_new());
- /*
- * We don't want the object to be destroyed
- * when it is removed from the vbox.
- */
- g_object_ref_sink(sci);
-
- scintilla_set_id(sci, 0);
-
- gtk_widget_set_size_request(GTK_WIDGET(sci), 500, 300);
-
- /*
- * This disables mouse and key events on this view.
- * For some strange reason, masking events on
- * the event box does NOT work.
- *
- * NOTE: Scroll events are still allowed - scrolling
- * is currently not under direct control of SciTECO
- * (i.e. it is OK the side effects of scrolling are not
- * tracked).
- */
- gtk_widget_set_can_focus(GTK_WIDGET(sci), FALSE);
- gint events = gtk_widget_get_events(GTK_WIDGET(sci));
- events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
- events &= ~(GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
- gtk_widget_set_events(GTK_WIDGET(sci), events);
-
- g_signal_connect(sci, SCINTILLA_NOTIFY,
- G_CALLBACK(teco_view_scintilla_notify), NULL);
-
- return (teco_view_t *)sci;
-}
-
-static inline GtkWidget *
-teco_view_get_widget(teco_view_t *ctx)
-{
- return GTK_WIDGET(ctx);
-}
-
-sptr_t
-teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
-{
- return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
-}
-
-void
-teco_view_free(teco_view_t *ctx)
-{
- g_object_unref(teco_view_get_widget(ctx));
-}
-
static struct {
GtkCssProvider *css_var_provider;
@@ -198,6 +132,7 @@ static struct {
GtkIMContext *input_method;
GtkWidget *popup_widget;
+ gsize popup_prefix_len;
GtkWidget *current_view_widget;
@@ -240,7 +175,7 @@ teco_interface_init(void)
G_CALLBACK(teco_interface_window_delete_cb), NULL);
g_signal_connect(teco_interface.window, "key-press-event",
- G_CALLBACK(teco_interface_key_pressed_cb), NULL);
+ G_CALLBACK(teco_interface_input_cb), NULL);
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
@@ -313,8 +248,20 @@ teco_interface_init(void)
gtk_box_pack_start(GTK_BOX(overlay_vbox), teco_interface.event_box_widget,
TRUE, TRUE, 0);
- g_signal_connect(teco_interface.event_box_widget, "size-allocate",
- G_CALLBACK(teco_interface_size_allocate_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "realize",
+ G_CALLBACK(teco_interface_event_box_realized_cb), NULL);
+
+ gint events = gtk_widget_get_events(teco_interface.event_box_widget);
+ events |= GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK;
+ gtk_widget_set_events(teco_interface.event_box_widget, events);
+
+ g_signal_connect(teco_interface.event_box_widget, "button-press-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "button-release-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
+ g_signal_connect(teco_interface.event_box_widget, "scroll-event",
+ G_CALLBACK(teco_interface_input_cb), NULL);
teco_interface.message_bar_widget = gtk_info_bar_new();
gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar");
@@ -350,7 +297,7 @@ teco_interface_init(void)
/* we will forward key events, so the view should only react to text insertion */
teco_view_ssm(teco_interface.cmdline_view, SCI_CLEARALLCMDKEYS, 0, 0);
- GtkWidget *cmdline_widget = teco_view_get_widget(teco_interface.cmdline_view);
+ GtkWidget *cmdline_widget = GTK_WIDGET(teco_interface.cmdline_view);
gtk_widget_set_name(cmdline_widget, "sciteco-cmdline");
g_signal_connect(cmdline_widget, "size-allocate",
G_CALLBACK(teco_interface_cmdline_size_allocate_cb), NULL);
@@ -373,6 +320,8 @@ teco_interface_init(void)
*/
teco_interface.popup_widget = teco_gtk_info_popup_new();
gtk_widget_set_name(teco_interface.popup_widget, "sciteco-info-popup");
+ g_signal_connect(teco_interface.popup_widget, "clicked",
+ G_CALLBACK(teco_interface_popup_clicked_cb), NULL);
gtk_overlay_add_overlay(GTK_OVERLAY(overlay_widget), teco_interface.popup_widget);
g_signal_connect(overlay_widget, "get-child-position",
G_CALLBACK(teco_gtk_info_popup_get_position_in_overlay), NULL);
@@ -389,6 +338,17 @@ teco_interface_init(void)
teco_interface_cmdline_update(&empty_cmdline);
}
+static void
+teco_interface_set_cursor(GtkWidget *widget, const gchar *name)
+{
+ GdkWindow *window = gtk_widget_get_window(widget);
+ g_assert(window != NULL);
+ GdkDisplay *display = gdk_window_get_display(window);
+
+ g_autoptr(GdkCursor) cursor = name ? gdk_cursor_new_from_name(display, name) : NULL;
+ gdk_window_set_cursor(window, cursor);
+}
+
GOptionGroup *
teco_interface_get_options(void)
{
@@ -746,12 +706,16 @@ teco_interface_popup_add(teco_popup_entry_type_t type, const gchar *name, gsize
}
void
-teco_interface_popup_show(void)
+teco_interface_popup_show(gsize prefix_len)
{
- if (gtk_widget_get_visible(teco_interface.popup_widget))
- teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
- else
- gtk_widget_show(teco_interface.popup_widget);
+ teco_interface.popup_prefix_len = prefix_len;
+ gtk_widget_show(teco_interface.popup_widget);
+}
+
+void
+teco_interface_popup_scroll(void)
+{
+ teco_gtk_info_popup_scroll_page(TECO_GTK_INFO_POPUP(teco_interface.popup_widget));
}
gboolean
@@ -876,7 +840,7 @@ teco_interface_set_css_variables(teco_view_t *view)
* This cannot be done via CSS or Scintilla messages.
* Currently, it is always exactly one line high in order to mimic the Curses UI.
*/
- gtk_widget_set_size_request(teco_view_get_widget(teco_interface.cmdline_view), -1, text_height);
+ gtk_widget_set_size_request(GTK_WIDGET(teco_interface.cmdline_view), -1, text_height);
}
static void
@@ -910,19 +874,12 @@ teco_interface_refresh(gboolean current_view_changed)
gtk_container_remove(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
- teco_interface.current_view_widget = teco_view_get_widget(teco_interface_current_view);
+ teco_interface.current_view_widget = GTK_WIDGET(teco_interface_current_view);
gtk_container_add(GTK_CONTAINER(teco_interface.event_box_widget),
teco_interface.current_view_widget);
gtk_widget_show(teco_interface.current_view_widget);
}
-
- /*
- * Scintilla has been patched to avoid any automatic scrolling since that
- * has been benchmarked to be a very costly operation.
- * Instead we do it only once after every keypress.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
}
static void
@@ -977,7 +934,7 @@ teco_interface_get_ansi_key(GdkEventKey *event)
static gboolean
teco_interface_handle_key_press(GdkEventKey *event, GError **error)
{
- const teco_view_t *last_view = teco_interface_current_view;
+ g_assert(event->type == GDK_KEY_PRESS);
switch (event->keyval) {
case GDK_KEY_Escape:
@@ -1105,10 +1062,81 @@ teco_interface_handle_key_press(GdkEventKey *event, GError **error)
gtk_im_context_filter_keypress(teco_interface.input_method, event);
}
- teco_interface_refresh(teco_interface_current_view != last_view);
return TRUE;
}
+static gboolean
+teco_interface_handle_mouse_button(GdkEventButton *event, GError **error)
+{
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ teco_mouse.type = TECO_MOUSE_PRESSED;
+ break;
+ case GDK_BUTTON_RELEASE:
+ teco_mouse.type = TECO_MOUSE_RELEASED;
+ break;
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ default:
+ /* delivered in addition to GDK_BUTTON_PRESS */
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = event->button;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
+static gboolean
+teco_interface_handle_scroll(GdkEventScroll *event, GError **error)
+{
+ g_assert(event->type == GDK_SCROLL);
+
+ /*
+ * FIXME: Do we have to support GDK_SCROLL_SMOOTH?
+ */
+ switch (event->direction) {
+ case GDK_SCROLL_UP:
+ teco_mouse.type = TECO_MOUSE_SCROLLUP;
+ break;
+ case GDK_SCROLL_DOWN:
+ teco_mouse.type = TECO_MOUSE_SCROLLDOWN;
+ break;
+ default:
+ return TRUE;
+ }
+
+ teco_mouse.x = event->x;
+ teco_mouse.y = event->y;
+ teco_mouse.button = -1;
+
+ teco_mouse.mods = 0;
+ if (event->state & GDK_SHIFT_MASK)
+ teco_mouse.mods |= TECO_MOUSE_SHIFT;
+ if (event->state & GDK_CONTROL_MASK)
+ teco_mouse.mods |= TECO_MOUSE_CTRL;
+ /*
+ * NOTE: GTK returns MOD1 *without* SHIFT for ALT.
+ */
+ if ((event->state & (GDK_MOD1_MASK | GDK_SHIFT_MASK)) == GDK_MOD1_MASK)
+ teco_mouse.mods |= TECO_MOUSE_ALT;
+
+ return teco_cmdline_keymacro("MOUSE", -1, error);
+}
+
gboolean
teco_interface_event_loop(GError **error)
{
@@ -1251,6 +1279,29 @@ teco_interface_cleanup(void)
*/
/**
+ * Called some time after processing an input event in order to show
+ * business.
+ *
+ * The delay avoids cursor flickering during normal typing.
+ *
+ * @fixme It would be nicer to set the cursor for the entire window,
+ * but that would apparently require another GtkEventBox, spanning everything.
+ */
+static gboolean
+teco_interface_busy_timeout_cb(gpointer user_data)
+{
+ teco_interface_set_cursor(teco_interface.event_box_widget, "wait");
+ return G_SOURCE_REMOVE;
+}
+
+static void
+teco_interface_event_box_realized_cb(GtkWidget *widget, gpointer user_data)
+{
+ /* It's only now safe to get the GdkWindow. */
+ teco_interface_set_cursor(widget, "text");
+}
+
+/**
* Called when the commandline widget is resized.
* This should ensure that the caret jumps to the middle of the command line,
* imitating the behaviour of the current Curses command line.
@@ -1263,26 +1314,16 @@ teco_interface_cmdline_size_allocate_cb(GtkWidget *widget,
CARET_SLOP | CARET_EVEN, allocation->width/2);
}
-static void
-teco_interface_size_allocate_cb(GtkWidget *widget,
- GdkRectangle *allocation, gpointer user_data)
-{
- /*
- * This especially ensures that the caret is visible after startup.
- */
- teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
-}
-
static gboolean
-teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
static gboolean recursed = FALSE;
- g_autoptr(GError) error = NULL;
#ifdef DEBUG
- g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
- event->string, *event->string,
- event->state & GDK_SHIFT_MASK, event->state & GDK_CONTROL_MASK);
+ if (event->type == GDK_KEY_PRESS)
+ g_printf("KEY \"%s\" (%d) SHIFT=%d CNTRL=%d\n",
+ event->key.string, *event->key.string,
+ event->key.state & GDK_SHIFT_MASK, event->key.state & GDK_CONTROL_MASK);
#endif
if (recursed) {
@@ -1295,8 +1336,9 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
* during execution, but the current implementation is
* probably easier.
*/
- if (event->state & GDK_CONTROL_MASK &&
- gdk_keyval_to_upper(event->keyval) == GDK_KEY_C)
+ if (event->type == GDK_KEY_PRESS &&
+ event->key.state & GDK_CONTROL_MASK &&
+ gdk_keyval_to_upper(event->key.keyval) == GDK_KEY_C)
/*
* Handle asynchronous interruptions if CTRL+C is pressed.
* If the execution thread is currently blocking,
@@ -1305,20 +1347,26 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_interrupted = TRUE;
else
g_queue_push_tail(teco_interface.event_queue,
- gdk_event_copy((GdkEvent *)event));
+ gdk_event_copy(event));
return TRUE;
}
recursed = TRUE;
+ GSource *busy_timeout = g_timeout_source_new(500); /* ms */
+ g_source_set_callback(busy_timeout, teco_interface_busy_timeout_cb, NULL, NULL);
+ g_source_attach(busy_timeout, NULL);
+
teco_memory_start_limiting();
- g_queue_push_tail(teco_interface.event_queue, gdk_event_copy((GdkEvent *)event));
+ g_queue_push_tail(teco_interface.event_queue, gdk_event_copy(event));
GdkWindow *top_window = gdk_window_get_toplevel(gtk_widget_get_window(teco_interface.window));
do {
+ g_autoptr(GError) error = NULL;
+
/*
* The event queue might be filled when pressing keys when SciTECO
* is busy executing code.
@@ -1334,10 +1382,37 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
*/
gdk_window_freeze_updates(top_window);
+ const teco_view_t *last_view = teco_interface_current_view;
+ sptr_t last_pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0);
+
teco_interrupted = FALSE;
- teco_interface_handle_key_press(&event->key, &error);
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ teco_interface_handle_key_press(&event->key, &error);
+ break;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ teco_interface_handle_mouse_button(&event->button, &error);
+ break;
+ case GDK_SCROLL:
+ teco_interface_handle_scroll(&event->scroll, &error);
+ break;
+ default:
+ g_assert_not_reached();
+ }
teco_interrupted = FALSE;
+ teco_interface_refresh(teco_interface_current_view != last_view);
+ /*
+ * Scintilla has been patched to avoid any automatic scrolling since that
+ * has been benchmarked to be a very costly operation.
+ * Instead we do it only once after every keypress.
+ */
+ if (last_pos != teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0))
+ teco_interface_ssm(SCI_SCROLLCARET, 0, 0);
+
gdk_window_thaw_updates(top_window);
if (g_error_matches(error, TECO_ERROR, TECO_ERROR_QUIT)) {
@@ -1355,10 +1430,36 @@ teco_interface_key_pressed_cb(GtkWidget *widget, GdkEventKey *event, gpointer us
teco_memory_stop_limiting();
+ g_source_destroy(busy_timeout);
+ g_source_unref(busy_timeout);
+ teco_interface_set_cursor(teco_interface.event_box_widget, "text");
+
recursed = FALSE;
return TRUE;
}
+static void
+teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong len, gpointer user_data)
+{
+ g_assert(len >= teco_interface.popup_prefix_len);
+ const teco_string_t insert = {str+teco_interface.popup_prefix_len, len-teco_interface.popup_prefix_len};
+ teco_machine_t *machine = &teco_cmdline.machine.parent;
+
+ const teco_view_t *last_view = teco_interface_current_view;
+
+ /*
+ * NOTE: It shouldn't really be necessary to catch TECO_ERROR_QUIT here.
+ * A auto completion should never result in program termination.
+ */
+ if (machine->current->insert_completion_cb &&
+ !machine->current->insert_completion_cb(machine, &insert, NULL))
+ return;
+ teco_interface_popup_clear();
+ teco_interface_cmdline_update(&teco_cmdline);
+
+ teco_interface_refresh(teco_interface_current_view != last_view);
+}
+
static gboolean
teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer user_data)
{
@@ -1374,7 +1475,7 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer
close_event->key.window = gtk_widget_get_parent_window(widget);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(widget, &close_event->key, NULL);
+ return teco_interface_input_cb(widget, close_event, NULL);
}
static gboolean
@@ -1386,5 +1487,5 @@ teco_interface_sigterm_handler(gpointer user_data)
g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS);
close_event->key.keyval = GDK_KEY_Close;
- return teco_interface_key_pressed_cb(teco_interface.window, &close_event->key, NULL);
+ return teco_interface_input_cb(teco_interface.window, close_event, NULL);
}
diff --git a/src/interface-gtk/view.c b/src/interface-gtk/view.c
new file mode 100644
index 0000000..ef839d6
--- /dev/null
+++ b/src/interface-gtk/view.c
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012-2025 Robin Haberkorn
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+
+#include <glib.h>
+
+#include <gtk/gtk.h>
+
+#include <Scintilla.h>
+#include <ScintillaWidget.h>
+
+#include "view.h"
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(ScintillaObject, g_object_unref)
+
+#define TECO_TYPE_VIEW teco_view_get_type()
+G_DECLARE_FINAL_TYPE(TecoView, teco_view, TECO, VIEW, ScintillaObject)
+
+struct _TecoView {
+ ScintillaObject parent_instance;
+ /** current size allocation */
+ GdkRectangle allocation;
+};
+
+G_DEFINE_TYPE(TecoView, teco_view, SCINTILLA_TYPE_OBJECT)
+
+static void
+teco_view_scintilla_notify_cb(ScintillaObject *sci, gint iMessage, SCNotification *notify)
+{
+ teco_view_process_notify((teco_view_t *)TECO_VIEW(sci), notify);
+}
+
+/**
+ * Called when the view is size allocated.
+ *
+ * This especially ensures that the caret is visible after startup and when
+ * opening files on specific lines.
+ * It's important to scroll the caret only when the size actually changes,
+ * so we do not interfere with mouse scrolling.
+ * That callback is invoked even if the size does not change, so that's why
+ * we have to store the current allocation in teco_view_t.
+ * Calling it once is unfortunately not sufficient since the window size
+ * can change during startup.
+ */
+static void
+teco_view_size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation)
+{
+ /* chain to parent class */
+ GTK_WIDGET_CLASS(teco_view_parent_class)->size_allocate(widget, allocation);
+
+ TecoView *view = TECO_VIEW(widget);
+
+ if (allocation->width == view->allocation.width && allocation->height == view->allocation.height)
+ return;
+ teco_view_ssm((teco_view_t *)view, SCI_SCROLLCARET, 0, 0);
+ memcpy(&view->allocation, allocation, sizeof(view->allocation));
+}
+
+teco_view_t *
+teco_view_new(void)
+{
+ TecoView *ctx = TECO_VIEW(g_object_new(TECO_TYPE_VIEW, NULL));
+ /*
+ * We don't want the object to be destroyed
+ * when it is removed from the vbox.
+ */
+ g_object_ref_sink(ctx);
+
+ scintilla_set_id(SCINTILLA(ctx), 0);
+
+ gtk_widget_set_size_request(GTK_WIDGET(ctx), 500, 300);
+
+ /*
+ * This disables mouse and key events on this view.
+ * For some strange reason, masking events on
+ * the event box does NOT work.
+ */
+ gtk_widget_set_can_focus(GTK_WIDGET(ctx), FALSE);
+ gint events = gtk_widget_get_events(GTK_WIDGET(ctx));
+ events &= ~(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SCROLL_MASK |
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK);
+ gtk_widget_set_events(GTK_WIDGET(ctx), events);
+
+ return (teco_view_t *)ctx;
+}
+
+static void
+teco_view_class_init(TecoViewClass *klass)
+{
+ SCINTILLA_CLASS(klass)->notify = teco_view_scintilla_notify_cb;
+ GTK_WIDGET_CLASS(klass)->size_allocate = teco_view_size_allocate_cb;
+}
+
+static void teco_view_init(TecoView *self) {}
+
+sptr_t
+teco_view_ssm(teco_view_t *ctx, unsigned int iMessage, uptr_t wParam, sptr_t lParam)
+{
+ return scintilla_send_message(SCINTILLA(ctx), iMessage, wParam, lParam);
+}
+
+void
+teco_view_free(teco_view_t *ctx)
+{
+ g_object_unref(ctx);
+}
diff --git a/src/interface.c b/src/interface.c
index 2973dd2..9ec1bed 100644
--- a/src/interface.c
+++ b/src/interface.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -43,6 +43,9 @@ TECO_DEFINE_UNDO_CALL(teco_interface_ssm, unsigned int, uptr_t, sptr_t);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_qreg, const teco_qreg_t *);
TECO_DEFINE_UNDO_CALL(teco_interface_info_update_buffer, const teco_buffer_t *);
+/** Last mouse event */
+teco_mouse_t teco_mouse = {0};
+
typedef struct {
teco_string_t str;
gchar name[];
diff --git a/src/interface.h b/src/interface.h
index 80da8d9..33b094b 100644
--- a/src/interface.h
+++ b/src/interface.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -132,7 +132,9 @@ typedef enum {
void teco_interface_popup_add(teco_popup_entry_type_t type,
const gchar *name, gsize name_len, gboolean highlight);
/** @pure */
-void teco_interface_popup_show(void);
+void teco_interface_popup_show(gsize prefix_len);
+/** @pure */
+void teco_interface_popup_scroll(void);
/** @pure */
gboolean teco_interface_popup_is_shown(void);
/** @pure */
@@ -141,6 +143,28 @@ void teco_interface_popup_clear(void);
/** @pure */
gboolean teco_interface_is_interrupted(void);
+typedef struct {
+ enum {
+ TECO_MOUSE_PRESSED = 1,
+ TECO_MOUSE_RELEASED,
+ TECO_MOUSE_SCROLLUP,
+ TECO_MOUSE_SCROLLDOWN
+ } type;
+
+ guint x; /*< X-coordinate relative to view */
+ guint y; /*< Y-coordinate relative to view */
+
+ gint button; /*< number of pressed mouse button or -1 */
+
+ enum {
+ TECO_MOUSE_SHIFT = (1 << 0),
+ TECO_MOUSE_CTRL = (1 << 1),
+ TECO_MOUSE_ALT = (1 << 2)
+ } mods;
+} teco_mouse_t;
+
+extern teco_mouse_t teco_mouse;
+
/** @pure main entry point */
gboolean teco_interface_event_loop(GError **error);
diff --git a/src/lexer.c b/src/lexer.c
index c0c7847..1124b99 100644
--- a/src/lexer.c
+++ b/src/lexer.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/lexer.h b/src/lexer.h
index 87b0d0f..2b011be 100644
--- a/src/lexer.h
+++ b/src/lexer.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/list.h b/src/list.h
index 156d4a7..2a7ab91 100644
--- a/src/list.h
+++ b/src/list.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/main.c b/src/main.c
index e93a3de..7849798 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -141,7 +141,7 @@ teco_process_options(gchar ***argv)
g_autoptr(GError) error = NULL;
- g_autoptr(GOptionContext) options = g_option_context_new("[--] [SCRIPT] [ARGUMENT...]");
+ g_autoptr(GOptionContext) options = g_option_context_new("[--|-S] [SCRIPT] [ARGUMENT...]");
g_option_context_set_summary(
options,
@@ -168,27 +168,48 @@ teco_process_options(gchar ***argv)
* in many situations.
* It is also strictly required to make hash-bang lines like
* #!/usr/bin/sciteco -m
- * work.
+ * work (without additional --).
*/
g_option_context_set_strict_posix(options, TRUE);
+ /*
+ * The first unknown parameter will be left in argv and
+ * terminates option parsing (see above).
+ * This means we can use "-S" as an alternative to "--",
+ * that is always preserved and passed down to the macro.
+ */
+ g_option_context_set_ignore_unknown_options(options, TRUE);
+
if (!g_option_context_parse_strv(options, argv, &error)) {
g_fprintf(stderr, "Option parsing failed: %s\n",
error->message);
exit(EXIT_FAILURE);
}
- /*
- * GOption will NOT remove "--" if followed by an
- * option-argument, which may interfer with scripts
- * doing their own option handling and interpreting "--".
- *
- * NOTE: This is still true if we're parsing in GNU-mode
- * and "--" is not the first non-option argument as in
- * sciteco foo -- -C bar.
- */
- if ((*argv)[0] && !g_strcmp0((*argv)[1], "--"))
+ if ((*argv)[0] && !g_strcmp0((*argv)[1], "-S")) {
+ /* translate -S to --, this is always passed down */
+ (*argv)[1][1] = '-';
+ } else if ((*argv)[0] && !g_strcmp0((*argv)[1], "--")) {
+ /*
+ * GOption will NOT remove "--" if followed by an
+ * option-argument, which may interfer with scripts
+ * doing their own option handling and interpreting "--".
+ * Otherwise, GOption will always remove "--".
+ *
+ * NOTE: This is still true if we're parsing in GNU-mode
+ * and "--" is not the first non-option argument as in
+ * sciteco foo -- -C bar.
+ */
g_free(teco_strv_remove(*argv, 1));
+ } else if ((*argv)[0] && (*argv)[1] && *(*argv)[1] == '-') {
+ /*
+ * GOption does not remove "--" if it is followed by "-",
+ * so if the first parameter starts with "-", we know it's
+ * not a known built-in parameter.
+ */
+ g_fprintf(stderr, "Unknown option \"%s\"\n", (*argv)[1]);
+ exit(EXIT_FAILURE);
+ }
gchar *mung_filename = NULL;
diff --git a/src/memory.c b/src/memory.c
index 653d1ef..ea056bc 100644
--- a/src/memory.c
+++ b/src/memory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/memory.h b/src/memory.h
index 39f8319..ae7b506 100644
--- a/src/memory.h
+++ b/src/memory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/parser.c b/src/parser.c
index 018e35f..295c635 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -447,11 +447,14 @@ teco_state_stringbuilding_start_input(teco_machine_stringbuilding_t *ctx, gunich
/* in cmdline.c */
gboolean teco_state_stringbuilding_start_process_edit_cmd(teco_machine_stringbuilding_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+gboolean teco_state_stringbuilding_insert_completion(teco_machine_stringbuilding_t *ctx, const teco_string_t *str, GError **error);
TECO_DEFINE_STATE(teco_state_stringbuilding_start,
.is_start = TRUE,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)
- teco_state_stringbuilding_start_process_edit_cmd
+ teco_state_stringbuilding_start_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)
+ teco_state_stringbuilding_insert_completion
);
static teco_state_t *
diff --git a/src/parser.h b/src/parser.h
index 7ca5ab3..fe8e764 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -110,6 +110,7 @@ typedef gboolean (*teco_state_refresh_cb_t)(teco_machine_t *ctx, GError **error)
typedef gboolean (*teco_state_end_of_macro_cb_t)(teco_machine_t *ctx, GError **error);
typedef gboolean (*teco_state_process_edit_cmd_cb_t)(teco_machine_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+typedef gboolean (*teco_state_insert_completion_cb_t)(teco_machine_t *ctx, const teco_string_t *str, GError **error);
typedef enum {
TECO_KEYMACRO_MASK_START = (1 << 0),
@@ -187,6 +188,19 @@ struct teco_state_t {
teco_state_process_edit_cmd_cb_t process_edit_cmd_cb;
/**
+ * Insert completion after clicking an entry in the popup
+ * window.
+ *
+ * All implementations of this method are currently
+ * defined in cmdline.c.
+ *
+ * It can be NULL if not required.
+ *
+ * @fixme Perhaps move all implementations to interface.c.
+ */
+ teco_state_insert_completion_cb_t insert_completion_cb;
+
+ /**
* Whether this state is a start state (i.e. not within any
* escape sequence etc.).
* This is separate of TECO_KEYMACRO_MASK_START which is set
@@ -241,11 +255,12 @@ gboolean teco_state_process_edit_cmd(teco_machine_t *ctx, teco_machine_t *parent
#define TECO_DEFINE_STATE(NAME, ...) \
/** @ingroup states */ \
teco_state_t NAME = { \
- .initial_cb = NULL, /* do nothing */ \
+ .initial_cb = NULL, /* do nothing */ \
.input_cb = (teco_state_input_cb_t)NAME##_input, /* always required */ \
- .refresh_cb = NULL, /* do nothing */ \
+ .refresh_cb = NULL, /* do nothing */ \
.end_of_macro_cb = teco_state_end_of_macro, \
.process_edit_cmd_cb = teco_state_process_edit_cmd, \
+ .insert_completion_cb = NULL, /* do nothing */ \
.is_start = FALSE, \
.keymacro_mask = TECO_KEYMACRO_MASK_DEFAULT, \
.style = SCE_SCITECO_DEFAULT, \
@@ -552,7 +567,10 @@ teco_state_t *teco_state_expectstring_input(teco_machine_main_t *ctx, gunichar c
gboolean teco_state_expectstring_refresh(teco_machine_main_t *ctx, GError **error);
/* in cmdline.c */
-gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_expectstring_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTSTRING
@@ -577,6 +595,8 @@ gboolean teco_state_expectstring_process_edit_cmd(teco_machine_main_t *ctx, teco
.refresh_cb = (teco_state_refresh_cb_t)teco_state_expectstring_refresh, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectstring_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectstring_insert_completion, \
.keymacro_mask = TECO_KEYMACRO_MASK_STRING, \
.style = SCE_SCITECO_STRING, \
.expectstring.string_building = TRUE, \
@@ -591,6 +611,7 @@ gboolean teco_state_expectfile_process(teco_machine_main_t *ctx, const teco_stri
/* in cmdline.c */
gboolean teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectfile_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTFILE
@@ -601,12 +622,15 @@ gboolean teco_state_expectfile_process_edit_cmd(teco_machine_main_t *ctx, teco_m
TECO_DEFINE_STATE_EXPECTSTRING(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectfile_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectfile_insert_completion, \
.expectstring.process_cb = teco_state_expectfile_process, \
##__VA_ARGS__ \
)
/* in cmdline.c */
gboolean teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectdir_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str, GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTDIR
@@ -617,5 +641,7 @@ gboolean teco_state_expectdir_process_edit_cmd(teco_machine_main_t *ctx, teco_ma
TECO_DEFINE_STATE_EXPECTFILE(NAME, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectdir_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectdir_insert_completion, \
##__VA_ARGS__ \
)
diff --git a/src/qreg-commands.c b/src/qreg-commands.c
index 0a64b2f..ebf6caa 100644
--- a/src/qreg-commands.c
+++ b/src/qreg-commands.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/qreg-commands.h b/src/qreg-commands.h
index d999587..6dbd1c4 100644
--- a/src/qreg-commands.h
+++ b/src/qreg-commands.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,7 +37,10 @@ gboolean teco_state_expectqreg_initial(teco_machine_main_t *ctx, GError **error)
teco_state_t *teco_state_expectqreg_input(teco_machine_main_t *ctx, gunichar chr, GError **error);
/* in cmdline.c */
-gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_expectqreg_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/**
* @interface TECO_DEFINE_STATE_EXPECTQREG
@@ -56,6 +59,8 @@ gboolean teco_state_expectqreg_process_edit_cmd(teco_machine_main_t *ctx, teco_m
.initial_cb = (teco_state_initial_cb_t)teco_state_expectqreg_initial, \
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t) \
teco_state_expectqreg_process_edit_cmd, \
+ .insert_completion_cb = (teco_state_insert_completion_cb_t) \
+ teco_state_expectqreg_insert_completion, \
.style = SCE_SCITECO_QREG, \
.expectqreg.type = TECO_QREG_REQUIRED, \
.expectqreg.got_register_cb = NAME##_got_register, /* always required */ \
diff --git a/src/qreg.c b/src/qreg.c
index 271e7cb..4beea74 100644
--- a/src/qreg.c
+++ b/src/qreg.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -1445,11 +1445,15 @@ teco_state_qregspec_start_input(teco_machine_qregspec_t *ctx, gunichar chr, GErr
}
/* in cmdline.c */
-gboolean teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_qregspec_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_qregspec_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str,
+ GError **error);
TECO_DEFINE_STATE(teco_state_qregspec_start,
.is_start = TRUE,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_insert_completion
);
static teco_state_t *
@@ -1585,9 +1589,12 @@ teco_state_qregspec_string_input(teco_machine_qregspec_t *ctx, gunichar chr, GEr
/* in cmdline.c */
gboolean teco_state_qregspec_string_process_edit_cmd(teco_machine_qregspec_t *ctx, teco_machine_t *parent_ctx,
gunichar key, GError **error);
+gboolean teco_state_qregspec_string_insert_completion(teco_machine_qregspec_t *ctx, const teco_string_t *str,
+ GError **error);
TECO_DEFINE_STATE(teco_state_qregspec_string,
- .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_string_process_edit_cmd
+ .process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_qregspec_string_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_qregspec_string_insert_completion
);
/** @static @memberof teco_machine_qregspec_t */
diff --git a/src/qreg.h b/src/qreg.h
index a29b6ed..4260202 100644
--- a/src/qreg.h
+++ b/src/qreg.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/rb3str.c b/src/rb3str.c
index d51ac5d..276b624 100644
--- a/src/rb3str.c
+++ b/src/rb3str.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -143,7 +143,7 @@ teco_rb3str_auto_complete(teco_rb3str_tree_t *tree, gboolean case_sensitive,
cur->key.data, cur->key.len, FALSE);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(str_len);
}
return prefixed_entries == 1;
diff --git a/src/rb3str.h b/src/rb3str.h
index adf5f89..466cf90 100644
--- a/src/rb3str.h
+++ b/src/rb3str.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/ring.c b/src/ring.c
index 8ce6948..0f1d67f 100644
--- a/src/ring.c
+++ b/src/ring.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -413,7 +413,7 @@ teco_state_edit_file_initial(teco_machine_main_t *ctx, GError **error)
strlen(filename), cur == teco_ring_current);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(0);
} else if (id > 0) {
allow_filename = FALSE;
if (!teco_current_doc_undo_edit(error) ||
@@ -480,6 +480,10 @@ teco_state_edit_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* area.
* Naturally this only has any effect in interactive
* mode.
+ * Note that you can also click on these entries \(em
+ * if mouse support is enabled \(em to immediately switch
+ * to any file in the buffer ring just like with any
+ * other popup.
*
* <file> may also be a glob pattern, in which case
* all regular files matching the pattern are opened/edited.
@@ -577,3 +581,61 @@ teco_state_save_file_done(teco_machine_main_t *ctx, const teco_string_t *str, GE
* characters are enabled by default.
*/
TECO_DEFINE_STATE_EXPECTFILE(teco_state_save_file);
+
+/*$ EF close
+ * [bool]EF -- Remove buffer from ring
+ * -EF
+ * :EF
+ *
+ * Removes buffer from buffer ring, effectively
+ * closing it.
+ * If the buffer is dirty (modified), EF will yield
+ * an error.
+ * <bool> may be a specified to enforce closing dirty
+ * buffers.
+ * If it is a Failure condition boolean (negative),
+ * the buffer will be closed unconditionally.
+ * If <bool> is absent, the sign prefix (1 or -1) will
+ * be implied, so \(lq-EF\(rq will always close the buffer.
+ *
+ * When colon-modified, <bool> is ignored and \fBEF\fP
+ * will save the buffer before closing.
+ * The file is always written, unlike \(lq:EX\(rq which
+ * saves only dirty buffers.
+ * This can fail of course, e.g. when called on the unnamed
+ * buffer.
+ *
+ * It is noteworthy that EF will be executed immediately in
+ * interactive mode but can be rubbed out at a later time
+ * to reopen the file.
+ * Closed files are kept in memory until the command line
+ * is terminated.
+ */
+void
+teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error)
+{
+ if (teco_qreg_current) {
+ const teco_string_t *name = &teco_qreg_current->head.name;
+ g_autofree gchar *name_printable = teco_string_echo(name->data, name->len);
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Q-Register \"%s\" currently edited", name_printable);
+ return;
+ }
+
+ if (teco_machine_main_eval_colon(ctx) > 0) {
+ if (!teco_buffer_save(teco_ring_current, NULL, error))
+ return;
+ } else {
+ teco_int_t v;
+ if (!teco_expressions_pop_num_calc(&v, teco_num_sign, error))
+ return;
+ if (teco_is_failure(v) && teco_ring_current->dirty) {
+ g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED,
+ "Buffer \"%s\" is dirty",
+ teco_ring_current->filename ? : "(Unnamed)");
+ return;
+ }
+ }
+
+ teco_ring_close(error);
+}
diff --git a/src/ring.h b/src/ring.h
index 833d052..34293b9 100644
--- a/src/ring.h
+++ b/src/ring.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -100,6 +100,8 @@ void teco_ring_cleanup(void);
TECO_DECLARE_STATE(teco_state_edit_file);
TECO_DECLARE_STATE(teco_state_save_file);
+void teco_state_ecommand_close(teco_machine_main_t *ctx, GError **error);
+
/*
* Helper functions applying to any current
* document (whether a buffer or QRegister).
diff --git a/src/sciteco.h b/src/sciteco.h
index 7fe09d4..4868303 100644
--- a/src/sciteco.h
+++ b/src/sciteco.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -89,7 +89,7 @@ enum {
TECO_ED_AUTOCASEFOLD = (1 << 3),
TECO_ED_AUTOEOL = (1 << 4),
TECO_ED_HOOKS = (1 << 5),
- //TECO_ED_MOUSEKEY = (1 << 6),
+ TECO_ED_MOUSEKEY = (1 << 6),
TECO_ED_SHELLEMU = (1 << 7),
TECO_ED_OSC52 = (1 << 8),
TECO_ED_ICONS = (1 << 9)
diff --git a/src/search.c b/src/search.c
index 1945f5c..3b2ebe3 100644
--- a/src/search.c
+++ b/src/search.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/search.h b/src/search.h
index 40ab4d8..621fdd1 100644
--- a/src/search.h
+++ b/src/search.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/spawn.c b/src/spawn.c
index fb7a946..e44ecc4 100644
--- a/src/spawn.c
+++ b/src/spawn.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -129,11 +129,13 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error)
teco_string_t comspec;
if (!reg->vtable->get_string(reg, &comspec.data, &comspec.len, NULL, error))
return NULL;
+ if (teco_string_contains(&comspec, '\0')) {
+ teco_string_clear(&comspec);
+ teco_error_qregcontainsnull_set(error, "$COMSPEC", 8, FALSE);
+ return NULL;
+ }
argv = g_new(gchar *, 5);
- /*
- * FIXME: What if $COMSPEC contains null-bytes?
- */
argv[0] = comspec.data;
argv[1] = g_strdup("/q");
argv[2] = g_strdup("/c");
@@ -148,11 +150,13 @@ teco_parse_shell_command_line(const gchar *cmdline, GError **error)
teco_string_t shell;
if (!reg->vtable->get_string(reg, &shell.data, &shell.len, NULL, error))
return NULL;
+ if (teco_string_contains(&shell, '\0')) {
+ teco_string_clear(&shell);
+ teco_error_qregcontainsnull_set(error, "$SHELL", 6, FALSE);
+ return NULL;
+ }
argv = g_new(gchar *, 4);
- /*
- * FIXME: What if $SHELL contains null-bytes?
- */
argv[0] = shell.data;
argv[1] = g_strdup("-c");
argv[2] = g_strdup(cmdline);
diff --git a/src/spawn.h b/src/spawn.h
index 312de6e..ef210e9 100644
--- a/src/spawn.h
+++ b/src/spawn.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/string-utils.c b/src/string-utils.c
index b284760..10e34a8 100644
--- a/src/string-utils.c
+++ b/src/string-utils.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/string-utils.h b/src/string-utils.h
index ebe25d5..2491d07 100644
--- a/src/string-utils.h
+++ b/src/string-utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/symbols.c b/src/symbols.c
index 7198639..7dcc601 100644
--- a/src/symbols.c
+++ b/src/symbols.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -155,7 +155,7 @@ teco_symbol_list_auto_complete(teco_symbol_list_t *ctx, const gchar *symbol, tec
strlen(entry->data), FALSE);
}
- teco_interface_popup_show();
+ teco_interface_popup_show(symbol_len);
}
return glist_len == 1;
@@ -252,7 +252,10 @@ teco_state_scintilla_symbols_done(teco_machine_main_t *ctx, const teco_string_t
}
/* in cmdline.c */
-gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx, gunichar key, GError **error);
+gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx, teco_machine_t *parent_ctx,
+ gunichar key, GError **error);
+gboolean teco_state_scintilla_symbols_insert_completion(teco_machine_main_t *ctx, const teco_string_t *str,
+ GError **error);
/*$ ES scintilla message
* -- Send Scintilla message
@@ -332,6 +335,7 @@ gboolean teco_state_scintilla_symbols_process_edit_cmd(teco_machine_main_t *ctx,
*/
TECO_DEFINE_STATE_EXPECTSTRING(teco_state_scintilla_symbols,
.process_edit_cmd_cb = (teco_state_process_edit_cmd_cb_t)teco_state_scintilla_symbols_process_edit_cmd,
+ .insert_completion_cb = (teco_state_insert_completion_cb_t)teco_state_scintilla_symbols_insert_completion,
.expectstring.last = FALSE
);
diff --git a/src/symbols.h b/src/symbols.h
index 0325d9d..1d0af12 100644
--- a/src/symbols.h
+++ b/src/symbols.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/undo.c b/src/undo.c
index c49405c..c8b22ab 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/undo.h b/src/undo.h
index 1d1d6fb..d7b7c8e 100644
--- a/src/undo.h
+++ b/src/undo.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/view.c b/src/view.c
index b8c72a5..df09aac 100644
--- a/src/view.c
+++ b/src/view.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/src/view.h b/src/view.h
index eebafbf..7776e5b 100644
--- a/src/view.h
+++ b/src/view.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012-2024 Robin Haberkorn
+ * Copyright (C) 2012-2025 Robin Haberkorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/tests/testsuite.at b/tests/testsuite.at
index f17a711..3f0b7e5 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -270,6 +270,14 @@ AT_SETUP([Jump to beginning of macro])
AT_CHECK([$SCITECO -e "%a-2\"< F< ' Qa-2\"N(0/0)'"], 0, ignore, ignore)
AT_CLEANUP
+AT_SETUP([Gotos and labels])
+# Not a label redefinition, there must not even be a warning.
+AT_CHECK([$SCITECO -e '2<!foo!>'], 0, ignore, stderr)
+AT_FAIL_IF([$GREP "^Warning:" stderr])
+# Will print a warning about label redefinition, though...
+AT_CHECK([$SCITECO -e "!foo! Qa\"S^C' !foo! Qa\"S(0/0)' -Ua @O/foo/"], 0, ignore, ignore)
+AT_CLEANUP
+
#
# Command-line editing bugs.
#
diff --git a/www/screenshots.md b/www/screenshots.md
index 0e4462b..2cc14a1 100644
--- a/www/screenshots.md
+++ b/www/screenshots.md
@@ -1,5 +1,9 @@
# Screenshots
+## v2.3.0
+
+![FreeBSD/Gtk+3, SciTECO syntax highlighting](https://sciteco.sf.net/screenshots/v2.3.0-freebsd-gtk.png "FreeBSD/Gtk+3, SciTECO syntax highlighting")
+
## v2.1.0
![FreeBSD/Gtk+3, Unicode editing and spell checking](https://sciteco.sf.net/screenshots/v2.1.0-freebsd-gtk.png "FreeBSD/Gtk+3, Unicode editing and spell checking")