diff options
52 files changed, 1245 insertions, 667 deletions
diff --git a/.fmsbw/10-freebsd14-msys-sciteco b/.fmsbw/10-freebsd14-msys-sciteco index 037ac66..f8e1f40 100755 --- a/.fmsbw/10-freebsd14-msys-sciteco +++ b/.fmsbw/10-freebsd14-msys-sciteco @@ -1,39 +1,5 @@ #!/usr/local/bin/bash set -ex -export ASSUME_ALWAYS_YES=yes - -# Already in freebsd14-sciteco -# TODO: Build this with buildah. -# Start with --network=host -#pkg update -#pkg install FreeBSD-clang FreeBSD-clibs-dev \ -# gmake pkgconf autoconf automake libtool \ -# glib gtk3 groff doxygen lowdown valgrind -# -#pkg install llvm21 gnugrep gmake coreutils gsed gawk git wget gnupg bash groff zip autoconf automake libtool python3 -#pkg remove FreeBSD-clang -#git clone https://github.com/HolyBlackCat/quasi-msys2.git /opt/quasi-msys2 -#cd /opt/quasi-msys2 -#ln -s /usr/local/bin/gpgv2 /usr/local/bin/gpgv -#ln -s /usr/local/bin/bash /bin/bash -#mkdir -p gnu-overrides -#ln -s /usr/local/bin/ggrep gnu-overrides/grep -#ln -s /usr/local/bin/gmake gnu-overrides/make -#ln -s /usr/local/bin/gsed gnu-overrides/sed -#ln -s /usr/local/bin/greadlink gnu-overrides/readlink -#ln -s /usr/local/bin/wine64 gnu-overrides/wine -#echo MINGW64 >msystem.txt -#cat >activate << EOF -#cd /opt/quasi-msys2 -#export PATH=`pwd`/gnu-overrides:$PATH -#export PKG_CONFIG=pkg-config -#set +ex -#. env/all.src -#set -ex -#EOF -#gmake install _autotools _gcc _libc++ _glib2 _pdcurses _gtk3 _librsvg -#ln -nfs "/opt/quasi-msys2/root/mingw64" /mingw64 -#pkg clean -a autoreconf -i mkdir build-freebsd @@ -75,7 +41,11 @@ cp ico/sciteco-48.png /opt/htdocs/graphics . /opt/quasi-msys2/activate cd /opt/build -export CURSES_CFLAGS=-I/mingw64/include/pdcurses/ +# NOTE: Sometimes the PDCursesMod MSYS upstream package is too old, +# so we also installed v4.5.4 into the container. +#export CURSES_CFLAGS=-I/mingw64/include/pdcurses/ +export CURSES_CFLAGS=-I/opt/PDCursesMod + # 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 @@ -90,25 +60,31 @@ export LDFLAGS="-flto=thin -stdlib=libc++" # We cannot run Windows binaries automatically through Wine, # so we must still force cross-compilation with --host. +# There is a --with-launcher=wine64, but SciTECO is currently +# simply broken under Wine. #autoreconf -i mkdir build-wingui build-wincon cd build-wingui +# See above, we use a manually built PDCursesMod v4.5.4 +#export CURSES_LIBS="-lpdcurses_wingui -lgdi32 -lcomdlg32 -lwinmm" +export CURSES_LIBS="/opt/PDCursesMod/wingui/pdcurses.a -lgdi32 -lcomdlg32 -lwinmm" ../configure --host=x86_64-w64-mingw32 \ --with-interface=pdcurses-gui --enable-html-docs --program-prefix=g \ --with-scitecodatadir=. \ - --disable-bootstrap \ - CURSES_LIBS="-lpdcurses_wingui -lgdi32 -lcomdlg32 -lwinmm" + --disable-bootstrap make make install-strip #make check TESTSUITEFLAGS="--verbose --color=never" cd ../build-wincon +# See above, we use a manually built PDCursesMod v4.5.4 +#export CURSES_LIBS="-lpdcurses_wincon -lgdi32 -lwinmm" +export CURSES_LIBS="/opt/PDCursesMod/wincon/pdcurses.a -lgdi32 -lwinmm" ../configure --host=x86_64-w64-mingw32 \ --with-interface=pdcurses --enable-html-docs \ --with-scitecodatadir=. \ - --disable-bootstrap \ - CURSES_LIBS="-lpdcurses_wincon -lgdi32 -lwinmm" + --disable-bootstrap make make install-strip #make check TESTSUITEFLAGS="--verbose --color=never" diff --git a/.fmsbw/20-freebsd14-osx-sciteco b/.fmsbw/20-freebsd14-osx-sciteco index bef1cfb..5cbd49f 100755 --- a/.fmsbw/20-freebsd14-osx-sciteco +++ b/.fmsbw/20-freebsd14-osx-sciteco @@ -25,12 +25,14 @@ cd .. #UNATTENDED=1 ./build.sh #unset CPPFLAGS #export MACOSX_DEPLOYMENT_TARGET=10.13 -# FIXME: This is not unattended. Perhaps echo https://nue.de.packages.macports.org/macports/packages >target/macports/MIRROR +# FIXME: This is not unattended. +# Perhaps echo https://nue.de.packages.macports.org/macports/packages >target/macports/MIRROR # dylibbundler is available but can't be run naturally. +# FIXME: How to install variants, like the Quartz variant for Gtk3? #osxcross-macports install --static glib2-devel gtk3-devel # #pkg install cmake -#git clone https://github.com/auriamg/macdylibbundler.git /opt/macdylibbundler +#git clone --depth=1 https://github.com/auriamg/macdylibbundler.git /opt/macdylibbundler #cd /opt/macdylibbundler #cmake . #make @@ -68,6 +70,7 @@ cd .. # FIXME: Also build -arch arm64 and package with x86_64-apple-darwin25-lipo into universal binary. # x86_64-apple-darwin25-lipo -lipo -create -output sciteco x86_64/usr/local/bin/sciteco arm64/usr/local/bin/sciteco # TODO: Build Gtk version as well. +# Currently, we cannot install the Quartz variant. mkdir -p /opt/htdocs/downloads/nightly/ cp sciteco-curses_nightly_macos_x86_64.tar.gz /opt/htdocs/downloads/nightly/ diff --git a/.fmsbw/50-ubuntu22-appimage b/.fmsbw/50-ubuntu22-appimage index ade1100..ca987fa 100755 --- a/.fmsbw/50-ubuntu22-appimage +++ b/.fmsbw/50-ubuntu22-appimage @@ -9,15 +9,6 @@ set -ex # we run this CI job. # We should be fine, though unless committing at 6:00 in the morning. -#apt-get update -o APT::Cache-Start=100000000 -#apt-get install -o APT::Cache-Start=100000000 -y fuse libfuse2 imagemagick wget file binutils libglib2.0-bin -#mkdir -p ~/pkg2appimage -#cd ~/pkg2appimage -#wget -O pkg2appimage.AppImage https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage -#chmod +x pkg2appimage.AppImage -# FIXME: We could get automatic mounting to work with fusefs in the host and by exposesing /dev/fuse. -#./pkg2appimage.AppImage --appimage-extract - cd AppImage ~/pkg2appimage/squashfs-root/AppRun curses.yml mv out/*.AppImage /opt/htdocs/downloads/nightly/sciteco-curses_nightly_x86_64.AppImage diff --git a/.fmsbw/images/Makefile b/.fmsbw/images/Makefile new file mode 100644 index 0000000..678a05a --- /dev/null +++ b/.fmsbw/images/Makefile @@ -0,0 +1,67 @@ +# Don't build any image by default +all: + +# Base image for building SciTECO on FreeBSD. +freebsd14-sciteco: + buildah from --name $@-working --network=host quay.io/dougrabson/freebsd14.1-small + buildah config --env ASSUME_ALWAYS_YES=yes $@-working + buildah run $@-working pkg update + buildah run $@-working pkg install FreeBSD-clang FreeBSD-lld FreeBSD-libcompiler_rt-dev FreeBSD-clibs-dev \ + gmake pkgconf autoconf automake libtool \ + glib gtk3 groff doxygen lowdown valgrind + buildah run $@-working pkg clean -a + buildah commit $@-working $@ + +freebsd14-msys-sciteco: + buildah from --name $@-working --network=host freebsd14-sciteco + buildah run $@-working pkg install llvm21 gnugrep gmake coreutils gsed gawk \ + git wget gnupg bash groff zip autoconf automake libtool python3 \ + wine + #buildah run $@-working pkg remove FreeBSD-clang + buildah run $@-working pkg clean -a + # Cannot check out with --depth=1 as we need a particular commit. + buildah run $@-working git clone https://github.com/HolyBlackCat/quasi-msys2.git /opt/quasi-msys2 + buildah config --workingdir /opt/quasi-msys2 $@-working + buildah run $@-working git checkout e41c4d0f7dde15031132348875d1d01c8d0ea857 + buildah run $@-working ln -s /usr/local/bin/gpgv2 /usr/local/bin/gpgv + buildah run $@-working ln -s /usr/local/bin/bash /bin/bash + buildah run $@-working mkdir -p gnu-overrides + buildah run $@-working ln -s /usr/local/bin/ggrep gnu-overrides/grep + buildah run $@-working ln -s /usr/local/bin/gmake gnu-overrides/make + buildah run $@-working ln -s /usr/local/bin/gsed gnu-overrides/sed + buildah run $@-working ln -s /usr/local/bin/greadlink gnu-overrides/readlink + buildah run $@-working ln -s /usr/local/bin/wine64 gnu-overrides/wine + buildah run $@-working bash -c 'echo MINGW64 >msystem.txt' + buildah copy $@-working msys-activate activate + buildah run $@-working gmake install _autotools _gcc _libc++ _glib2 _pdcurses _gtk3 _librsvg + buildah run $@-working ln -nfs "/opt/quasi-msys2/root/mingw64" /mingw64 + # The upstream _pdcurses package is often too outdated, so we also build from sources. + # TOOD: Build this with -flto. + buildah run $@-working git clone --depth=1 -b v4.5.4 https://github.com/Bill-Gray/PDCursesMod.git /opt/PDCursesMod + buildah run $@-working bash -c '. /opt/quasi-msys2/activate && gmake -j2 -C /opt/PDCursesMod/wincon CC=$$CC AR=$$AR WIDE=Y UTF8=Y' + buildah run $@-working bash -c '. /opt/quasi-msys2/activate && gmake -j2 -C /opt/PDCursesMod/wingui CC=$$CC AR=$$AR WIDE=Y UTF8=Y' + buildah commit $@-working $@ + +freebsd14-osx-sciteco: + false # TODO + +APT_GET = apt-get -o APT::Cache-Start=100000000 --yes + +ubuntu22-appimage: + buildah from --name $@-working --network=host --os=linux ubuntu:22.04 + buildah run $@-working $(APT_GET) update + buildah run $@-working $(APT_GET) install fuse libfuse2 imagemagick wget file binutils libglib2.0-bin + buildah run $@-working $(APT_GET) clean + buildah run $@-working mkdir -p ~/pkg2appimage + buildah config --workingdir '~/pkg2appimage' $@-working + buildah run $@-working wget -O pkg2appimage.AppImage \ + https://github.com/AppImageCommunity/pkg2appimage/releases/download/continuous/pkg2appimage-1eceb30-x86_64.AppImage + buildah run $@-working chmod +x pkg2appimage.AppImage + # FIXME: We could get automatic mounting to work with fusefs in the host and by exposing /dev/fuse. + # FIXME: This does not run without /proc. + buildah run $@-working ./pkg2appimage.AppImage --appimage-extract + buildah commit $@-working $@ + +# Remove all temporary containers +clean: + buildah rm --all diff --git a/.fmsbw/images/msys-activate b/.fmsbw/images/msys-activate new file mode 100644 index 0000000..655fcae --- /dev/null +++ b/.fmsbw/images/msys-activate @@ -0,0 +1,6 @@ +cd /opt/quasi-msys2 +export PATH=`pwd`/gnu-overrides:$PATH +export PKG_CONFIG=pkg-config +set +ex +. env/all.src +set -ex @@ -6,6 +6,77 @@ using a prebuilt binary) are included. Entries marked with "(!)" might break macro portability compared to the preceding release. +Version 2.5.2 (2026-04-20) +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* sped up `EQq<filename>$` +* Curses/UNIX: It should be possible to interrupt more potentially blocking + operations with CTRL+C (SIGINT). +* `^A` is no longer executed in parse-only mode - fixes bogus warnings. + Has been broken since v2.5.0. +* (!) `^A` now supports a message severity level, so you can log + warnings and errors as well. +* Added TANK MODE. + Launch with EI^EQ[$SCITECOPATH]/tank.tes$ +* support Groff v1.24.0 and later +* fnkeys.tes: When preserving the horizontal column, + take the character representations and tab draw modes into account. +* SIGTERM/SIGHUP always quits. + On GTK recovery files are always dumped first. +* decreased default recovery dumping interval to 120s +* (!) `-$$` and `-^C` always return -1 now instead of passing down the prefix sign +* fixed scrolling at the end of a multi-line command-line +* fixed auto completion of two-character Q-register names +* `ER` updates `^S`/`^Y` now as any other text insertion +* GTK: fixed --detach and support stdout/stderr redirections. + Has been broken since v2.5.0. +* GTK: Fixed interaction between `^T` and main input handling. + Key presses are no longer missed and inserted into the command line later. +* Curses: handle window resizes when blocking in `^T` and don't return function keys. + Also, filter out bogus key press events on PDCursesMod/WinGUI. +* Curses: fixed monochrome support. + Has been broken since v2.5.0. +* Curses: fixed 16-color schemes and default colors (`sciteco --no-profile`) + on 8-color terminals (like the Linux/FreeBSD VTs) + Has been broken since v2.5.0. +* Curses: default colors are now closer to terminal.tes +* Curses: fixed colors on emulators with less than 256 color pairs + (e.g. TERM=rxvt) +* Curses: support up to 32767 color pairs +* Curses: require wide-char support even on PDCurses +* fixed auto-completion of Unicode (non-ANSI) file names +* disallow replacing non-Unicode command lines via `{` and `}` +* Curses: fixed flickering of the hardware cursor on the command line. + On the downside, this can result in the cursor being visible when + drawing the buffer as well. + Has been broken since v2.5.1. +* fallback.teco_ini: added commented-out line to disable the hardware cursor +* fallback.teco_ini: fixed exclusion of the unnamed buffer from custom + margin and indention settings. + Has been broken since v2.5.0. +* opener.tes: opener.check-recovery can be used in ~/.teco_ini to check + for and warn about the presence of `#recovery#` files. + +Version 2.5.1 (2026-01-10) +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* Haiku is officially supported again from now on +* minor documentation improvement for `ED` +* Curses: The command-line uses the hardware cursor by default now. + To re-enable the self-drawn cursor, do something like + `0,2048ED 2#16@ES/SETCARETSTYLE//$ 2048,0ED` +* PDCurses/XCurses (X11): fixed crashes on startup and mouse support + +Regressions introduced by v2.5.0 and now fixed: + +* GTK: Fixed bogus "(Unnamed)" strings in empty message lines +* PDCurses: Fixed reporting of mouse modifiers (CTRL, ALT, SHIFT) +* PDCurses: Fixed colors + (affects the Windows sciteco.exe and gsciteco.exe). +* PDCurses: Fixed hanging after pressing mouse keys + (affects the Windows sciteco.exe and gsciteco.exe). +* PDCurses/WinGUI: Fixed hanging input (affects gsciteco.exe). + Version 2.5.0 (2026-01-01) ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -115,7 +186,7 @@ Version 2.5.0 (2026-01-01) * implemented ^E<code> string building constructs for embedding bytes and codepoints in a strtoul()-like manner * support <:]q> (pop Q-Register) for getting a success/failure boolean - * support <==> and <===> for printing octal and hexadecimal numbers + * (!) support <==> and <===> for printing octal and hexadecimal numbers * support :=/:==/:=== commands: print number without trailing linefeed * Implemented the <^A> command for printing arbitrary strings. You can use :^A to force raw ANSI output. @@ -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-2025 Robin Haberkorn +Copyright (C) 2013-2026 Robin Haberkorn Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright @@ -21,13 +21,14 @@ SciTECO Build and Runtime Dependencies * Glib 2 as a cross-platform runtime library (v2.44 or later): https://developer.gnome.org/glib/ * When choosing the Curses interface, you need one of: - * NCurses (http://www.gnu.org/software/ncurses/). - If you plan to use the ncurses MinGW port, - I recommend ncurses 6.0 or later. + * ncurses (http://www.gnu.org/software/ncurses/). + Should be built with wide-character support (--enable-widec). + If you plan to use the ncurses MinGW port, I recommend ncurses 6.0 or later. * NetBSD Curses (https://github.com/sabotage-linux/netbsd-curses). - This is the default on NetBSD. - * PDCursesMod v4.5.1 or later (https://github.com/Bill-Gray/PDCursesMod.git). + This is the default on NetBSD and should always support wide characters. + * PDCursesMod v4.5.4 or later (https://github.com/Bill-Gray/PDCursesMod.git). This is the recommended flavor of PDCurses to use. + It must be built with WIDE=Y and UTF8=Y. * PDCurses/EMCurses (https://github.com/rhaberkorn/emcurses). * PDCurses/XCurses (http://pdcurses.sourceforge.net/). Note that XCurses v3.4 appears to be broken, you may have to @@ -47,7 +48,7 @@ installed by the user manually: * Scintilla (v5.3.0 or later): http://www.scintilla.org/ * When choosing the Curses interface: - * Scinterm (v5.2 or later): + * Scinterm (v5.5 or later): http://foicica.com/scinterm/ * Lexilla (v5.0.0 or later, optional): https://www.scintilla.org/Lexilla.html @@ -75,6 +76,11 @@ On NetBSD: $ sudo pkgin install git gmake pkg-config autoconf automake libtool-base \ glib2 gtk3+ doxygen +On Haiku: + + $ pkgman install git make pkgconfig autoconf automake libtool \ + glib2_devel ncurses6_devel gtk3_devel groff doxygen + Building from Source Tar Ball or Repository =========================================== @@ -2,8 +2,5 @@ News ==== <span class="nf nf-md-new_box"></span> -SciTECO [v2.5.0](https://sciteco.fmsbw.de/downloads/v2.5.0/) has been released. -This release brings many new features, but most importantly -makes the language much more usable as a non-interactive scripting language. -Also, the command-line is syntax highlighted now and recovery files are written -to help you recover from crashes and other unexpected terminations. +SciTECO [v2.5.2](https://sciteco.fmsbw.de/downloads/v2.5.2/) has been released. +This is mostly a fixup to v2.5.1 and v2.5.0. @@ -23,11 +23,11 @@ The Curses frontend is verified to work with [ncurses](https://www.gnu.org/softw [PDCurses/XCurses](https://github.com/wmcbrine/PDCurses), [PDCursesMod](https://github.com/Bill-Gray/PDCursesMod) and [EMCurses](https://github.com/rhaberkorn/emcurses). -All X/Open-compatible libraries should be supported. +All X/Open-compatible libraries with enhanced and wide-character support should be supported. SVr4 curses without enhanced definitions is **not** supported. Linux, FreeBSD, NetBSD, [Mac OS X](https://sciteco.fmsbw.de/knowledge/Mac%20OS%20Support), -Windows (MinGW 32/64) ~~and [Haiku](https://www.haiku-os.org/) (gcc4)~~ are tested and supported. +Windows (MinGW 32/64) and [Haiku](https://www.haiku-os.org/) (gcc4) are tested and supported. SciTECO compiles with both GCC and Clang. SciTECO should compile just fine on other UNIX-compatible platforms. However UNIX-compatibility is not strictly required: @@ -121,7 +121,9 @@ There are prebuilt binary packages and source bundles for your convenience: * [Main download archive](https://sciteco.fmsbw.de/downloads) * [Download Archive at Sourceforge](https://sourceforge.net/projects/sciteco/files/) * [FreeBSD port](https://www.freshports.org/editors/sciteco/) - [](https://repology.org/project/sciteco-curses/versions) +  +* [Haiku port](https://depot.haiku-os.org/#!/pkg/sciteco_curses) +  * OBS repositories and binary downloads for RPM-based (Fedora, openSUSE, etc.) and Debian-based (Debian, Raspbian, Ubuntu) distributions: [](https://build.opensuse.org/package/show/home:rhaberkorn:sciteco:STABLE/sciteco) @@ -129,12 +131,12 @@ There are prebuilt binary packages and source bundles for your convenience: * [Gtk packages](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:STABLE&package=sciteco-gtk) * [Curses packages](https://software.opensuse.org/download.html?project=home:rhaberkorn:sciteco:STABLE&package=sciteco-curses) * [Arch User Repository](https://aur.archlinux.org/packages/sciteco-git) - [](https://repology.org/project/sciteco/versions) +  * [Alpine Linux package](https://pkgs.alpinelinux.org/package/edge/community/x86_64/sciteco) - [](https://repology.org/project/sciteco/versions) +  * [Chocolatey package](https://community.chocolatey.org/packages/SciTECO) for Windows users - [](https://repology.org/project/sciteco/versions) +  * Yocto/OpenEmbedded users should try the [`sciteco` package from this layer](https://git.fmsbw.de/meta-rhaberkorn/). * Users of OpenWrt may try to install the @@ -1,7 +1,7 @@ Tasks: * Have a look at TECO-86. * VEDIT and PMATE for MS-DOS - * Scintilla: upstream 2 patches + * Make sure that fmsbw.de is indexed by Google Known Bugs: * OBS GTK builds sometimes fail: "cannot open display" @@ -9,48 +9,36 @@ Known Bugs: * In multiline command lines, the asterisk can scroll out of view. Perhaps it has to be drawn independently of Scinterm. * FreeBSD's `crontab -e` is not compatible with SciTECO's savepoint mechanism. - * ncurses: GNOME Terminal and Xterm produces BUTTON3_PRESSED (without BUTTON3_RELEASED) - events when scrolling horizontally. - This is fixed upstream in ncurses and there is a workaround for - older ncurses versions which limits the effects of this bug. - * ncurses: st and Xterm produce BUTTON2_RELEASED, followed by BUTTON2_PRESSED - when clicking the middle button. - We also *sometimes* get it in the correct order. - This bug has been fixed upstream and there is a workaround for - older ncurses versions. You may loose the distinction between - pressed and release events, though. + * Gnome Terminal: You can see hardware cursor flickering when deleting + lines repeatedly (if it's enabled of course). This is also visible in Aerc. + Scroll regions are already used, but only when scrolling. + Disabling the hardware cursor around doupdate() causes flickering + of the cursor on some terminals. * Upgrade to Scintilla 5.5.7 requires charconv header which bumps the minimum GCC version to 8.1 (officially 9). This breaks OpenSUSE 15.5-15.6 builds. * Build problems on Fedora 41: See mail from Blake McBride. - * @^Um{-$$} Mm= should probably return -1. * {@I/$$23=/} doesn't insert anything after $$. - This would be necessary for an interactive screen editing script, - that leaves you in <I> always. This would require some refactoring. + * GTK on Haiku: CTRL and AltGr-combinations do not work. + The modifiers are not delivered properly and it appears to affect all Gtk apps + on Haiku. * Gtk: The control characters in tutorial.woman are still styled with the variable-width font since its rendered in STYLE_CONTROLCHAR (36), which is reset in woman.tes. Perhaps it should always be in lexer.font. - * The current horizontal position (set by 4EJ via SCI_GETCOLUMN) is - often wrong, i.e. pressing the up-cursor key can get you into the - wrong column. - Character representations obviously take always one column. - This would have to be fixed in Scintilla's SCI_GETCOLUMN. * Excessively long lines slow down SciTECO too much, especially when enabling SCI_SETWRAPMODE(SC_WRAP_CHAR). In some cases, the internal redrawing blocks SciTECO forever. * @ES/SCI_CLEARALLREPRESENTATIONS// does nothing. Might be a Scintilla bug. - * PDCurses/Wincon does not report button released events. - We try to work around this with click detection, - but it still behaves a bit oddly. - See https://github.com/Bill-Gray/PDCursesMod/issues/330 - Waiting for PDCurses in MSYS to be updated. - * 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: - https://github.com/Bill-Gray/PDCursesMod/issues/322 + * Some operations can be hard to interrupt because they result + in costly calculations within Scintilla. + For instance interrupting ECcat /dev/zero$ can hang in + the Scintilla Undo operation. + This particular hang could be avoided by getting rid of + Scintilla undo actions in teco_view_load_from_channel() and spawn.c. + We can probably only get rid of undo actions completely and globally. * Win32: Interrupting <EC> will sometimes hang. Affects both PDCurses/WinGUI and Gtk. This no longer happens with ECbash -c 'while true; do true; done'$. @@ -59,28 +47,23 @@ Known Bugs: However the UNIX path translation appears to be a Cygwin feature. If SciTECO would do that, it might break other things (e.g. you might want to refer directory C:\mingw64 instead). - * Win32/Wincon: cat ... | sciteco -i - "Redirection is not supported." - PR: https://github.com/Bill-Gray/PDCursesMod/pull/344 - Waiting for an MSYS package releae. - * PDCurses/Win32: Both Wincon and WinGUI crash when you press *any* - non-ANSI character. This is fixed since PDCurses v4.5.2: - https://github.com/Bill-Gray/PDCursesMod/issues/335 - We're waiting for an MSYS package upgrade. - It could also be worked around by using wget_wch() instead of wgetch(). * PDCurses/Win32: Crashes sometimes without any error message. * NetBSD Curses: scrolling apparently uses hardware idl capabilities - resulting in graphical glitches on slow terminals. + without scroll regions resulting in graphical glitches on slow terminals. idlok(FALSE) is apparently ignored. - * NetBSD: Very slow, even the redrawing. + * NetBSD Curses: Very slow, even the redrawing. This does not happen with ncurses on NetBSD. + Do they have immedok(TRUE) enabled by default? * dlmalloc's malloc_trim() does not seem to free any resident memory after hitting the OOM limit, eg. after <%a>. Apparently an effect of HAVE_MORECORE (sbrk()) - some allocation is always left at the end. * S<LF>^ES^N<$ does not find the first line that does not begin with "<". This is because \s+ backtracks and can match shorter sequences. - Perhaps ^ES should always be translated to \s++ (possessive quantifier)? + TECO-11 patterns do not backtrack apparently. + Perhaps ^ES should always be translated to \s++ (possessive quantifier). + On the other hand, we would have to extend the pattern syntax to allow + backtracking. * Colors are still wrong in Linux console even if TERM=linux-16color when using Solarized. Affects e.g. the message line which uses the reverse of STYLE_DEFAULT. @@ -105,6 +88,7 @@ Known Bugs: 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 would also be possible to port hxrex to UTF-8 and add it as a submodule. * 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 @@ -215,6 +199,27 @@ Known Bugs: Also, the command-line redrawing is still broken in GNOME Terminal. * When config.status is run as a consequence of touching configure.ac, config.h will be wrong, forcing us to rerun ./configure. + * Does not scroll at the end of a multiline command-line + (the first character on a scrolled line will not be visible). + On Curses the hardware cursor will still stay at the end of the window of course. + Obviously a Scintilla bug. + Break on EditView::DisplayFromPosition + Perhaps the problem is in Editor::MaxScrollPos(). + As a workaround, we set SCI_SETENDATLASTLINE(0). + * OBS builds do not provide Ubuntu i586 since subversion is obviously no longer + packaged, breaking obs-service-tar-scm. + Is there any third-party build? + * GTK: too much flickering in tank mode when shooting. + This is because my font doesn't contain the correct glyph. + Try: I ^E<10041>$ + Perhaps we should use more widespread glyphs. + https://groups.google.com/g/scintilla-interest/c/ZgLik8tNyjM + * There are still potentially blocking syscalls that could result in + SciTECO hanging. Curses/UNIX can rely on SIGINT, but this won't + help on any other platform. + All blocking operations must be within an event loop and call into + teco_interface_is_interrupted() to potentially drive the UI and + detect CTRL+C presses. Features: * Should we support *.sgml files with the HTML lexer? @@ -235,6 +240,7 @@ Features: Would probably require some kind of server... * opener.tes should try to center the opened line (SCI_SETFIRSTVISIBLELINE). + However, this would require a new ED hook, so we can query SCI_LINESONSCREEN. * Rubout of SCI_GOTOPOS could also restore the vertical scrolling position (SCI_SETFIRSTVISIBLELINE). So e.g. rubbing out ZJ restores the exact view. @@ -312,21 +318,11 @@ Features: line. May be useful e.g. for solarized's F5 key (i.e. function key macros that need to terminate the command line as they cannot be rubbed out properly). - * Key macros could support special escape sequences - that allow us to modify the parser state reliably. - E.g. one construct could expand to the current string argument's - termination character (which may not be Escape). - In combination with a special key macro state - effective only in the start state of the string building - state machine, perhaps only in insertion commands, this - could be used to make the cursor movement keys work in - insertion commands by automatically terminating the command. - Even more simple, the function key flag could be effective - only when the termination character is $. - Instead of reserving a character, we could also reserverve - a bit in the function key maks. - It's however impossible to reliably return to the start state - from arbitrary parser states. + * Key macro flag to automatically terminate insertion commands + (or everywhere where it's safe). + Should be used to make the cursor movement keys (fnkeys.tes) + automatically terminate string arguments. + Will probably require a new state callback. * Support more function keys. We can define more function keys via define_key(3NCURSES). Unfortunately they are not really standardized - st and urxvt for instance @@ -403,7 +399,6 @@ Features: * ^A currently doesn't flush the output. Perhaps flushing should be controlled by the language. Perhaps via the ET flags. - * Mini game where you can drive a tank around your source code. * `@]q` to pop keeping the numeric part of q intact. Perhaps `@@]q` to overwrite __only__ the numeric part, but keeping the string intact. @@ -494,7 +489,12 @@ Features: command-line termination. Also, we'd need a command to fetch the modification timestamp as well. - * Recovery files should probably be hidden during auto completions. + * Curses: Dump recovery files on SIGTERM and SIGHUP as well. + Useful when we are unexpectedly but gracefully terminated, + Could only be done on Curses by setting a small timeout and + returning from getch() frequently to check whether the signal + ocurred. + On Wincon we could also react to window closes. * Error handling in SciTECO macros: Allow throwing errors with e.g. [n]^F<description>^F where n is an error code, defaulting to 0 and description is the error string - there could be code-specific @@ -607,6 +607,7 @@ Features: * It should be possible to disable auto-completions of one-character register names, so that we can map the idention macro to M<TAB>. * Add a configure-switch for LTO (--enable-lto). + All of the packages will have to be adapted as well. * There should be a string building construct for escaping search patterns. Since string building is performed immediately before @@ -614,9 +615,12 @@ Features: search for a Q-Register verbatim. * Tweak the Makefile lexer for Automake support. In the simplest case, just add the *.am file extension. - * Add an fnkeys.tes alternative where moving cursor keys + * Add key macros where moving cursor keys leaves you in the insert (I) command. That will behave very similar to classical editors. + See n00b.tes. + This still requires an ED hook for new command lines and + FQ to remove Q-registers. * Lexing via SciTECO macros? They would have to be in their own parser instance since Scintilla could ask us to restyle at any time and within string arguments, @@ -713,6 +717,9 @@ Features: ^U[^L0]SciTECO ^XI <^XT> ^XF$ As a first step, customizing the window title might be the most useful, as often, you don't need the "SciTECO - <Buffer>" prefix. + * Would also allow the configuration of "powerline" symbols at least + for the Curses variant. + https://github.com/ryanoasis/powerline-extra-symbols * Until we have customizeable layouts, it may make sense to hardcode the current line+column in to the message line. * With Unicode icon support, we might want to replace a few more @@ -762,14 +769,6 @@ Features: To format a hex byte, you would write 16^R 2,Qa\ ^D. The same extension might not be desirable for =/==/===, unless they are colon-modified?. - * Curses: allow freely using RGB colors instead of only the constants - corresponding with the 16 default curses colors. - Scinterm v5.5 supports that. This means we also need a hash map for - mapping RGB color values to curses color numbers, analoguous to what - Scinterm does. - The problem of Scinterm reusing the same color namespace both for - arbitrary RGB values and for predefined special constants however - hasn't been solved yet. * Scinterm: INDIC_PLAIN and INDIC_STRIKE could theoretically be supported. PDCurses has A_STRIKETHROUGH. This would require Scintilla API changes, though, as it currently calls SurfaceImpl::FillRectangle(), @@ -801,6 +800,40 @@ Features: * There could be a key macro script for my Russian-phonetic keyboard layout. This would probably require some new key macro states as well. + * Allow the hardware cursor (CARETSTYLE_CURSES) on the main view as well. + We'd have to handle an overlapping info popup, though. + Should we deactivate the hardware cursor via curs_set(0) if it overlaps + the popup? + * ncurses: Support default colors (use_default_colors()). + Instead of drawing a black background and white foreground + we could use pair number -1. + This means you don't suddenly become white-on-black on terminals + that use black-on-white by default. + On the other hand, not all color schemes might look good, + so this would have to be a setting (ED flag). + Also, Scinterm would have to support it as well. + This also appears to be responsible for the greyish background + on Haiku and GNOME Terminal: They don't have a true black as color 0 + even if the default terminal background is a true black. + * Ctags support + Perhaps can be Video TECO compatible. + https://github.com/universal-ctags/ctags + * Non-spacing code points would currently be handled as separate + SciTECO characters. + On the one hand, this means you can remove accents from characters. + On the other hand, it may be inconvinient in texts with many accents. + What do Asian scripts like Hangul do? + * :^Q should perhaps return a status code, so you can check for + invalid line ranges. + The current :^Q (dot to line) would have to be renamed to + something else, like ^L. + Or alternatively @^Q could simply ignore out of range + arguments. + * Perhaps add math.tes with primitives from my Project Euler + solutions to the standard library. + * :^W should perhaps inhibit the caret scrolling. + When using ^W purely as a wait command, this could be undesirable. + Also, it is undesirable for some animations (e.g. shooting in tank.tes). Optimizations: * Use SC_DOCUMENTOPTION_STYLES_NONE in batch mode. diff --git a/configure.ac b/configure.ac index 5ac7aad..f64636b 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.65]) -AC_INIT([SciTECO], [2.5.0], +AC_INIT([SciTECO], [2.5.2], [hackers@fmsbw.de], [sciteco], [https://sciteco.fmsbw.de/]) @@ -215,7 +215,7 @@ case $host in *-*-linux* | *-*-*bsd* | *-*-darwin* | *-*-cygwin* | *-*-haiku*) # NOTE: Keep this on a single line for compatibility # with ancient versions of Autoconf. - AC_CHECK_FUNCS([realpath readlink pathconf fchown dup dup2 getpid open read kill mmap popen pclose isatty fork setsid], , [ + AC_CHECK_FUNCS([realpath readlink pathconf fchown dup dup2 getpid open read kill mmap popen pclose isatty fork execv setsid sigaction], , [ AC_MSG_ERROR([Missing libc function]) ]) AC_SEARCH_LIBS(dladdr, [dl], , [ @@ -289,7 +289,8 @@ case $INTERFACE in case $INTERFACE in ncurses) # This gives precendence to the widechar version of ncurses, - # which is necessary for Unicode support even when not using widechar APIs. + # which is necessary for Unicode support and when using more than + # 256 color pairs. # However we also accept libncurses.so if it also contains the # enhanced definitions. # NOTE: This also defines CURSES_CFLAGS and CURSES_LIBS arguments, @@ -303,6 +304,9 @@ case $INTERFACE in CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" LIBS="$LIBS $CURSES_LIBS" + # Scinterm cares about the correct flags itself. + AC_DEFINE(NCURSES_WIDECHAR, 1, [Provide wide-character functions]) + AC_CHECK_FUNCS([tigetstr]) ;; @@ -312,10 +316,6 @@ case $INTERFACE in # installed, we want this to be an explicit setting. AC_DEFINE(NETBSD_CURSES, 1, [Building against netbsd-curses]) - CFLAGS="$CFLAGS $CURSES_CFLAGS" - CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" - LIBS="$LIBS $CURSES_LIBS" - if [[ "x$CURSES_LIBS" = "x" ]]; then # libncurses.pc is only shipped by Void Linux' fork, # not in NetBSD itself. @@ -325,15 +325,20 @@ case $INTERFACE in else AC_MSG_CHECKING([checking for netbsd-curses (CURSES_LIBS)]) AC_MSG_RESULT([$CURSES_LIBS]) - LIBS="$LIBS $CURSES_LIBS" fi + CFLAGS="$CFLAGS $CURSES_CFLAGS" + CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" + LIBS="$LIBS $CURSES_LIBS" + AC_CHECK_FUNCS([tigetstr]) ;; xcurses) AC_CHECK_PROG(XCURSES_CONFIG, xcurses-config, xcurses-config) + # NOTE: `xcurses-config --cflags` is expected to set + # -DPDC_WIDE and -DPDC_FORCE_UTF8 (if these features are enabled). if [[ "x$CURSES_CFLAGS" = "x" -a "x$XCURSES_CONFIG" != "x" ]]; then CURSES_CFLAGS=`$XCURSES_CONFIG --cflags` fi @@ -351,15 +356,18 @@ case $INTERFACE in LIBS="$LIBS $CURSES_LIBS" # It is crucial to define XCURSES before including curses.h. + # This is not important for Scinterm. AC_DEFINE(XCURSES, 1, [Enable PDCurses/XCurses extensions]) + AC_CHECK_FUNC([has_mouse], [ + # not important to pass to Scinterm + AC_DEFINE(PDC_NCMOUSE, 1, [PDCurses built with ncurses mouse API]) + ]) + AC_DEFINE(PDCURSES_GUI, 1, [PDCurses with GUI window]) ;; pdcurses*) - CFLAGS="$CFLAGS $CURSES_CFLAGS" - CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" - if [[ "x$CURSES_LIBS" = "x" ]]; then AC_CHECK_LIB(pdcurses, PDC_get_version, , [ AC_MSG_ERROR([libpdcurses missing!]) @@ -367,21 +375,26 @@ case $INTERFACE in else AC_MSG_CHECKING([checking for PDCurses (CURSES_LIBS)]) AC_MSG_RESULT([$CURSES_LIBS]) - LIBS="$LIBS $CURSES_LIBS" fi # It is crucial to define PDC_WIDE before including curses.h. # FIXME: MinGW has a pdcurses.h that already defines all necessary macros, # but it's not in upstream PDCurses/PDCursesMod. - AC_CHECK_FUNC([add_wch], [ - AC_DEFINE(PDC_WIDE, 1, [PDCurses built with wide-character support]) - # FIXME: It would be better to check for PDC_FORCE_UTF8. - # Theoretically, we could check for endwin_u[32|64]_4302, - # but I'm not sure this will work reliably in the future. - AC_DEFINE(PDC_FORCE_UTF8, 1, [PDCursesMod forces use of UTF8]) + # FIXME: It would be better to check for PDC_FORCE_UTF8. + # Theoretically, we could check for endwin_u[32|64]_4302, + # but I'm not sure this will work reliably in the future. + CURSES_CFLAGS="$CURSES_CFLAGS -DPDC_WIDE -DPDC_FORCE_UTF8" + + CFLAGS="$CFLAGS $CURSES_CFLAGS" + CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" + LIBS="$LIBS $CURSES_LIBS" + + AC_CHECK_FUNC([add_wch], [], [ + AC_MSG_ERROR([libpdcurses does not include wide-character support!]) ]) AC_CHECK_FUNC([has_mouse], [ + # not important to pass to Scinterm AC_DEFINE(PDC_NCMOUSE, 1, [PDCurses built with ncurses mouse API]) ]) @@ -452,7 +465,13 @@ AC_ARG_ENABLE(malloc-replacement, [Replace the libc malloc() [default=check]]), [malloc_replacement=$enableval], [malloc_replacement=check]) if [[ $malloc_replacement = check ]]; then - # We currently do not support dlmalloc on Windows and Mac OS. + # malloc() replacement via dlmalloc should work practically everywhere + # but does not extend to shared libraries on Windows and Mac OS. + # That's why it is disabled by default on Windows and Mac OS where other cheap ways + # of introspection are available (see memory.c). + # On the remaining platforms you can try to combine --enable-malloc-replacement + # with --enable-static-executables to link in as many libraries statically + # as possible. case $host in *-*-darwin* | *-mingw*) malloc_replacement=no;; *) malloc_replacement=yes;; diff --git a/contrib/scinterm b/contrib/scinterm -Subproject 53eb713af10abf46f250746a4acc336b16b02f3 +Subproject 274656ad8fd7478ff7ed7fc4e04eedb7863b2f9 diff --git a/contrib/scintilla b/contrib/scintilla -Subproject b64652b857d3a5922c72ccc801ac77aa9cff27c +Subproject 748f9cbc4f2a9d7a4216feaa37aee8e834123f0 diff --git a/debian/changelog b/debian/changelog index 0923f61..ab0d425 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +sciteco (2.5.2) unstable; urgency=low + + * new upstream version v2.5.2 + + -- Robin Haberkorn <rhaberkorn@fmsbw.de> Sun, 19 Apr 2026 20:21:14 +0000 + +sciteco (2.5.1) unstable; urgency=low + + * new upstream version v2.5.1 + + -- Robin Haberkorn <rhaberkorn@fmsbw.de> Sat, 10 Jan 2026 15:16:39 +0000 + sciteco (2.5.0-0) unstable; urgency=low * new upstream version v2.5.0 diff --git a/doc/cheat-sheet.mm b/doc/cheat-sheet.mm index ba7247c..9a63f4e 100644 --- a/doc/cheat-sheet.mm +++ b/doc/cheat-sheet.mm @@ -825,7 +825,12 @@ Binary negate \fIn\fP \(em negate TECO boolean. . TD . CI "" n = . TD -Show value of \fIn\fP in message line. +Show value of \fIn\fP in message line (decimal). +. TRX +. TD +. CI "" n === +. TD +Show value of \fIn\fP in message line (hexadecimal). . TRX . TD colspan=2 Q-Registers consist of 2 cells: strings and integers. diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in index e47ca93..83dba44 100644 --- a/doc/sciteco.1.in +++ b/doc/sciteco.1.in @@ -425,13 +425,12 @@ Note that this signal can usually also be generated when pressing .TP .SCITECO_TOPIC "SIGTERM" .B SIGTERM +.TQ +.SCITECO_TOPIC "SIGHUP" +.B SIGHUP Try to gracefully shut down \*(ST. -In batch mode this only interrupts the currently running macro -similar to \fBSIGINT\fP causing \*(ST to exit. -If technically possible, user interfaces will additionally -process \fBSIGTERM\fP in interactive mode as if the \fICLOSE\fP -function key has been pressed, which will result in unconditional -program termination or user-programmed behaviour. +If technically possible, this will immediately dump recovery +files unless recovery file dumping is disabled (\(lq0,6EJ\(rq). . . .SH FILES diff --git a/doc/tutorial.ms.in b/doc/tutorial.ms.in index 92da0af..4016605 100644 --- a/doc/tutorial.ms.in +++ b/doc/tutorial.ms.in @@ -36,6 +36,10 @@ . ns .. . +.\" Work around problems with early device extension commands. +.\" See groff(7) and https://savannah.gnu.org/bugs/?67992 +\c +. .\" The entire document is monospaced as users are supposed to .\" navigate through it and perhaps even modify it inplace. .SCITECO_TT diff --git a/fallback.teco_ini b/fallback.teco_ini Binary files differindex c0b3a49..bd92c29 100644 --- a/fallback.teco_ini +++ b/fallback.teco_ini diff --git a/freebsd/Makefile b/freebsd/Makefile index 1dcd194..8550f5a 100644 --- a/freebsd/Makefile +++ b/freebsd/Makefile @@ -1,5 +1,5 @@ PORTNAME= sciteco -DISTVERSION= 2.5.0 +DISTVERSION= 2.5.2 CATEGORIES= editors textproc devel MASTER_SITES= https://sciteco.fmsbw.de/downloads/v${DISTVERSION}/ \ SOURCEFORGE/${PORTNAME}/v${DISTVERSION}/ diff --git a/freebsd/distinfo b/freebsd/distinfo index 49a7b48..9396b55 100644 --- a/freebsd/distinfo +++ b/freebsd/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1745085748 -SHA256 (sciteco-2.4.0.tar.gz) = 5b053644d8365eb0fddd9b268af9d6c6c44786dfa51dd4cbb962abac66670402 -SIZE (sciteco-2.4.0.tar.gz) = 4077220 +TIMESTAMP = 1776638070 +SHA256 (sciteco-2.5.2.tar.gz) = 94149fa3371e3adff723ae933502b5983804c4e06a681c6d00b0af19c439a978 +SIZE (sciteco-2.5.2.tar.gz) = 4113973 diff --git a/freebsd/pkg-plist b/freebsd/pkg-plist index ab74145..dd3e7cb 100644 --- a/freebsd/pkg-plist +++ b/freebsd/pkg-plist @@ -13,6 +13,7 @@ share/man/man7/%%PROGRAM_PREFIX%%sciteco.7.gz %%DATADIR%%/lib/getopt.tes %%DATADIR%%/lib/lexer.tes %%DATADIR%%/lib/repl.tes +%%DATADIR%%/lib/tank.tes %%DATADIR%%/lib/tecat.tes %%LEXILLA%%%%DATADIR%%/lib/lexers/abaqus.tes %%LEXILLA%%%%DATADIR%%/lib/lexers/ada.tes diff --git a/lib/Makefile.am b/lib/Makefile.am index 0da616a..04e83ad 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,5 +1,5 @@ dist_scitecolib_DATA = color.tes lexer.tes session.tes opener.tes \ - fnkeys.tes string.tes getopt.tes + fnkeys.tes string.tes getopt.tes tank.tes # Standalone scripts. # These are not installed via _SCRIPTS as it would add the --program-prefix. diff --git a/lib/colors/solarized.tes b/lib/colors/solarized.tes index 11567d9..91b2718 100644 --- a/lib/colors/solarized.tes +++ b/lib/colors/solarized.tes @@ -17,41 +17,24 @@ * `solarized.toggle` will be mapped to the F5 function key. * F5 will also automatically terminate the command line. *! -0EJ-1"= - 000,043,054:M[color.rgb],08,3EJ Q[color.lblack] U[color.base03] - 007,054,066:M[color.rgb],00,3EJ Q[color.black] U[color.base02] - 088,110,117:M[color.rgb],10,3EJ Q[color.lgreen] U[color.base01] - 101,123,131:M[color.rgb],11,3EJ Q[color.lyellow] U[color.base00] - 131,148,150:M[color.rgb],12,3EJ Q[color.lblue] U[color.base0] - 147,161,161:M[color.rgb],14,3EJ Q[color.lcyan] U[color.base1] - 238,232,213:M[color.rgb],07,3EJ Q[color.white] U[color.base2] - 253,246,227:M[color.rgb],15,3EJ Q[color.lwhite] U[color.base3] - 181,137,000:M[color.rgb],03,3EJ !* yellow *! - 203,075,022:M[color.rgb],09,3EJ Q[color.lred] U[color.orange] - 220,050,047:M[color.rgb],01,3EJ !* red *! - 211,054,130:M[color.rgb],05,3EJ !* magenta *! - 108,113,196:M[color.rgb],13,3EJ Q[color.lmagenta]U[color.violet] - 038,139,210:M[color.rgb],04,3EJ !* blue *! - 042,161,152:M[color.rgb],06,3EJ !* cyan *! - 133,153,000:M[color.rgb],02,3EJ !* green *! -| - 000,043,054:M[color.rgb]U[color.base03] - 007,054,066:M[color.rgb]U[color.base02] - 088,110,117:M[color.rgb]U[color.base01] - 101,123,131:M[color.rgb]U[color.base00] - 131,148,150:M[color.rgb]U[color.base0] - 147,161,161:M[color.rgb]U[color.base1] - 238,232,213:M[color.rgb]U[color.base2] - 253,246,227:M[color.rgb]U[color.base3] - 181,137,000:M[color.rgb]U[color.yellow] - 203,075,022:M[color.rgb]U[color.orange] - 220,050,047:M[color.rgb]U[color.red] - 211,054,130:M[color.rgb]U[color.magenta] - 108,113,196:M[color.rgb]U[color.violet] - 038,139,210:M[color.rgb]U[color.blue] - 042,161,152:M[color.rgb]U[color.cyan] - 133,153,000:M[color.rgb]U[color.green] -' +0,3EJ + +000,043,054:M[color.rgb]U[color.base03] +007,054,066:M[color.rgb]U[color.base02] +088,110,117:M[color.rgb]U[color.base01] +101,123,131:M[color.rgb]U[color.base00] +131,148,150:M[color.rgb]U[color.base0] +147,161,161:M[color.rgb]U[color.base1] +238,232,213:M[color.rgb]U[color.base2] +253,246,227:M[color.rgb]U[color.base3] +181,137,000:M[color.rgb]U[color.yellow] +203,075,022:M[color.rgb]U[color.orange] +220,050,047:M[color.rgb]U[color.red] +211,054,130:M[color.rgb]U[color.magenta] +108,113,196:M[color.rgb]U[color.violet] +038,139,210:M[color.rgb]U[color.blue] +042,161,152:M[color.rgb]U[color.cyan] +133,153,000:M[color.rgb]U[color.green] :Q[solarized.light]"< 0U[solarized.light] ' @[solarized.light]{ diff --git a/lib/colors/terminal.tes b/lib/colors/terminal.tes index ea048aa..39c5bac 100644 --- a/lib/colors/terminal.tes +++ b/lib/colors/terminal.tes @@ -9,6 +9,7 @@ Q[color.white]U[color.selback] !* Also used for popups *! [color.calltip] 0,Q[color.lwhite],Q[color.black] +!* Due to the bold attribute this should be grey even on 8-color terminals *! [color.comment] 1,Q[color.black],Q[color.lblack] [color.number] 0,Q[color.black],Q[color.cyan] [color.keyword] 1,Q[color.black],Q[color.lwhite] diff --git a/lib/fnkeys.tes b/lib/fnkeys.tes Binary files differindex 937caf9..f58a26a 100644 --- a/lib/fnkeys.tes +++ b/lib/fnkeys.tes diff --git a/lib/opener.tes b/lib/opener.tes index c0183c8..8bcd896 100644 --- a/lib/opener.tes +++ b/lib/opener.tes @@ -36,3 +36,18 @@ EBN[\.i] Q.c-1,Q.l-1ESFINDCOLUMN:J > ]* } + +!*$ + * M[opener.check-recovery] -- Warn if there if a recovery file (#filename#) is detected + * + * This points to a prior crash or unexpected termination. + *! +@[opener.check-recovery]{ + :Q*"= ' + [* + EQ.f G* I# R + <-A"I 1; ' :R;> + I# + 1:EN*Q.f"S 2Detected recovery file "Q.f" ' + ]* +} diff --git a/lib/tank.tes b/lib/tank.tes new file mode 100644 index 0000000..0894009 --- /dev/null +++ b/lib/tank.tes @@ -0,0 +1,211 @@ +!* + * TANK MODE + * + * Drive an ASCII tank around your buffer. + * Invoke via EIQ[$SCITECOPATH]/tank.tes + *! +[tank.sprite.0] !* background *! + +!* FIXME: Perhaps encode the gun's top in the integer cell *! +[tank.sprite.1] +█▓█▓ +╠════○ +▓█▓█ +[tank.sprite.2] + █▓█▓ +○════╣ + ▓█▓█ +[tank.sprite.3] + ┌┐ +▓││█ +█││▓ +▓┴┴█ +[tank.sprite.4] +█┬┬▓ +▓││█ +█││▓ + └┘ + +!* + * f,tM[tank.rand] - LCG pseudo random number generator for values between f and t + * This is based on the version in the BSD libc. + *! +::U[tank.rand] +@[tank.rand]{ + U.tU.f + (1103515245*Q[tank.rand]+12345) & (2^*31-1)U[tank.rand] + (Q[tank.rand]^/(Q.t-Q.f+1)+Q.f) +} + +!* x,yM[tank.goto] -> success|failure *! +@[tank.goto]{ + U.yU.x + Q.x"< 0 ' Q.y"< 0 ' + J Q.y:L"F 0 ' + Q.x<0A"< I F> ' 0A-10"= I F> ' C> + -1 +} + +!* x,y,spriteM[tank.blit] *! +@[tank.blit]{ + U.sU.yU.x Q.xU.#x0 + Q.s"N + 10[tank.sprite.0] !* background *! + %[tank.blit] + ' + < + %.iQ[tank.sprite.\.s]U.c Q.c:; + Q[tank.blit]&1"= + !* toggle the chain character to simulate movements *! + Q.c-█"= ▓U.c | Q.c-▓"= █U.c ' ' + ' + + Q.c-10"= + %.y Q.#x0U.x + Q.s"N 10:[tank.sprite.0] ' + | + Q.x,Q.yM[tank.goto]; + Q.s"N !* not background sprite *! + Q.c- "= + !* space is "transparent" - copy background unmodified *! + 0A"< I | 0A-10"= I | C ' ' + -A:[tank.sprite.0] + %.x F< + ' + !* "destroy" fields we have touched *! + U.d + 0A"F 0A-10"N 0A- "N 33,126M[tank.rand]U.d ' ' ' + Q.d:[tank.sprite.0] + ' + 0A"F 0A-10"N D ' ' Q.cI + %.x + ' + > +} + +@[tank.explode]{ + !!.[explosion]*oO%. + .[explosion]✹✸✶✧· + :Q.[explosion]< + 0A"F 0A-10"N D ' ' + %.i-1Q.[explosion]IR + > +} + +0U[tank.score] + +@[tank.shoot]{ + U.#dr U.yU.x + + !* FIXME: Could also be encoded into a map *! + Q.#dr-1Oright,left,up,down + !right! + 6-1%.x %.y + 1U.#dx 0U.#dy + Oend + !left! + %.y + -U.#dx 0U.#dy + Oend + !up! + 2-1%.x + 0U.#dx -U.#dy + Oend + !down! + 2-1%.x 3%.y + 0U.#dx 1U.#dy + !* fall through *! + !end! + < + (Q.#dx%.x),(Q.#dy%.y)M[tank.goto]"F ' + 0A"< ' Q.#dy"= 0A-10"= ' ' + 0A- "N 0A-10"N 1; ' ' + 0A"F 0A-10"N D ' ' I● + -DI + > + M[tank.explode] + + %[tank.score] 1SCORE: \[tank.score] +} + +@[tank.selfdestruct]{ + U.yU.x Q.x+2U.#cx Q.y+2U.#cy + .[explosion]✹✹✸✸✶✶✧✧· + 10< + Q.#cx-10U.x 20< + Q.x-1"< %.x F> ' + Q.#cy-10U.y 20< + !* d = sqrt((x - cx)^2 + ((y - cy) * aspect)^2) *! + (Q.x-Q.#cx)^*2 + ((Q.y-Q.#cy)*2)^*2 U.#d2 + + Q.r^*2 - Q.#d2"< + Q.#d2 - (Q.r+2)^*2"< + !* on shock wave *! + Q.x,Q.yM[tank.goto]"F %.y F> ' + -D Q.rQ.[explosion]I + ' + | + !* within shock wave *! + Q.x,Q.yM[tank.goto]"F %.y F> ' + -D I + ' + %.y> + %.x> + 1 + %.r> + Q.#cx,Q.#cyM[tank.goto] +} + +ESGETCOLUMNU.x :-1U.y +Q.xU.#xo Q.yU.#yo + +!* + * Normalize identations to spaces -- assumed by M[goto] + * FIXME: Will not work well with character representations. + *! +ESGETTABDRAWMODE-2"N + J <S^I; + ESGETCOLUMN-(-1ESGETCOLUMN)U.w + -D Q.w<I > + > +' + +1CONTROL: W A S D. SHOOT: SPACE. QUIT: ESCAPE. PRESS ANY KEY TO START. + +ESGETCARETSTYLEU.[caretstyle] +0ESSETCARETSTYLE + +1U.[sprite] +< + Q.#xo,Q.#yo,0M[tank.blit] !* redraw background *! + Q.xU.#xo Q.yU.#yo + + Q.x,Q.y,Q.[sprite]M[tank.blit] + 0 U.c + :Okey.U.c + F> + !key.w! + Q.y"> -%.y ' 3U.[sprite] + F> + !key.a! + Q.x,Q.y,0M[tank.blit] !* redraw background *! + Q.x"> -%.x ' 2U.[sprite] + F> + !key.s! + Q.x,Q.y,0M[tank.blit] !* redraw background *! + %.y 4U.[sprite] + F> + !key.d! + Q.x,Q.y,0M[tank.blit] !* redraw background *! + %.x 1U.[sprite] + F> + !key. ! + !* FIXME: this also scrolls due to *! + Q.x,Q.y,Q.[sprite]M[tank.shoot] + F> + !key.! + Q.x,Q.yM[tank.selfdestruct] + 1; +> + +Q.[caretstyle]ESSETCARETSTYLE diff --git a/m4/ax_with_ncurses.m4 b/m4/ax_with_ncurses.m4 index 4dc2f33..cdcfd04 100644 --- a/m4/ax_with_ncurses.m4 +++ b/m4/ax_with_ncurses.m4 @@ -5,8 +5,8 @@ # DESCRIPTION # # This macro checks for an ncurses library with enhanced definitions -# providing a curses.h either in the default search path or as -# established by pkg-config. +# (including wide-char support) providing a curses.h either in the default +# search path or as established by pkg-config. # # It is based on the AX_WITH_CURSES macro but does not attempt # to find any non-standard header, which would require #ifdefing diff --git a/src/cmdline.c b/src/cmdline.c index fa69d91..b944b5e 100644 --- a/src/cmdline.c +++ b/src/cmdline.c @@ -99,6 +99,14 @@ teco_cmdline_init(void) teco_cmdline_ssm(SCI_SETTABDRAWMODE, SCTD_CONTROLCHAR, 0); /* + * FIXME: This works around a problem where the caret is not + * scrolled in multi-line mode after it wraps at the end of the + * line. You cannot scroll the command-line with the mouse, so the + * user won't see any difference. + */ + teco_cmdline_ssm(SCI_SETENDATLASTLINE, FALSE, 0); + + /* * FIXME: Something resets the margin text, so we have to set it last. */ teco_cmdline_ssm(SCI_MARGINSETTEXT, 0, (sptr_t)"*"); @@ -162,12 +170,32 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) * Result of command line replacement (}): * Exchange command lines */ + g_clear_error(&tmp_error); + teco_qreg_t *cmdline_reg = teco_qreg_table_find(&teco_qreg_table_globals, "\e", 1); g_auto(teco_string_t) new_cmdline = {NULL, 0}; if (!cmdline_reg->vtable->get_string(cmdline_reg, &new_cmdline.data, &new_cmdline.len, - NULL, error)) + NULL, &tmp_error)) { + teco_error_add_frame_toplevel(); + teco_error_display_short(tmp_error); + g_propagate_error(error, g_steal_pointer(&tmp_error)); + return FALSE; + } + + /* + * SciTECO code must always be UTF-8, but you can smuggle arbitrary bytes + * into the "\e" register. + * This would be cumbersome to test for in teco_state_start_cmdline_pop(). + */ + if (!teco_string_validate_utf8(new_cmdline)) { + g_set_error_literal(&tmp_error, TECO_ERROR, TECO_ERROR_CODEPOINT, + "Invalid UTF-8 byte sequence in command-line replacement"); + teco_error_add_frame_toplevel(); + teco_error_display_short(tmp_error); + g_propagate_error(error, g_steal_pointer(&tmp_error)); return FALSE; + } /* * Search for first differing character in old and @@ -229,7 +257,7 @@ teco_cmdline_insert(const gchar *data, gsize len, GError **error) } } - /* error is handled in teco_cmdline_keypress_c() */ + /* error is handled in teco_cmdline_keypress() */ g_propagate_error(error, g_steal_pointer(&tmp_error)); return FALSE; } diff --git a/src/core-commands.c b/src/core-commands.c index f81bdf3..d2c8b9d 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -1739,8 +1739,10 @@ TECO_DEFINE_STATE(teco_state_ascii, /*$ ^[^[ ^[$ $$ ^C terminate return * [a1,a2,...]$$ -- Terminate command line or return from macro + * -$$ * [a1,a2,...]^[$ * [a1,a2,...]^C + * -^C * * Returns from the current macro invocation. * This will pass control to the calling macro immediately @@ -1754,6 +1756,8 @@ TECO_DEFINE_STATE(teco_state_ascii, * All braces opened in the current macro invocation will * be closed and their values discarded. * Only the direct arguments to \fB$$\fP will be kept. + * If the prefix sign is set (\(lq-\fB$$\fP\(rq) the command + * will always leave -1 on the stack. * * Returning from the top-level macro in batch mode * will exit the program or start up interactive mode depending @@ -1806,6 +1810,9 @@ teco_return(teco_machine_main_t *ctx, GError **error) ctx->parent.current = &teco_state_start; if (!teco_expressions_eval(FALSE, error)) return NULL; + if (teco_num_sign < 0) + /* only if you wrote -$$ */ + teco_expressions_push(1); teco_error_return_set(error, teco_expressions_args()); return NULL; } @@ -1873,6 +1880,18 @@ TECO_DEFINE_STATE_START(teco_state_escape, .end_of_macro_cb = teco_state_escape_end_of_macro ); +static gboolean +teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error) +{ + if (G_UNLIKELY(ctx == &teco_cmdline.machine)) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "<^C> is not allowed to terminate command-lines"); + return FALSE; + } + + return TRUE; +} + /* * Just like ^[, ^C actually implements a lookahead, * so a ^C itself does nothing. @@ -1892,20 +1911,20 @@ teco_state_ctlc_input(teco_machine_main_t *ctx, gunichar chr, GError **error) } static gboolean -teco_state_ctlc_initial(teco_machine_main_t *ctx, GError **error) +teco_state_ctlc_end_of_macro(teco_machine_main_t *ctx, GError **error) { - if (G_UNLIKELY(ctx == &teco_cmdline.machine)) { - g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, - "<^C> is not allowed to terminate command-lines"); - return FALSE; - } - + if (ctx->flags.mode > TECO_MODE_NORMAL) + return TRUE; + if (teco_num_sign < 0) + /* only if you wrote -^C */ + teco_expressions_push(1); return TRUE; } TECO_DEFINE_STATE_START(teco_state_ctlc, .initial_cb = (teco_state_initial_cb_t)teco_state_ctlc_initial, - .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input + .input_cb = (teco_state_input_cb_t)teco_state_ctlc_input, + .end_of_macro_cb = (teco_state_end_of_macro_cb_t)teco_state_ctlc_end_of_macro ); static teco_state_t * @@ -1983,7 +2002,7 @@ TECO_DEFINE_STATE_COMMAND(teco_state_ctlc_control, * Without any argument ED returns the current flags. * * Currently, the following flags are used by \*(ST: - * .IP 2: 5 + * .IP 2: 6 * Reflects whether program termination has been requested * by successfully performing the \fBEX\fP command. * This flag can also be used to cancel the effect of any @@ -2117,46 +2136,35 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * first. * Memory limiting is enabled by default. * .IP 3: - * This \fBwrite-only\fP property allows redefining the - * first 16 entries of the terminal color palette \(em a - * feature required by some - * color schemes when using the Curses user interface. - * When setting this property, you are making a request - * to define the terminal \fIcolor\fP as the Scintilla-compatible - * RGB color value given in the \fIrgb\fP parameter. - * \fIcolor\fP must be a value between 0 and 15 - * corresponding to black, red, green, yellow, blue, magenta, - * cyan, white, bright black, bright red, etc. in that order. - * The \fIrgb\fP value has the format 0xBBGGRR, i.e. the red - * component is the least-significant byte and all other bytes - * are ignored. - * Note that on curses, RGB color values sent to Scintilla - * are actually mapped to these 16 colors by the Scinterm port - * and may represent colors with no resemblance to the \(lqRGB\(rq - * value used (depending on the current palette) \(em they should - * instead be viewed as placeholders for 16 standard terminal - * color codes. - * Please refer to the Scinterm manual for details on the allowed - * \(lqRGB\(rq values and how they map to terminal colors. - * This command provides a crude way to request exact RGB colors - * for the first 16 terminal colors. - * The color definition may be queued or be completely ignored - * on other user interfaces and no feedback is given - * if it fails. In fact feedback cannot be given reliably anyway. - * Note that on 8 color terminals, only the first 8 colors - * can be redefined (if you are lucky). - * Note that due to restrictions of most terminal emulators - * and some curses implementations, this command simply will not - * restore the original palette entry or request - * when rubbed out and should generally only be used in - * \fIbatch-mode\fP \(em typically when loading a color scheme. - * For the same reasons \(em even though \*(ST tries hard to - * restore the original palette on exit \(em palette changes may - * persist after \*(ST terminates on most terminal emulators on Unix. - * The only emulator which will restore their default palette - * on exit the author is aware of is \fBxterm\fP(1) and - * the Linux console driver. - * You have been warned. Good luck. + * This \fBwrite-only\fP property allows disabling use of + * the 16 color palette of default colors when using + * the Curses user interface. + * By default, when working with RGB values in Scintilla + * messages (and \*(ST color schemes for that matter) + * the 16 \(lqdefault\(rq colors are represented by + * special RGB placeholders (e.g. 0x000080 for \fICOLOR_RED\fP) + * as documented in the Scinterm manual. + * Any other RGB values will be automatically allocated + * in the terminal emulator if supported, giving the illusion + * of true color support, even though the actual number of + * concurrent colors on screen may be limited by the terminal + * emulator (see \fImax_colors\fP and \fImax_pairs\fP + * .B terminfo (1) + * capabilities) and other factors. + * \*(ST tries to avoid the first 16 color ids so as not to + * interfer with other terminal-based applications. + * The default palette however may be overwritten when requesting + * new colors in terminals restricted to 16 colors. + * In this case the original palette might not be restored properly on + * program termination and affect other terminal applications. + * The 16 predefined colors do not map to exact RGB values, + * though, but point into a palette at the terminal emulator. + * \*(ST therefore allows you to disable the 16 RGB placeholders + * by calling \(lq0,3EJ\(rq so that all colors are freshly initialized + * and will represent their exact RGB values. + * It has no effect when using the GTK interface. + * You are recommended to add \(lq0,3EJ\(rq to all color + * schemes that rely on exact RGB values. * .IP 4: * The column after the last horizontal movement. * This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal @@ -2169,12 +2177,14 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * .IP 6: * .SCITECO_TOPIC recovery * Interval in seconds for the creation of recovery files - * or 0 if those dumps are disabled (the default is 300 seconds). + * or 0 if those dumps are disabled (the default is 120 seconds). * When enabled all dirty buffers are dumped to files with hash * signs around the original basename (\fB#\fIfilename\fB#\fR). * They are removed automatically when no longer required, * but may be left around when the \*(ST crashes or terminates * unexpectedly. + * During graceful shutdowns (\fBSIGTERM\fP etc.) recovery + * files may be dumped immediately even before the interval expires. * After changing the interval, the new value may become * active only after the previous interval expires. * Recovery files are not dumped in batch mode. @@ -2229,7 +2239,7 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) EJ_USER_INTERFACE = 0, EJ_BUFFERS, EJ_MEMORY_LIMIT, - EJ_INIT_COLOR, + EJ_PALETTE, EJ_CARETX, EJ_CMDLINE_HEIGHT, EJ_RECOVERY_INTERVAL @@ -2253,19 +2263,13 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) return; break; - case EJ_INIT_COLOR: - if (value < 0 || value >= 16) { - g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, - "Invalid color code %" TECO_INT_FORMAT " " - "specified for <EJ>", value); - return; - } - if (!teco_expressions_args()) { - teco_error_argexpected_set(error, "EJ"); + case EJ_PALETTE: + if (teco_is_success(value)) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Default color palette can only be disabled"); return; } - teco_interface_init_color((guint)value, - (guint32)teco_expressions_pop_num(0)); + teco_interface_disable_palette(); break; case EJ_CARETX: diff --git a/src/interface-curses/curses-info-popup.c b/src/interface-curses/curses-info-popup.c index edb6e15..83d4665 100644 --- a/src/interface-curses/curses-info-popup.c +++ b/src/interface-curses/curses-info-popup.c @@ -73,7 +73,7 @@ teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_ } static void -teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) +teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair) { int pad_lines; /**! pad height */ gint pad_cols; /**! entry columns */ @@ -102,11 +102,11 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) ctx->pad = newpad(pad_lines, COLS - 2); /* - * NOTE: attr could contain A_REVERSE on monochrome terminals, + * NOTE: attr could contain WA_REVERSE on monochrome terminals, * so we use foreground attributes instead of background attributes. - * This way, we can cancel out the A_REVERSE if necessary. + * This way, we can cancel out the WA_REVERSE if necessary. */ - wattrset(ctx->pad, attr); + wattr_set(ctx->pad, attr, pair, NULL); teco_curses_clrtobot(ctx->pad); /* @@ -161,7 +161,7 @@ teco_curses_info_popup_init_pad(teco_curses_info_popup_t *ctx, attr_t attr) } void -teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) +teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair) { if (!ctx->length) /* nothing to display */ @@ -171,7 +171,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) delwin(ctx->window); if (!ctx->pad) - teco_curses_info_popup_init_pad(ctx, attr); + teco_curses_info_popup_init_pad(ctx, attr, pair); gint pad_lines = getmaxy(ctx->pad); /* @@ -183,15 +183,17 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) /* window covers message, scintilla and info windows */ ctx->window = newwin(popup_lines, 0, LINES - teco_cmdline.height - popup_lines, 0); - wattrset(ctx->window, attr); + wattr_set(ctx->window, attr, pair, NULL); - wborder(ctx->window, - ACS_VLINE, - ACS_VLINE, /* may be overwritten with scrollbar */ - ACS_HLINE, - ' ', /* no bottom line */ - ACS_ULCORNER, ACS_URCORNER, - ACS_VLINE, ACS_VLINE); + /* + * NOTE: wborder() is broken for large pair numbers, at least on ncurses. + */ + waddch(ctx->window, ACS_ULCORNER); + whline(ctx->window, ACS_HLINE, COLS - 2); + mvwaddch(ctx->window, 0, COLS - 1, ACS_URCORNER); + mvwvline(ctx->window, 1, 0, ACS_VLINE, getmaxy(ctx->window)-1); + /* may be overwritten with scrollbar */ + mvwvline(ctx->window, 1, COLS - 1, ACS_VLINE, getmaxy(ctx->window)-1); copywin(ctx->pad, ctx->window, ctx->pad_first_line, 0, @@ -214,7 +216,7 @@ teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr) * Instead, simply draw reverse blanks. */ wmove(ctx->window, bar_y, COLS-1); - wattrset(ctx->window, attr ^ A_REVERSE); + wattr_set(ctx->window, attr ^ WA_REVERSE, pair, NULL); wvline(ctx->window, ' ', bar_height); } diff --git a/src/interface-curses/curses-info-popup.h b/src/interface-curses/curses-info-popup.h index fd923e9..898ba70 100644 --- a/src/interface-curses/curses-info-popup.h +++ b/src/interface-curses/curses-info-popup.h @@ -49,7 +49,7 @@ teco_curses_info_popup_init(teco_curses_info_popup_t *ctx) void teco_curses_info_popup_add(teco_curses_info_popup_t *ctx, teco_popup_entry_type_t type, const gchar *name, gsize name_len, gboolean highlight); -void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr); +void teco_curses_info_popup_show(teco_curses_info_popup_t *ctx, attr_t attr, gshort pair); 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); diff --git a/src/interface-curses/curses-utils.c b/src/interface-curses/curses-utils.c index 875c332..3b25d56 100644 --- a/src/interface-curses/curses-utils.c +++ b/src/interface-curses/curses-utils.c @@ -53,9 +53,9 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) /* * The entire background might be in reverse, especially * on monochrome terminals. - * In those cases, we have to __remove__ the A_REVERSE flag. + * In those cases, we have to __remove__ the WA_REVERSE flag. */ - attr_t attrs = A_NORMAL; + attr_t attrs = WA_NORMAL; short pair = 0; wattr_get(win, &attrs, &pair, NULL); @@ -81,28 +81,28 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) chars_added++; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddch(win, '$'); break; case '\r': chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "CR"); break; case '\n': chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "LF"); break; case '\t': chars_added += 3; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddstr(win, "TAB"); break; default: @@ -110,7 +110,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) chars_added += 2; if (chars_added > max_width) goto truncate; - wattr_set(win, attrs ^ A_REVERSE, pair, NULL); + wattr_set(win, attrs ^ WA_REVERSE, pair, NULL); waddch(win, '^'); waddch(win, TECO_CTL_ECHO(*str)); } else { @@ -126,7 +126,7 @@ teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_width) waddnstr(win, str, clen); } } - /* restore original state of A_REVERSE */ + /* restore original state of WA_REVERSE */ wattr_set(win, attrs, pair, NULL); str += clen; diff --git a/src/interface-curses/curses-utils.h b/src/interface-curses/curses-utils.h index 97fc1cc..c6d9d8d 100644 --- a/src/interface-curses/curses-utils.h +++ b/src/interface-curses/curses-utils.h @@ -27,15 +27,12 @@ guint teco_curses_format_str(WINDOW *win, const gchar *str, gsize len, gint max_ guint teco_curses_format_filename(WINDOW *win, const gchar *filename, gint max_width); -/** - * Add Unicode character to window. - * This is just like wadd_wch(), but does not require wide-char APIs. - */ +/** Add Unicode character to window. */ static inline void teco_curses_add_wc(WINDOW *win, gunichar chr) { - gchar buf[6]; - waddnstr(win, buf, g_unichar_to_utf8(chr, buf)); + wchar_t wc = chr; + waddnwstr(win, &wc, 1); } /** diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 0764db3..21e728f 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -125,7 +125,8 @@ teco_console_ctrl_handler(DWORD type) { switch (type) { case CTRL_C_EVENT: - teco_interrupted = TRUE; + case CTRL_BREAK_EVENT: + teco_interrupted = TECO_INTERRUPTED; return TRUE; } @@ -137,6 +138,17 @@ teco_console_ctrl_handler(DWORD type) static gint teco_xterm_version(void) G_GNUC_UNUSED; static gint teco_interface_blocking_getch(void); +static inline gboolean teco_interface_check_key(gint key); + +#define MAX_COLORS (MIN(COLORS, G_MAXSHORT)/2) +/** + * Maximum effective number of color pairs. + * Scinterm uses shorts for color pairs, so G_MAXSHORT is the maximum. + * Some Curses implementations (NetBSD, PDCurses) may support less, + * but set COLOR_PAIRS accordingly. + * Half of the range is reserved to Scinterm. + */ +#define MAX_PAIRS (MIN(COLOR_PAIRS, G_MAXSHORT)/2) /** * Get bright variant of one of the 8 standard @@ -154,6 +166,12 @@ static gint teco_interface_blocking_getch(void); * The 8 bright colors (if terminal supports at * least 16 colors), else they are identical to * the non-bright colors (default curses colors). + * + * As a consequence a light black (grey) foreground + * on black background might be invisible on 8 color + * terminals. + * If you add in A_BOLD, there is a good chance that + * the text will still become grey. */ #define COLOR_LBLACK COLOR_LIGHT(COLOR_BLACK) #define COLOR_LRED COLOR_LIGHT(COLOR_RED) @@ -166,30 +184,22 @@ static gint teco_interface_blocking_getch(void); static struct { /** + * Mapping of RGB codes (encoded into a pointer) + * to a curses color number. + */ + GHashTable *color_table; + /** * Mapping of foreground and background curses color tuples * (encoded into a pointer) to a color pair number. */ GHashTable *pair_table; - /** - * Mapping of the first 16 curses color codes (that may or may not - * correspond with the standard terminal color codes) to - * Scintilla-compatible RGB values (red is LSB) to initialize after - * Curses startup. - * Negative values mean no color redefinition (keep the original - * palette entry). + * Whether to map the 16 standard palettized colors + * in color_table. + * Should be disabled when using arbitrary RGB values, + * so every color is initialized to exact RGB values. */ - gint32 color_table[16]; - - /** - * Mapping of the first 16 curses color codes to their - * original values for restoring them on shutdown. - * Unfortunately, this may not be supported on all - * curses ports, so this array may be unused. - */ - struct { - gshort r, g, b; - } orig_color_table[16]; + gboolean use_palette; int stdin_orig, stdout_orig, stderr_orig; SCREEN *screen; @@ -232,56 +242,50 @@ static struct { * * Scinterm no longer initializes all color pairs for all combinations of * the builtin foreground and background colors. - * Since curses guarantees only 256 color pairs, we cannot do that either. - * Instead we allocate color pairs beginnig at 128 on demand - * (similar to what Scinterm does). - * - * @note Scinterm now also has scintilla_set_color_offsets(), - * so we could use the lower 127 color pairs as well. + * Since curses does not guarantee any number of color pairs, we cannot do that either. + * Instead we allocate color pairs in the first half of + * color pair space on demand, while the second half is reserved to Scinterm. * + * @param attr attributes to modify (for supporting monochrome terminals) * @param fg curses foreground color * @param bg curses background color * @return curses color pair number */ static gshort -teco_color_pair(gshort fg, gshort bg) +teco_color_pair(attr_t *attr, gshort fg, gshort bg) { - static gshort last_pair = 127; + static guint next_pair = 1; + + /* + * Basic support for monochrome terminals: + * Every background, that is not black is assumed to be a + * dark-on-bright area, rendered in reverse. + * This will at least work with the terminal.tes and contrast.tes + * color schemes. + */ + if (!has_colors()) { + if (bg != COLOR_BLACK) + *attr |= WA_REVERSE; + return 0; + } G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint)); - gpointer key = GUINT_TO_POINTER(((guint)fg << 16) | bg); + gpointer key = GUINT_TO_POINTER(((guint)fg << 8*sizeof(bg)) | bg); gpointer value = g_hash_table_lookup(teco_interface.pair_table, key); if (G_LIKELY(value != NULL)) return GPOINTER_TO_UINT(value); - init_pair(++last_pair, fg, bg); - g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(last_pair)); - return last_pair; + if (G_UNLIKELY(next_pair >= MAX_PAIRS)) + return 0; + init_pair(next_pair, fg, bg); + g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(next_pair)); + return next_pair++; } -/** - * Curses attribute for the color combination - * according to the color pairs initialized by - * Scinterm. - * This is equivalent to Scinterm's internal term_color_attr(). - * - * @param fg foreground color - * @param bg background color - * @return curses attribute - */ -static inline attr_t -teco_color_attr(gshort fg, gshort bg) +static inline gint +teco_wattr_set(WINDOW *win, attr_t attr, gshort fg, gshort bg) { - if (has_colors()) - return COLOR_PAIR(teco_color_pair(fg, bg)); - - /* - * Basic support for monochrome terminals: - * Every background, that is not black is assumed to be a - * dark-on-bright area, rendered in reverse. - * This will at least work with the terminal.tes - * color scheme. - */ - return bg != COLOR_BLACK ? A_REVERSE : 0; + gshort pair = teco_color_pair(&attr, fg, bg); + return wattr_set(win, attr, pair, NULL); } /** @@ -298,38 +302,60 @@ teco_rgb2curses_triple(guint32 rgb, gshort *r, gshort *g, gshort *b) *b = ((rgb & 0xFF0000) >> 16)*1000/0xFF; } +static inline void +teco_interface_insert_color(guint32 rgb, gshort color) +{ + /* + * Color ids are stored one-offset, so that COLOR_BLACK + * can be discerned from a missing entry. + */ + g_hash_table_insert(teco_interface.color_table, + GUINT_TO_POINTER(rgb), GUINT_TO_POINTER(color)+1); +} + /** * Convert a Scintilla-compatible RGB color value * (0xBBGGRR) to a Curses color code (e.g. COLOR_BLACK). - * This does not work with arbitrary RGB values but - * only the 16 RGB color values defined by Scinterm - * corresponding to the 16 terminal colors. - * It is equivalent to Scinterm's internal `term_color` - * function. + * + * This will automatically initialize new curses colors + * when necessary. + * Unless the palette is disabled with + * teco_interface_disable_palette(), this will return + * the 16 "standard" colors for a predefined list of + * RGB values (just like defined by Scinterm). */ static gshort teco_rgb2curses(guint32 rgb) { - switch (rgb) { - case 0x000000: return COLOR_BLACK; - case 0x000080: return COLOR_RED; - case 0x008000: return COLOR_GREEN; - case 0x008080: return COLOR_YELLOW; - case 0x800000: return COLOR_BLUE; - case 0x800080: return COLOR_MAGENTA; - case 0x808000: return COLOR_CYAN; - case 0xC0C0C0: return COLOR_WHITE; - case 0x404040: return COLOR_LBLACK; - case 0x0000FF: return COLOR_LRED; - case 0x00FF00: return COLOR_LGREEN; - case 0x00FFFF: return COLOR_LYELLOW; - case 0xFF0000: return COLOR_LBLUE; - case 0xFF00FF: return COLOR_LMAGENTA; - case 0xFFFF00: return COLOR_LCYAN; - case 0xFFFFFF: return COLOR_LWHITE; - } + static guint colors = 0; + + if (!has_colors()) + return COLOR_WHITE; - return COLOR_WHITE; + gpointer value = g_hash_table_lookup(teco_interface.color_table, + GUINT_TO_POINTER(rgb)); + if (G_LIKELY(value != NULL)) + return GPOINTER_TO_UINT(value)-1; + + /* + * Even when initializing new colors, we will __try__ not to + * touch the 16 palettized colors. + * First of all, we might still need them and secondly, + * this ensures we don't have to reset them on exit + * (which is not always possible anyway). + * There can still be 16 color terminals with + * can_change_color() == TRUE, though. + */ + guint next_color = colors + (COLORS > 16 ? 16 : 0); + + if (G_UNLIKELY(next_color >= MAX_COLORS) || !can_change_color()) + return COLOR_WHITE; + gshort r, g, b; + teco_rgb2curses_triple(rgb, &r, &g, &b); + init_color(next_color, r, g, b); + teco_interface_insert_color(rgb, next_color); + colors++; + return next_color; } static gint @@ -395,6 +421,13 @@ teco_view_noutrefresh(teco_view_t *ctx) scintilla_noutrefresh(ctx); } +static inline void +teco_view_update_cursor(teco_view_t *ctx) +{ + if (teco_view_ssm(ctx, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES) + scintilla_update_cursor(ctx); +} + static inline WINDOW * teco_view_get_window(teco_view_t *ctx) { @@ -413,9 +446,6 @@ teco_view_free(teco_view_t *ctx) scintilla_delete(ctx); } -static void teco_interface_init_color_safe(guint color, guint32 rgb); -static void teco_interface_restore_colors(void); - static void teco_interface_init_screen(void); static gboolean teco_interface_init_interactive(GError **error); static void teco_interface_restore_batch(void); @@ -428,12 +458,9 @@ static void teco_interface_set_window_title(const gchar *title); static void teco_interface_draw_info(void); void -teco_interface_init(void) +teco_interface_init(gint argc, gchar **argv) { - for (guint i = 0; i < G_N_ELEMENTS(teco_interface.color_table); i++) - teco_interface.color_table[i] = -1; - for (guint i = 0; i < G_N_ELEMENTS(teco_interface.orig_color_table); i++) - teco_interface.orig_color_table[i].r = -1; + teco_interface.use_palette = TRUE; teco_interface.stdin_orig = teco_interface.stdout_orig = teco_interface.stderr_orig = -1; @@ -442,9 +469,13 @@ teco_interface_init(void) teco_cmdline_init(); /* * The default INDIC_STRIKE wouldn't be visible. - * Instead we use INDIC_SQUIGGLE, which is rendered as A_UNDERLINE. + * Instead we use INDIC_SQUIGGLE, which is rendered as WA_UNDERLINE. */ teco_cmdline_ssm(SCI_INDICSETSTYLE, INDICATOR_RUBBEDOUT, INDIC_SQUIGGLE); + /* + * Enable hardware cursor by default. + */ + teco_cmdline_ssm(SCI_SETCARETSTYLE, CARETSTYLE_CURSES, 0); /* * On all platforms except Curses/XTerm, it's @@ -471,133 +502,11 @@ teco_interface_get_options(void) return NULL; } -static void -teco_interface_init_color_safe(guint color, guint32 rgb) -{ -#if defined(__PDCURSES__) && !defined(PDCURSES_GUI) - if (teco_interface.orig_color_table[color].r < 0) { - color_content((short)color, - &teco_interface.orig_color_table[color].r, - &teco_interface.orig_color_table[color].g, - &teco_interface.orig_color_table[color].b); - } -#endif - - gshort r, g, b; - teco_rgb2curses_triple(rgb, &r, &g, &b); - init_color((short)color, r, g, b); -} - -#if defined(__PDCURSES__) && !defined(PDCURSES_GUI) - -/* - * On PDCurses/WinCon, color_content() will actually return - * the real console color palette - or at least the default - * palette when the console started. - */ -static void -teco_interface_restore_colors(void) -{ - if (!can_change_color()) - return; - - for (guint i = 0; i < G_N_ELEMENTS(teco_interface.orig_color_table); i++) { - if (teco_interface.orig_color_table[i].r < 0) - continue; - - init_color((short)i, - teco_interface.orig_color_table[i].r, - teco_interface.orig_color_table[i].g, - teco_interface.orig_color_table[i].b); - } -} - -#elif defined(CURSES_TTY) - -/* - * FIXME: On UNIX/ncurses teco_interface_init_color_safe() __may__ - * change the terminal's palette permanently and there does not - * appear to be any portable way of restoring the original one. - * Curses has color_content(), but there is actually no terminal - * that allows querying the current palette and so color_content() - * will return bogus "default" values and only for the first 8 colors. - * It would do more damage to restore the palette returned by - * color_content() than it helps. - * xterm has the escape sequence "\e]104\a" which restores - * the palette from Xdefaults but not all terminal emulators - * claiming to be "xterm" via $TERM support this escape sequence. - * lxterminal for instance will print gibberish instead. - * So we try to look whether $XTERM_VERSION is set. - * There are hardly any other terminal emulators that support palette - * resets. - * The only emulator I'm aware of which can be identified reliably - * by $TERM supporting a palette reset is the Linux console - * (see console_codes(4)). The escape sequence "\e]R" is already - * part of its terminfo description (orig_colors capability) - * which is apparently sent by endwin(), so the palette is - * already properly restored on endwin(). - * Welcome in Curses hell. - */ -static void -teco_interface_restore_colors(void) -{ - if (teco_xterm_version() < 0) - return; - - /* - * Looks like a real XTerm - */ - fputs("\e]104\a", teco_interface.screen_tty); - fflush(teco_interface.screen_tty); -} - -#else /* (!__PDCURSES__ || PDCURSES_GUI) && !CURSES_TTY */ - -static void -teco_interface_restore_colors(void) -{ - /* - * No way to restore the palette, or it's - * unnecessary (e.g. XCurses) - */ -} - -#endif - void -teco_interface_init_color(guint color, guint32 rgb) +teco_interface_disable_palette(void) { - if (color >= G_N_ELEMENTS(teco_interface.color_table)) - return; - -#if defined(__PDCURSES__) && !defined(PDC_RGB) - /* - * PDCurses will usually number color codes differently - * (least significant bit is the blue component) while - * SciTECO macros will assume a standard terminal color - * code numbering with red as the LSB. - * Therefore we have to swap the bit order of the least - * significant 3 bits here. - */ - color = (color & ~0x5) | - ((color & 0x1) << 2) | ((color & 0x4) >> 2); -#endif - - if (teco_interface.input_pad) { - /* interactive mode */ - if (!can_change_color()) - return; - - teco_interface_init_color_safe(color, rgb); - } else { - /* - * batch mode: store colors, - * they can only be initialized after start_color() - * which is called by Scinterm when interactive - * mode is initialized - */ - teco_interface.color_table[color] = (gint32)rgb; - } + teco_interface.use_palette = FALSE; + scintilla_disable_color_palette(); } #ifdef CURSES_TTY @@ -739,8 +648,33 @@ teco_interface_init_interactive(GError **error) teco_interface_init_screen(); + teco_interface.color_table = g_hash_table_new(g_direct_hash, g_direct_equal); teco_interface.pair_table = g_hash_table_new(g_direct_hash, g_direct_equal); start_color(); + scintilla_set_color_offsets(MAX_COLORS, MAX_PAIRS); + + if (teco_interface.use_palette && has_colors()) { + teco_interface_insert_color(0x000000, COLOR_BLACK); + teco_interface_insert_color(0x000080, COLOR_RED); + teco_interface_insert_color(0x008000, COLOR_GREEN); + teco_interface_insert_color(0x008080, COLOR_YELLOW); + teco_interface_insert_color(0x800000, COLOR_BLUE); + teco_interface_insert_color(0x800080, COLOR_MAGENTA); + teco_interface_insert_color(0x808000, COLOR_CYAN); + teco_interface_insert_color(0xC0C0C0, COLOR_WHITE); + /* + * On 8-color terminals, we still support the + * higher (light) colors, but map them to the lower codes as well. + */ + teco_interface_insert_color(0x404040, COLOR_LBLACK); + teco_interface_insert_color(0x0000FF, COLOR_LRED); + teco_interface_insert_color(0x00FF00, COLOR_LGREEN); + teco_interface_insert_color(0x00FFFF, COLOR_LYELLOW); + teco_interface_insert_color(0xFF0000, COLOR_LBLUE); + teco_interface_insert_color(0xFF00FF, COLOR_LMAGENTA); + teco_interface_insert_color(0xFFFF00, COLOR_LCYAN); + teco_interface_insert_color(0xFFFFFF, COLOR_LWHITE); + } /* * On UNIX terminals, the escape key is usually @@ -784,8 +718,6 @@ teco_interface_init_interactive(GError **error) cbreak(); noecho(); - /* Scintilla draws its own cursor */ - curs_set(0); /* * This has also been observed to reduce flickering * in teco_interface_refresh(). @@ -824,19 +756,6 @@ teco_interface_init_interactive(GError **error) teco_interface_show_view(teco_interface_current_view); /* - * Only now it's safe to redefine the 16 default colors. - */ - if (can_change_color()) { - for (guint i = 0; i < G_N_ELEMENTS(teco_interface.color_table); i++) { - /* - * init_color() may still fail if COLORS < 16 - */ - if (teco_interface.color_table[i] >= 0) - teco_interface_init_color_safe(i, (guint32)teco_interface.color_table[i]); - } - } - - /* * Only now (in interactive mode), it's safe to initialize * the clipboard Q-Registers on ncurses with a compatible terminal * emulator since clipboard operations will no longer interfer @@ -868,7 +787,6 @@ teco_interface_restore_batch(void) * (i.e. return to batch mode) */ endwin(); - teco_interface_restore_colors(); /* * Restore stdin, stdout and stderr, so output goes to @@ -933,7 +851,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) teco_interface_stdio_msg(type, str, len); #endif - short fg, bg; + gshort fg, bg; fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); @@ -955,7 +873,7 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) } wmove(teco_interface.msg_window, 0, 0); - wattrset(teco_interface.msg_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.msg_window, 0, fg, bg); teco_curses_format_str(teco_interface.msg_window, str, len, -1); teco_curses_clrtobot(teco_interface.msg_window); } @@ -966,11 +884,11 @@ teco_interface_msg_clear(void) if (!teco_interface.input_pad) /* batch mode */ return; - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); wmove(teco_interface.msg_window, 0, 0); - wattrset(teco_interface.msg_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.msg_window, 0, fg, bg); teco_curses_clrtobot(teco_interface.msg_window); } @@ -984,25 +902,41 @@ teco_interface_getch(gboolean widechar) /* * Signal that we accept input by drawing a real cursor in the message bar. + * FIXME: curs_set(2) would be better, but isn't always visible. */ wmove(teco_interface.msg_window, 0, 0); - curs_set(1); wrefresh(teco_interface.msg_window); + curs_set(1); gchar buf[4]; gint i = 0; gint32 cp; do { - cp = teco_interface_blocking_getch(); - if (cp == TECO_CTL_KEY('C')) - teco_interrupted = TRUE; - if (cp == TECO_CTL_KEY('C') || cp == TECO_CTL_KEY('D')) { - cp = -1; - break; - } - if (cp < 0 || cp > 0xFF) - continue; + do { + cp = teco_interface_blocking_getch(); + switch (cp) { +#ifdef KEY_RESIZE + case KEY_RESIZE: + teco_interface_resize_all_windows(); + teco_interface_refresh(FALSE); + /* cursor was moved by the resize */ + wmove(teco_interface.msg_window, 0, 0); + wrefresh(teco_interface.msg_window); + break; +#endif + case KEY_BACKSPACE: + return TECO_CTL_KEY('H'); + case KEY_ENTER: + return '\n'; + case TECO_CTL_KEY('C'): + teco_interrupted = TECO_INTERRUPTED; + /* fall through */ + case TECO_CTL_KEY('D'): + /* emulates EOF on stdin */ + return -1; + } + } while (cp < 0 || cp > 0xFF || !teco_interface_check_key(cp)); if (!widechar || !cp) break; @@ -1014,7 +948,6 @@ teco_interface_getch(gboolean widechar) i = 0; } while (cp < 0); - curs_set(0); return cp; } @@ -1144,11 +1077,11 @@ teco_interface_draw_info(void) * the current buffer's STYLE_DEFAULT. * The same style is used for MSG_USER messages. */ - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); wmove(teco_interface.info_window, 0, 0); - wattrset(teco_interface.info_window, teco_color_attr(fg, bg)); + teco_wattr_set(teco_interface.info_window, 0, fg, bg); const gchar *info_type_str; @@ -1229,7 +1162,20 @@ teco_interface_info_update_buffer(const teco_buffer_t *buffer) * default clipboard ("~") as we do not know whether * it corresponds to the X11 PRIMARY, SECONDARY or * CLIPBOARD selections. + * + * On XCurses we must not (and don't have to) probe + * the clipboard as it would be before Xinitscr(). */ +#ifdef XCURSES + +static void +teco_interface_init_clipboard(void) +{ + teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); +} + +#else /* XCURSES */ + static void teco_interface_init_clipboard(void) { @@ -1254,6 +1200,8 @@ teco_interface_init_clipboard(void) teco_qreg_table_replace(&teco_qreg_table_globals, teco_qreg_clipboard_new("")); } +#endif /* !XCURSES */ + gboolean teco_interface_set_clipboard(const gchar *name, const gchar *str, gsize str_len, GError **error) { @@ -1702,11 +1650,13 @@ teco_interface_popup_show(gsize prefix_len) /* batch mode */ return; - short fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); - short bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + gshort 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, teco_color_attr(fg, bg)); + attr_t attr = 0; + gshort pair = teco_color_pair(&attr, fg, bg); + teco_curses_info_popup_show(&teco_interface.popup, attr, pair); } void @@ -1757,7 +1707,7 @@ teco_interface_popup_clear(void) gboolean teco_interface_is_interrupted(void) { - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; } #else /* !CURSES_TTY && !PDCURSES_WINCON && !NCURSES_WIN32 */ @@ -1776,7 +1726,7 @@ teco_interface_is_interrupted(void) { if (!teco_interface.input_pad) /* batch mode */ - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; /* * NOTE: wgetch() is configured to be nonblocking. @@ -1791,7 +1741,7 @@ teco_interface_is_interrupted(void) GINT_TO_POINTER(key)); } - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; } #endif @@ -1824,7 +1774,31 @@ teco_interface_refresh(gboolean force) wnoutrefresh(teco_interface.msg_window); teco_view_noutrefresh(teco_cmdline.view); teco_curses_info_popup_noutrefresh(&teco_interface.popup); + /* + * If hardware cursors (CARETSTYLE_CURSES) are enabled on the + * command-line view, make sure that the cursor is left + * in the correct position. + * + * FIXME: This shouldn't be necessary if we refreshed the command-line + * view last. Also, if we wanted to support the hardware cursor + * in the main view as well, we'd have to handle a possibly + * overlappig info popup. + */ + teco_view_update_cursor(teco_cmdline.view); doupdate(); + + /* + * Scinterm enables/disables the hardware cursor, + * but only if CARETSTYLE_CURSES is used. + * Disabling the cursor repeatedly ensures you can change + * the caret style interactively. + * It also makes sure the cursor is reset after + * teco_interface_getch(). + * Also, window resizes sometimes enable the hardware cursor on + * PDCurses/wincon (FIXME). + */ + if (!(teco_view_ssm(teco_cmdline.view, SCI_GETCARETSTYLE, 0, 0) & CARETSTYLE_CURSES)) + curs_set(0); } #if NCURSES_MOUSE_VERSION >= 2 @@ -1882,9 +1856,11 @@ teco_interface_process_mevent(MEVENT *event, GError **error) else if (event->bstate & BUTTON_NUM(5)) teco_curses_info_popup_scroll(&teco_interface.popup, +2); - 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, teco_color_attr(fg, bg)); + gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); + gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + attr_t attr = 0; + gshort pair = teco_color_pair(&attr, fg, bg); + teco_curses_info_popup_show(&teco_interface.popup, attr, pair); return TRUE; } @@ -2016,7 +1992,8 @@ teco_interface_blocking_getch(void) gboolean new_mousekey = (teco_ed & TECO_ED_MOUSEKEY) != 0; if (new_mousekey != old_mousekey) { old_mousekey = new_mousekey; - mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED); + mmask_t mmask = BUTTON_EVENT(PRESSED) | BUTTON_EVENT(RELEASED) | + BUTTON_SHIFT | BUTTON_CTRL | BUTTON_ALT; #ifdef __PDCURSES__ /* * On PDCurses it's crucial NOT to mask for BUTTONX_CLICKED. @@ -2053,7 +2030,7 @@ teco_interface_blocking_getch(void) gint key = wgetch(teco_interface.input_pad); teco_memory_start_limiting(); /* allow asynchronous interruptions on <CTRL/C> */ - teco_interrupted = FALSE; + teco_interrupted = TECO_NORMAL; wtimeout(teco_interface.input_pad, 0); #if defined(CURSES_TTY) || defined(PDCURSES_WINCON) || defined(NCURSES_WIN32) noraw(); /* FIXME: necessary because of NCURSES_WIN32 bug */ @@ -2069,6 +2046,31 @@ teco_interface_blocking_getch(void) return key; } +#ifdef __PDCURSES__ + +/* + * Especially PDCurses/WinGUI likes to report two keypresses, + * e.g. for CTRL+Shift+6 (CTRL+^). + * Make sure we don't filter out AltGr, which may be reported as CTRL+ALT. + */ +static inline gboolean +teco_interface_check_key(gint key) +{ + return (PDC_get_key_modifiers() & + (PDC_KEY_MODIFIER_CONTROL | PDC_KEY_MODIFIER_ALT)) != PDC_KEY_MODIFIER_CONTROL || + TECO_IS_CTL(key); +} + +#else /* __PDCURSES__ */ + +static inline gboolean +teco_interface_check_key(gint key) +{ + return TRUE; +} + +#endif + /** * One iteration of the event loop. * @@ -2099,11 +2101,6 @@ teco_interface_event_loop_iter(void) return; #ifdef KEY_RESIZE case KEY_RESIZE: - /* - * At least on PDCurses/Wincon, the hardware cursor is sometimes - * reactivated. - */ - curs_set(0); teco_interface_resize_all_windows(); break; #endif @@ -2179,21 +2176,9 @@ teco_interface_event_loop_iter(void) * Control keys and keys with printable representation */ default: - if (key > 0xFF) - /* unhandled function key */ - return; - -#ifdef __PDCURSES__ - /* - * Especially PDCurses/WinGUI likes to report two keypresses, - * e.g. for CTRL+Shift+6 (CTRL+^). - * Make sure we don't filter out AltGr, which may be reported as CTRL+ALT. - */ - if ((PDC_get_key_modifiers() & - (PDC_KEY_MODIFIER_CONTROL | PDC_KEY_MODIFIER_ALT)) == PDC_KEY_MODIFIER_CONTROL && - !TECO_IS_CTL(key)) + if (key > 0xFF || !teco_interface_check_key(key)) + /* unhandled function key or bogus key press */ return; -#endif /* * NOTE: There's also wget_wch(), but it requires @@ -2325,6 +2310,8 @@ teco_interface_cleanup(void) if (teco_interface.stdout_orig >= 0) close(teco_interface.stdout_orig); + if (teco_interface.color_table) + g_hash_table_destroy(teco_interface.color_table); if (teco_interface.pair_table) g_hash_table_destroy(teco_interface.pair_table); diff --git a/src/interface-gtk/gtk-info-popup.c b/src/interface-gtk/gtk-info-popup.c index 769f772..f2c8dc8 100644 --- a/src/interface-gtk/gtk-info-popup.c +++ b/src/interface-gtk/gtk-info-popup.c @@ -30,6 +30,8 @@ #include "gtk-label.h" #include "gtk-info-popup.h" +#define TECO_UNNAMED_FILE "(Unnamed)" + /* * FIXME: This is redundant with curses-info-popup.c. */ @@ -336,7 +338,7 @@ teco_gtk_info_popup_idle_add(TecoGtkInfoPopup *self, teco_popup_entry_type_t typ } } - GtkWidget *label = teco_gtk_label_new(name, len); + GtkWidget *label = teco_gtk_label_new(name, len, TECO_UNNAMED_FILE); /* * Gtk v3.20 changed the CSS element names. * Adding a style class eases writing a portable fallback.css. diff --git a/src/interface-gtk/gtk-label.c b/src/interface-gtk/gtk-label.c index 5052cdc..6e05045 100644 --- a/src/interface-gtk/gtk-label.c +++ b/src/interface-gtk/gtk-label.c @@ -32,8 +32,6 @@ #include "gtk-label.h" -#define TECO_UNNAMED_FILE "(Unnamed)" - #define GDK_TO_PANGO_COLOR(X) ((guint16)((X) * G_MAXUINT16)) struct _TecoGtkLabel { @@ -42,8 +40,10 @@ struct _TecoGtkLabel { PangoColor fg, bg; guint16 fg_alpha, bg_alpha; - /** text backing the label or empty string for "(Unnamed)" buffer */ + /** text backing the label or empty string for fallback */ teco_string_t string; + /** fallback string to render if `string` is empty or NULL */ + const gchar *fallback; }; G_DEFINE_TYPE(TecoGtkLabel, teco_gtk_label, GTK_TYPE_LABEL) @@ -125,11 +125,21 @@ teco_gtk_label_class_init(TecoGtkLabelClass *klass) static void teco_gtk_label_init(TecoGtkLabel *self) {} +/** + * Create new TECO label widget. + * + * @param str String to render (can be NULL) + * @param len Length of str or negative value for null-terminated strings. + * @param fallback Null-terminated fallback string to render + * instead of empty strings. + * Must be a string constant with global lifetime or NULL. + */ GtkWidget * -teco_gtk_label_new(const gchar *str, gssize len) +teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback) { TecoGtkLabel *widget = TECO_GTK_LABEL(g_object_new(TECO_TYPE_GTK_LABEL, NULL)); + widget->fallback = fallback; teco_gtk_label_set_text(widget, str, len); return GTK_WIDGET(widget); @@ -255,8 +265,8 @@ teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len) teco_string_init(&self->string, str, len < 0 ? strlen(str) : len); teco_string_t string = self->string; - if (!string.len) { - string.data = TECO_UNNAMED_FILE; + if (!string.len && self->fallback) { + string.data = (gchar *)self->fallback; string.len = strlen(string.data); } diff --git a/src/interface-gtk/gtk-label.h b/src/interface-gtk/gtk-label.h index ad39c6e..a84608a 100644 --- a/src/interface-gtk/gtk-label.h +++ b/src/interface-gtk/gtk-label.h @@ -24,7 +24,7 @@ #define TECO_TYPE_GTK_LABEL teco_gtk_label_get_type() G_DECLARE_FINAL_TYPE(TecoGtkLabel, teco_gtk_label, TECO, GTK_LABEL, GtkLabel) -GtkWidget *teco_gtk_label_new(const gchar *str, gssize len); +GtkWidget *teco_gtk_label_new(const gchar *str, gssize len, const gchar *fallback); void teco_gtk_label_set_text(TecoGtkLabel *self, const gchar *str, gssize len); teco_string_t teco_gtk_label_get_text(TecoGtkLabel *self); diff --git a/src/interface-gtk/interface.c b/src/interface-gtk/interface.c index 0e1507f..9ef86a1 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -22,6 +22,7 @@ #include <stdlib.h> #include <string.h> #include <signal.h> +#include <unistd.h> #include <glib.h> #include <glib/gprintf.h> @@ -77,7 +78,7 @@ static void teco_interface_popup_clicked_cb(GtkWidget *popup, gchar *str, gulong 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; +static gboolean teco_interface_termination_handler(gpointer user_data) G_GNUC_UNUSED; static gchar teco_interface_get_ansi_key(GdkEventKey *event); #define TECO_UNNAMED_FILE "(Unnamed)" @@ -132,10 +133,10 @@ static struct { } teco_interface; void -teco_interface_init(void) +teco_interface_init(gint argc, gchar **argv) { #ifdef G_OS_UNIX - if (teco_interface.detach) { + if (teco_interface.detach && !g_getenv("__SCITECO_DETACHED")) { /* * NOTE: There is also daemon() on BSD/Linux, * but the following should be more portable. @@ -148,9 +149,29 @@ teco_interface_init(void) setsid(); - g_freopen("/dev/null", "r", stdin); - g_freopen("/dev/null", "a+", stdout); - g_freopen("/dev/null", "a+", stderr); + if (isatty(0)) { + G_GNUC_UNUSED FILE *stdin_new = g_freopen("/dev/null", "r", stdin); + g_assert(stdin_new != NULL); + } + if (isatty(1)) { + G_GNUC_UNUSED FILE *stdout_new = g_freopen("/dev/null", "a+", stdout); + g_assert(stdout_new != NULL); + } + if (isatty(2)) { + G_GNUC_UNUSED FILE *stderr_new = g_freopen("/dev/null", "a+", stderr); + g_assert(stderr_new != NULL); + } + + /* + * gtk_get_option_group() already initialized GTK and even though the + * display is not yet opened, it's unsafe to continue. + * Instead, we re-exec already in the child process. + * We cannot easily remove --detach from argv, but still guard against + * recursive forks by using an environment variable. + */ + g_setenv("__SCITECO_DETACHED", "1", TRUE); + execv(argv[0], argv); + g_assert_not_reached(); } #endif @@ -202,7 +223,7 @@ teco_interface_init(void) */ teco_interface.info_bar_widget = gtk_header_bar_new(); gtk_widget_set_name(teco_interface.info_bar_widget, "sciteco-info-bar"); - teco_interface.info_name_widget = teco_gtk_label_new("", 0); + teco_interface.info_name_widget = teco_gtk_label_new(NULL, 0, NULL); gtk_widget_set_valign(teco_interface.info_name_widget, GTK_ALIGN_CENTER); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.info_name_widget), @@ -288,7 +309,7 @@ teco_interface_init(void) gtk_widget_set_name(teco_interface.message_bar_widget, "sciteco-message-bar"); GtkWidget *message_bar_content = gtk_info_bar_get_content_area(GTK_INFO_BAR(teco_interface.message_bar_widget)); - teco_interface.message_widget = teco_gtk_label_new(NULL, 0); + teco_interface.message_widget = teco_gtk_label_new(NULL, 0, NULL); /* eases writing portable fallback.css that avoids CSS element names */ gtk_style_context_add_class(gtk_widget_get_style_context(teco_interface.message_widget), "label"); @@ -383,7 +404,7 @@ teco_interface_get_options(void) return group; } -void teco_interface_init_color(guint color, guint32 rgb) {} +void teco_interface_disable_palette(void) {} void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) @@ -430,14 +451,15 @@ teco_interface_getch_commit_cb(GtkIMContext *context, gchar *str, gpointer user_ */ *cp = g_utf8_get_char_validated(str, -1); g_assert(*cp >= 0); - gtk_main_quit(); + + /* we might be invoked outside of a nested event loop */ + if (gtk_main_level() > 1) + gtk_main_quit(); } static gboolean -teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) +teco_interface_getch_process_event(GdkEvent *event, teco_int_t *cp) { - teco_int_t *cp = user_data; - g_assert(event->type == GDK_KEY_PRESS); switch (event->key.keyval) { @@ -455,19 +477,34 @@ teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_ *cp = TECO_CTL_KEY(g_ascii_toupper(*cp)); switch (*cp) { case TECO_CTL_KEY('C'): - teco_interrupted = TRUE; + teco_interrupted = TECO_INTERRUPTED; /* fall through */ case TECO_CTL_KEY('D'): + /* emulates EOF on stdin */ *cp = -1; } break; } + /* + * NOTE: The teco_interface_getch_commit_cb() could be called immediately + * without returning to the event loop. + */ gtk_im_context_filter_keypress(teco_interface.input_method, &event->key); - return TRUE; + return *cp >= 0; } - gtk_main_quit(); + return TRUE; +} + +static gboolean +teco_interface_getch_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + teco_int_t *cp = user_data; + + if (teco_interface_getch_process_event(event, cp)) + gtk_main_quit(); + return TRUE; } @@ -492,6 +529,22 @@ teco_interface_getch(gboolean widechar) G_CALLBACK(teco_interface_getch_commit_cb), &cp); /* + * The original teco_interface_input_cb() could have already enqueued events + * (also between ^T calls). + * This must be done after registering teco_interface_getch_commit_cb() already. + * + * NOTE: Already enqueued mouse events will currently be discarded silently. + */ + for (;;) { + g_autoptr(GdkEvent) event = g_queue_pop_head(teco_interface.event_queue); + if (!event) + break; + if (event->type == GDK_KEY_PRESS && + teco_interface_getch_process_event(event, &cp)) + goto cleanup; + } + + /* * Highlights the first character in the label. * This mimics what the Curses UI does. * Is there a better way to signal that we expect input? @@ -505,6 +558,7 @@ teco_interface_getch(gboolean widechar) gdk_window_freeze_updates(top_window); +cleanup: g_signal_handler_disconnect(teco_interface.input_method, commit_handler); g_signal_handlers_unblock_by_func(teco_interface.input_method, teco_interface_cmdline_commit_cb, NULL); g_signal_handler_disconnect(teco_interface.window, key_handler); @@ -769,7 +823,7 @@ teco_interface_is_interrupted(void) { if (!gtk_main_level()) /* batch mode */ - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; /* * By polling only every TECO_POLL_INTERVAL microseconds @@ -779,11 +833,11 @@ teco_interface_is_interrupted(void) guint64 now_ts = g_get_monotonic_time(); if (G_LIKELY(last_poll_ts+TECO_POLL_INTERVAL > now_ts)) - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; last_poll_ts = now_ts; gtk_main_iteration_do(FALSE); - return teco_interrupted != FALSE; + return teco_interrupted == TECO_INTERRUPTED; } void @@ -1244,13 +1298,12 @@ teco_interface_event_loop(GError **error) #endif /* - * SIGTERM emulates the "Close" key just like when - * closing the window if supported by this version of glib. - * Note that this replaces SciTECO's default SIGTERM handler - * so it will additionally raise(SIGINT). + * SIGTERM and SIGHUP terminate the program, but will + * dump recovery files first (if enabled). */ #ifdef G_OS_UNIX - g_unix_signal_add(SIGTERM, teco_interface_sigterm_handler, NULL); + g_unix_signal_add(SIGTERM, teco_interface_termination_handler, NULL); + g_unix_signal_add(SIGHUP, teco_interface_termination_handler, NULL); #endif /* the interval might have been changed in the profile */ @@ -1374,7 +1427,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) * If the execution thread is currently blocking, * the key is delivered like an ordinary key press. */ - teco_interrupted = TRUE; + teco_interrupted = TECO_INTERRUPTED; else g_queue_push_tail(teco_interface.event_queue, gdk_event_copy(event)); @@ -1415,7 +1468,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) const teco_view_t *last_view = teco_interface_current_view; sptr_t last_vpos = teco_interface_ssm(SCI_GETFIRSTVISIBLELINE, 0, 0); - teco_interrupted = FALSE; + teco_interrupted = TECO_NORMAL; switch (event->type) { case GDK_KEY_PRESS: teco_interface_handle_key_press(&event->key, &error); @@ -1432,7 +1485,7 @@ teco_interface_input_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data) default: g_assert_not_reached(); } - teco_interrupted = FALSE; + teco_interrupted = TECO_NORMAL; teco_interface_update(teco_interface_current_view != last_view); /* always expand folds, even after mouse clicks */ @@ -1542,16 +1595,23 @@ teco_interface_window_delete_cb(GtkWidget *widget, GdkEventAny *event, gpointer } static gboolean -teco_interface_sigterm_handler(gpointer user_data) +teco_interface_termination_handler(gpointer user_data) { + teco_interface_msg(TECO_MSG_WARNING, "Received termination signal - dumping recovery files"); + teco_interrupted = TECO_TERMINATED; + /* - * Similar to window deletion - emulate "close" key press. + * This may be an emergency shutdown or another kind of + * termination that we cannot ignore, so better dump + * recovery files before terminating. */ - GtkWidget *widget = teco_interface.window; + if (teco_ring_recovery_interval != 0) + teco_ring_dump_recovery(); - g_autoptr(GdkEvent) close_event = gdk_event_new(GDK_KEY_PRESS); - close_event->key.window = gtk_widget_get_parent_window(widget); - close_event->key.keyval = GDK_KEY_Close; + /* + * Otherwise we terminate and clean up as if by `-EX`. + */ + gtk_main_quit(); - return teco_interface_input_cb(widget, close_event, NULL); + return G_SOURCE_REMOVE; } diff --git a/src/interface.h b/src/interface.h index f196a83..ace0e3d 100644 --- a/src/interface.h +++ b/src/interface.h @@ -51,19 +51,20 @@ extern teco_view_t *teco_interface_current_view; /** @pure */ -void teco_interface_init(void); +void teco_interface_init(gint argc, gchar **argv); /** @pure */ GOptionGroup *teco_interface_get_options(void); /** @pure makes sense only on Curses */ -void teco_interface_init_color(guint color, guint32 rgb); +void teco_interface_disable_palette(void); typedef enum { - TECO_MSG_USER, + TECO_MSG_USER = 0, TECO_MSG_INFO, TECO_MSG_WARNING, - TECO_MSG_ERROR + TECO_MSG_ERROR, + TECO_MSG_MAX = TECO_MSG_ERROR } teco_msg_t; extern teco_msg_t teco_interface_msg_level; @@ -54,7 +54,7 @@ /* * Define this to pause the program at the beginning - * of main() (Windows only). + * of main(). * This is a useful hack on Windows, where gdbserver * sometimes refuses to start SciTECO but attaches * to a running process just fine. @@ -66,12 +66,15 @@ teco_int_t teco_ed = TECO_ED_AUTOEOL; /** - * Whether there was an asyncronous interruption (usually after pressing CTRL+C). + * Whether there was an asynchronous interruption (usually after + * pressing CTRL+C) or termination (SIGTERM). * However you should always use teco_interface_is_interrupted(), * to check for interruptions because of its side effects. * This variable is safe to set to TRUE from signal handlers and threads. + * + * This is a teco_interrupted_t. */ -volatile sig_atomic_t teco_interrupted = FALSE; +volatile sig_atomic_t teco_interrupted = TECO_NORMAL; /* * FIXME: Move this into file-utils.c? @@ -346,7 +349,7 @@ teco_initialize_environment(void) static void teco_sigint_handler(int signal) { - teco_interrupted = TRUE; + teco_interrupted = TECO_INTERRUPTED; } #ifdef G_OS_WIN32 @@ -382,12 +385,24 @@ main(int argc, char **argv) #endif #ifdef DEBUG_PAUSE - /* Windows debugging hack (see above) */ - system("pause"); + getchar(); #endif + /* + * FIXME: Gracefully handle SIGTERM. + * This however would require polling for the flag in Curses + * getch() loops. + * GTK already catches SIGTERM. + */ +#ifdef HAVE_SIGACTION + static const struct sigaction sigint = { + .sa_handler = teco_sigint_handler, + .sa_flags = 0 /* don't auto-restart syscalls */ + }; + sigaction(SIGINT, &sigint, NULL); +#else signal(SIGINT, teco_sigint_handler); - signal(SIGTERM, teco_sigint_handler); +#endif /* * Important for Unicode handling in curses and glib. @@ -445,7 +460,7 @@ main(int argc, char **argv) */ teco_qreg_table_init(&teco_qreg_table_globals, TRUE); - teco_interface_init(); + teco_interface_init(argc, argv); /* * QRegister view must be initialized only now @@ -1694,11 +1694,7 @@ teco_machine_qregspec_auto_complete(teco_machine_qregspec_t *ctx, teco_string_t /* two-letter Q-Reg */ restrict_len = 2; - /* - * FIXME: This is not quite right as it will propose even - * lower case single or two-letter Q-Register names. - */ - return teco_rb3str_auto_complete(&ctx->result_table->tree, !restrict_len, + return teco_rb3str_auto_complete(&ctx->result_table->tree, TRUE, ctx->name.data, ctx->name.len, restrict_len, insert) && ctx->nesting == 1; } @@ -143,7 +143,12 @@ teco_buffer_save(teco_buffer_t *ctx, const gchar *filename, GError **error) static inline void teco_buffer_free(teco_buffer_t *ctx) { - if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP) { + /* + * During graceful, but unexpected shutdowns (SIGTERM etc.), + * we must preserve the recovery files. + */ + if (ctx->state > TECO_BUFFER_DIRTY_NO_DUMP && + teco_interrupted != TECO_TERMINATED) { g_autofree gchar *filename_recovery = teco_buffer_get_recovery(ctx); g_unlink(filename_recovery); } @@ -312,12 +317,13 @@ teco_ring_save_all_dirty_buffers(GError **error) * Recovery creation interval in seconds or 0 if disabled. * It's not currently enforced in batch mode. */ -guint teco_ring_recovery_interval = 5*60; +guint teco_ring_recovery_interval = 2*60; /** * Create recovery files for all dirty buffers. * - * Should be called by the interface every teco_ring_recovery_interval seconds. + * Should be called by the interface every teco_ring_recovery_interval seconds + * or before graceful terminations (SIGTERM etc.). * This does not generate or expect undo tokens, so it can be called * even when idlying. */ @@ -731,13 +737,18 @@ teco_state_read_file_done(teco_machine_main_t *ctx, teco_string_t str, GError ** return &teco_state_start; sptr_t pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].from) = teco_interface_bytes2glyphs(pos); g_autofree gchar *filename = teco_file_expand_path(str.data); /* FIXME: Add wrapper to interface.h? */ if (!teco_view_load(teco_interface_current_view, filename, FALSE, error)) return NULL; - if (teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0) != pos) { + pos = teco_interface_ssm(SCI_GETCURRENTPOS, 0, 0); + teco_undo_int(teco_ranges[0].to) = teco_interface_bytes2glyphs(pos); + teco_undo_guint(teco_ranges_count) = 1; + + if (teco_ranges[0].from != teco_ranges[0].to) { teco_ring_dirtify(); if (teco_current_doc_must_undo()) diff --git a/src/sciteco.h b/src/sciteco.h index 16dba69..88078c2 100644 --- a/src/sciteco.h +++ b/src/sciteco.h @@ -109,6 +109,12 @@ teco_default_codepage(void) return teco_ed & TECO_ED_DEFAULT_ANSI ? SC_CHARSET_ANSI : SC_CP_UTF8; } +typedef enum { + TECO_NORMAL = 0, + TECO_INTERRUPTED, + TECO_TERMINATED +} teco_interrupted_t; + /* in main.c */ extern volatile sig_atomic_t teco_interrupted; diff --git a/src/spawn.c b/src/spawn.c index 61718fd..716bafa 100644 --- a/src/spawn.c +++ b/src/spawn.c @@ -869,7 +869,7 @@ teco_spawn_idle_cb(gpointer user_data) { if (G_LIKELY(!teco_interface_is_interrupted())) return G_SOURCE_CONTINUE; - teco_interrupted = FALSE; + teco_interrupted = TECO_NORMAL; /* * The first CTRL+C will try to gracefully terminate the process. diff --git a/src/stdio-commands.c b/src/stdio-commands.c index abb6566..c2a1c16 100644 --- a/src/stdio-commands.c +++ b/src/stdio-commands.c @@ -255,17 +255,42 @@ teco_state_print_string_initial(teco_machine_main_t *ctx, GError **error) static teco_state_t * teco_state_print_string_done(teco_machine_main_t *ctx, teco_string_t str, GError **error) { - teco_interface_msg_literal(TECO_MSG_USER, str.data, str.len); + teco_int_t type; + + if (ctx->flags.mode > TECO_MODE_NORMAL) + return &teco_state_start; + + if (!teco_expressions_pop_num_calc(&type, TECO_MSG_USER, error)) + return NULL; + if (type < TECO_MSG_USER || type > TECO_MSG_MAX) { + g_set_error_literal(error, TECO_ERROR, TECO_ERROR_FAILED, + "Invalid message level specified for <^A>"); + return NULL; + } + + teco_interface_msg_literal(type, str.data, str.len); return &teco_state_start; } /*$ "^A" ":^A" print "print string" - * ^A<string>^A -- Print string as message - * @^A/string/ - * :^A<string>^A + * [type]^A<string>^A -- Print string as message + * [type]@^A/string/ + * [type]:^A<string>^A * - * Print <string> as a message, i.e. in the message line - * in interactive mode and if possible on the terminal (stdout) as well. + * Print <string> as a message with severity <type>, i.e. in the + * message line in interactive mode and if possible on the terminal + * as well. + * The following message levels are supported: + * .IP 0: 3 + * User-level message: + * They are written without any prefixes directly to stdout. + * This is the default if <type> is omitted. + * .IP 1: + * Info-level message + * .IP 2: + * Warnings + * .IP 3: + * Errors * * \fB^A\fP differs from all other commands in the way <string> * is terminated. diff --git a/src/string-utils.c b/src/string-utils.c index e9dd148..d98b6b0 100644 --- a/src/string-utils.c +++ b/src/string-utils.c @@ -87,8 +87,10 @@ teco_string_get_coord(const gchar *str, gsize off, guint *pos, guint *line, guin } /** - * Get the length of the prefix common to two strings. - * Works with UTF-8 and single-byte encodings. + * Get the length of the prefix common to two UTF-8 strings. + * + * The UTF-8 strings must be validated, which should be the case + * for help labels and short Q-Register names. * * @param a Left string. * @param b Right string. @@ -103,8 +105,8 @@ teco_string_diff(teco_string_t a, const gchar *b, gsize b_len) gsize len = 0; while (len < a.len && len < b_len && - a.data[len] == b[len]) - len++; + g_utf8_get_char(a.data+len) == g_utf8_get_char(b+len)) + len = g_utf8_next_char(b+len) - b; return len; } @@ -112,9 +112,9 @@ teco_view_setup(teco_view_t *ctx) teco_view_ssm(ctx, SCI_SETCARETFORE, 0xFFFFFF, 0); teco_view_ssm(ctx, SCI_SETSELFORE, TRUE, 0x000000); - teco_view_ssm(ctx, SCI_SETSELBACK, TRUE, 0xFFFFFF); + teco_view_ssm(ctx, SCI_SETSELBACK, TRUE, 0xC0C0C0); - teco_view_ssm(ctx, SCI_STYLESETFORE, STYLE_DEFAULT, 0xFFFFFF); + teco_view_ssm(ctx, SCI_STYLESETFORE, STYLE_DEFAULT, 0xC0C0C0); teco_view_ssm(ctx, SCI_STYLESETBACK, STYLE_DEFAULT, 0x000000); teco_view_ssm(ctx, SCI_STYLESETFONT, STYLE_DEFAULT, (sptr_t)"Monospace"); teco_view_ssm(ctx, SCI_STYLECLEARALL, 0, 0); @@ -234,8 +234,8 @@ teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, */ guint cp = teco_view_get_codepage(ctx); if (cp == SC_CP_UTF8) - teco_interface_ssm(SCI_RELEASELINECHARACTERINDEX, - SC_LINECHARACTERINDEX_UTF32, 0); + teco_view_ssm(ctx, SCI_RELEASELINECHARACTERINDEX, + SC_LINECHARACTERINDEX_UTF32, 0); teco_view_ssm(ctx, SCI_BEGINUNDOACTION, 0, 0); if (clear) { @@ -314,8 +314,8 @@ cleanup: teco_view_ssm(ctx, SCI_ENDUNDOACTION, 0, 0); if (cp == SC_CP_UTF8) - teco_interface_ssm(SCI_ALLOCATELINECHARACTERINDEX, - SC_LINECHARACTERINDEX_UTF32, 0); + teco_view_ssm(ctx, SCI_ALLOCATELINECHARACTERINDEX, + SC_LINECHARACTERINDEX_UTF32, 0); return ret; } diff --git a/tests/atlocal.in b/tests/atlocal.in index b2ceda1..81dc1fb 100644 --- a/tests/atlocal.in +++ b/tests/atlocal.in @@ -40,4 +40,4 @@ esac # Default stack size on Linux (8M). # Some platforms allow very large stack sizes, making it hard to test # against potential stack overflows. -ulimit -s 8192 +ulimit -s 8192 || true diff --git a/tests/testsuite.at b/tests/testsuite.at index f34eee3..74fab24 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -221,6 +221,13 @@ AT_DATA([expout], [[TEST Line 2 ]]) TE_CHECK([[@^A/TEST^JLine 2^J/]], 0, expout, ignore) +# "Info" messages would be surpressed by --quiet. +AT_CHECK([[$SCITECO -e '1@^A/TEST/']], 0, stdout, ignore) +AT_FAIL_IF([! $GREP "^Info:" stdout]) +TE_CHECK([[2@^A/TEST/]], 0, ignore, stderr) +AT_FAIL_IF([! $GREP "^Warning:" stderr]) +TE_CHECK([[3@^A/TEST/]], 0, ignore, stderr) +AT_FAIL_IF([! $GREP "^Error:" stderr]) AT_CLEANUP AT_SETUP([Type out buffer contents]) @@ -296,6 +303,13 @@ TE_CHECK([[@I/ABCDEF/J @FS/ABC/1234/ ^S+4"N(0/0)']], 0, ignore, ignore) TE_CHECK([[@^Ua/XYZ/ Ga ^S+3"N(0/0)']], 0, ignore, ignore) # NOTE: EN currently inserts another trailing linefeed. TE_CHECK([[@EN/*/XYZ/ ^S+4"N(0/0)']], 0, ignore, ignore) +AT_DATA([test.txt], [[0123456789 +]]) +TE_CHECK([[@ER"test.txt" ^S+11"N(0/0)']], 0, ignore, ignore) +AT_CLEANUP + +AT_SETUP([Macro calls]) +TE_CHECK([[@^Ua{-$$} Ma+1"N(0/0)']], 0, ignore, ignore) AT_CLEANUP AT_SETUP([Editing local registers in macro calls]) @@ -439,6 +453,11 @@ TE_CHECK_CMDLINE([[@^Um{^C^C} Mm]], 0, ignore, stderr) AT_FAIL_IF([! $GREP "^Error:" stderr]) AT_CLEANUP +AT_SETUP([Replace non-Unicode command-line]) +TE_CHECK_CMDLINE([[{0EE 255@I//}]], 0, ignore, stderr) +AT_FAIL_IF([! $GREP "^Error:" stderr]) +AT_CLEANUP + AT_BANNER([Standard library]) AT_SETUP([Option parser]) @@ -659,6 +678,12 @@ TE_CHECK([[@^Um{U.a Q.a-100000"<%.aMm'} 0Mm]], 0, ignore, ignore) AT_XFAIL_IF(true) AT_CLEANUP +AT_SETUP([Backtracking in patterns]) +# ^ES should be greedy and posessive +TE_CHECK([[@I/ /J :@S/^ES^X/"S(0/0)']], 0, ignore, ignore) +AT_XFAIL_IF(true) +AT_CLEANUP + AT_SETUP([Rub out from empty string argument]) # Should rub out the modifiers as well. # Will currently fail because it tries to execute `:@{`. diff --git a/www/build.tes b/www/build.tes index 848a63c..89ac70a 100755 --- a/www/build.tes +++ b/www/build.tes @@ -18,6 +18,7 @@ <html> <head> <title>SciTECO - <Website> Q[title]</title> + <link rel="canonical" href="https://sciteco.fmsbw.de/Q[file]"> <link rel="icon" type="image/x-icon" href="https://sciteco.fmsbw.de/graphics/sciteco.ico"> <meta name="description" content="Advanced TECO dialect and interactive screen editor based on Scintilla"> <style> @@ -65,7 +66,7 @@ </html> } -EBindex.html HK +[file] EBindex.html HK [title]Home M[header] EClowdown -thtml --html-no-skiphtml --html-no-escapehtml ../NEWS.md I<hr> @@ -73,7 +74,7 @@ EBindex.html HK M[footer] EW -EBscreenshots.html HK +[file]screenshots.html EBN[file] HK [title]Screenshots M[header] EClowdown -thtml --html-no-skiphtml --html-no-escapehtml screenshots.md M[footer] @@ -87,44 +88,44 @@ EW * FIXME: Support out-of-tree builds. * Perhaps pass in the biuld directory. *! -EBQ[builddir]/doc/sciteco.1.html +[file]sciteco.1.html EBN[builddir]/doc/N[file] S<body>S<h1 L 0,.K [title]sciteco(1) M[header] G[grohtml-header] FD<hr>S</body> .,ZK M[footer] -EWsciteco.1.html +EWQ[file] -EBQ[builddir]/doc/sciteco.7.html +[file]sciteco.7.html EBN[builddir]/doc/N[file] S<body>S<h1 L 0,.K [title]sciteco(7) M[header] G[grohtml-header] FD<hr>S</body> .,ZK M[footer] -EWsciteco.7.html +EWQ[file] !* * These grohtml-generated documents are not in the header bar, * but still postprocessed for consinstency. *! -EBQ[builddir]/doc/grosciteco.tes.1.html +[file]grosciteco.tes.1.html EBN[builddir]/doc/N[file] S<body>S<h1 L 0,.K [title]grosciteco.tes(1) M[header] G[grohtml-header] FD<hr>S</body> .,ZK M[footer] -EWgrosciteco.tes.1.html +EWQ[file] -EBQ[builddir]/doc/tedoc.tes.1.html +[file]tedoc.tes.1.html EBN[builddir]/doc/N[file] S<body>S<h1 L 0,.K [title]tedoc.tes(1) M[header] G[grohtml-header] FD<hr>S</body> .,ZK M[footer] -EWtedoc.tes.1.html +EWQ[file] -EBQ[builddir]/doc/tutorial.html +[file]tutorial.html EBN[builddir]/doc/N[file] S<body>S<h1 L 0,.K [title]Tutorial M[header] G[grohtml-header] FD<hr>S</body> .,ZK M[footer] -EWtutorial.html +EWQ[file] EX diff --git a/www/screenshots.md b/www/screenshots.md index df8925a..471374e 100644 --- a/www/screenshots.md +++ b/www/screenshots.md @@ -1,5 +1,9 @@ # Screenshots +## v2.5.1 + + + ## v2.4.0  |
