diff options
| -rw-r--r-- | .gitmodules | 2 | ||||
| -rw-r--r-- | ChangeLog | 51 | ||||
| -rw-r--r-- | INSTALL | 2 | ||||
| -rw-r--r-- | NEWS | 16 | ||||
| -rw-r--r-- | TODO | 167 | ||||
| -rw-r--r-- | configure.ac | 17 | ||||
| -rw-r--r-- | contrib/dlmalloc/Makefile.am | 7 | ||||
| m--------- | contrib/scinterm | 0 | ||||
| m--------- | contrib/scintilla | 0 | ||||
| -rw-r--r-- | debian/changelog | 6 | ||||
| -rw-r--r-- | doc/sciteco.7.template | 14 | ||||
| -rw-r--r-- | freebsd/Makefile | 8 | ||||
| -rw-r--r-- | freebsd/distinfo | 6 | ||||
| -rw-r--r-- | freebsd/files/opener.check-recovery.patch | 30 | ||||
| -rw-r--r-- | freebsd/files/sbrk.patch | 76 | ||||
| -rw-r--r-- | lib/colors/contrast.tes | 2 | ||||
| -rw-r--r-- | lib/colors/solarized.tes | 53 | ||||
| -rw-r--r-- | lib/colors/terminal.tes | 3 | ||||
| -rw-r--r-- | lib/opener.tes | 9 | ||||
| -rw-r--r-- | lib/tank.tes | 4 | ||||
| -rw-r--r-- | src/core-commands.c | 150 | ||||
| -rw-r--r-- | src/interface-curses/interface.c | 417 | ||||
| -rw-r--r-- | src/interface-gtk/interface.c | 3 | ||||
| -rw-r--r-- | src/interface.h | 10 | ||||
| -rw-r--r-- | src/main.c | 14 | ||||
| -rw-r--r-- | src/search.c | 291 | ||||
| -rw-r--r-- | src/search.h | 2 | ||||
| -rw-r--r-- | src/stdio-commands.c | 37 | ||||
| -rw-r--r-- | src/view.c | 16 | ||||
| -rw-r--r-- | tests/testsuite.at | 24 |
30 files changed, 936 insertions, 501 deletions
diff --git a/.gitmodules b/.gitmodules index 841ce77..af9fd68 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,7 +4,7 @@ ignore = untracked [submodule "scinterm"] path = contrib/scinterm - url = https://github.com/orbitalquark/scinterm.git + url = https://github.com/rhaberkorn/scinterm.git [submodule "lexilla"] path = contrib/lexilla url = https://github.com/ScintillaOrg/lexilla.git @@ -6,6 +6,57 @@ 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) ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -48,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.5 or later): + * Scinterm (v6.0 or later): http://foicica.com/scinterm/ * Lexilla (v5.0.0 or later, optional): https://www.scintilla.org/Lexilla.html @@ -2,17 +2,5 @@ News ==== <span class="nf nf-md-new_box"></span> -There is now an official [Haiku port](https://depot.haiku-os.org/#!/pkg/sciteco_curses/) -for the ncurses version, so Haiku users can install SciTECO via the Depot. - -<span class="nf nf-md-new_box"></span> -SciTECO [v2.5.1](https://sciteco.fmsbw.de/downloads/v2.5.1/) has been released. -This is a hotfix release to v2.5.0, which first and foremost fixes the -Windows PDCurses builds. - -<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. @@ -1,8 +1,7 @@ Tasks: * Have a look at TECO-86. * VEDIT and PMATE for MS-DOS - * Scintilla: upstream 2 patches - * HaikuPorts + * Make sure that fmsbw.de is indexed by Google Known Bugs: * OBS GTK builds sometimes fail: "cannot open display" @@ -10,24 +9,16 @@ 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 @@ -36,16 +27,18 @@ Known Bugs: 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. + * 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'$. @@ -56,17 +49,21 @@ Known Bugs: want to refer directory C:\mingw64 instead). * 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. @@ -77,12 +74,23 @@ Known Bugs: and b) the file mode and ownership of re-created files can be preserved. We should fall back silently to an (inefficient) memory copy or temporary file strategy if this is detected. - * Crashes on large files: S^EM^X$ (regexp: (.)+) + * All backward searches from the end of excessively large files can be very + slow, especially in UTF mode, since you are always producing + all matches over the entire document. + Perhaps scan in 4kb blocks from dot upwards, but with partial matches. + When getting partial matches, the match falls on a block boundary and + we can extended the scanned area downwards until dot. + This currently doesn't work with glib's regexp (PCRE) since + g_match_info_fetch_pos() handles partial matches like errors. + Here's an upstream merge request to fix that: + https://gitlab.gnome.org/GNOME/glib/-/merge_requests/5199 + * Crashes on large files: S^EM^X$ (regexp: (?:.)+) Happens because the Glib regex engine is based on a recursive (backtracking) - Perl regex library. + Perl regex library and glib doesn't expose pcre_extra. + We could include `(*LIMIT_RECURSION=d)` in the pattern, though. I can provoke the problem only on Ubuntu 20.04. - This is apparently impossible to fix as long as we do not - have control over the regex engine build. + We can try g_regex_match_all_full() which will use a DFA, but + it doesn't capture subexpressions. We need something based on a non-backtracking Thompson's NFA with Unicode (UTF-8), see https://swtch.com/~rsc/regexp/ Basically only RE2 would check all the boxes. @@ -91,6 +99,14 @@ 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 one of Henry Spencer's engines (hxrex or its + PosgreSQL derivation or the version from Vim) to UTF-8 and add it as a submodule. + * It is still possible to hang searches on huge files since a single match + could still scan too much memory - e.g. try searching for a word that + occurs only at the end of the huge file. + Can probably be avoided by including `(*MATCH_LIMIT=d)` in the pattern. + A new regexp engine should also allow interruptions within a single match, + so we don't have to invent limits like that. * 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 @@ -201,6 +217,31 @@ 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. + * When adding the OBS repo to Ubuntu, Synaptic showed + sciteco-curses:s390x packages on an amd64. + The package could be installed without problems, though. + Probably a Synaptic bug. Features: * Should we support *.sgml files with the HTML lexer? @@ -221,6 +262,8 @@ 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. + * opener.tes: support *not* opening the .teco_session. * Rubout of SCI_GOTOPOS could also restore the vertical scrolling position (SCI_SETFIRSTVISIBLELINE). So e.g. rubbing out ZJ restores the exact view. @@ -298,21 +341,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 @@ -389,7 +422,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. @@ -480,7 +512,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 @@ -593,6 +630,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 @@ -600,9 +638,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, @@ -699,6 +740,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 @@ -748,14 +792,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(), @@ -791,6 +827,27 @@ Features: 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? + * 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). + * New ED option: use fsync() when writing files. + Could be useful on systems that crash frequently. Optimizations: * Use SC_DOCUMENTOPTION_STYLES_NONE in batch mode. @@ -914,6 +971,10 @@ Optimizations: co take care of it. However insertion commands would also have to take care of expanding LF to the buffers EOL sequence. + * Perhaps some operations can be sped up with mmap() instead of loading + files into the heap. glib has an appropriate function with fallback for + platforms without mmap(). + Perhaps file munging and <EI> could make use of it. Documentation: * Doxygen docs could be deployed on Github pages diff --git a/configure.ac b/configure.ac index 673fb33..43b4d1b 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.1], +AC_INIT([SciTECO], [2.5.2], [hackers@fmsbw.de], [sciteco], [https://sciteco.fmsbw.de/]) @@ -18,10 +18,10 @@ AC_CANONICAL_BUILD AC_CANONICAL_HOST AX_CHECK_ENABLE_DEBUG -AM_CONDITIONAL(DEBUG, [test x$ax_enable_debug != xno]) if [[ x$ax_enable_debug = xno ]]; then # glib does not look at NDEBUG AC_DEFINE(G_DISABLE_ASSERT, 1, [Disable g_assert()]) + DLMALLOC_CPPFLAGS="$DLMALLOC_CPPFLAGS -DINSECURE=1" fi # Use the user provided CXXFLAGS for Scintilla as well. @@ -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 execv 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], , [ @@ -224,6 +224,13 @@ case $host in ;; esac +# Optional UNIX libc functions. +# FreeBSD arm64 and riscv are missing sbrk(), which can be used by dlmalloc. +AC_CHECK_FUNCS([sbrk], , [ + DLMALLOC_CPPFLAGS="$DLMALLOC_CPPFLAGS -DHAVE_MORECORE=0" +]) +AC_SUBST(DLMALLOC_CPPFLAGS) + # # Config options # @@ -307,7 +314,7 @@ case $INTERFACE in # Scinterm cares about the correct flags itself. AC_DEFINE(NCURSES_WIDECHAR, 1, [Provide wide-character functions]) - AC_CHECK_FUNCS([tigetstr]) + AC_CHECK_FUNCS([tigetstr use_default_colors]) ;; netbsd-curses) @@ -331,7 +338,7 @@ case $INTERFACE in CXXFLAGS="$CXXFLAGS $CURSES_CFLAGS" LIBS="$LIBS $CURSES_LIBS" - AC_CHECK_FUNCS([tigetstr]) + AC_CHECK_FUNCS([tigetstr use_default_colors]) ;; xcurses) diff --git a/contrib/dlmalloc/Makefile.am b/contrib/dlmalloc/Makefile.am index 223ed13..73232d7 100644 --- a/contrib/dlmalloc/Makefile.am +++ b/contrib/dlmalloc/Makefile.am @@ -1,6 +1,6 @@ # Source: http://gee.cs.oswego.edu/dl/html/malloc.html # -# FIXME: On FreeBSD, we might implement a compatible mremap() based on the BSD mremap() and pass +# FIXME: On NetBSD, we might implement a compatible mremap() based on the NetBSD mremap() and pass # in -DHAVE_MREMAP=1 -DMREMAP=mremap_bsd. We'll have to add a declaration to malloc.c or # use -include mremap_bsd.h in CPPFLAGS. # @@ -8,10 +8,7 @@ # for increased portability. There is also AC_LIBOBJ, but it's usually for defining sources of # replacement libraries. -AM_CPPFLAGS = -DNO_MALLINFO=1 -DNO_MALLOC_STATS=1 -DUSE_LOCKS=1 -DUSE_DL_PREFIX -if !DEBUG -AM_CPPFLAGS += -DINSECURE=1 -endif +AM_CPPFLAGS = @DLMALLOC_CPPFLAGS@ -DNO_MALLINFO=1 -DNO_MALLOC_STATS=1 -DUSE_LOCKS=1 -DUSE_DL_PREFIX # FIXME: This optimization is still broken as of GCC v9.3.0. # This is a known GCC bug, triggered by memset() in calloc(). diff --git a/contrib/scinterm b/contrib/scinterm -Subproject 7f5b0e2bdfb23a2806ed0cf9c6d25c1f37c0392 +Subproject 14c560e63d280f2a8f26b4bbf287d7af679b0a1 diff --git a/contrib/scintilla b/contrib/scintilla -Subproject 748f9cbc4f2a9d7a4216feaa37aee8e834123f0 +Subproject a534721011f35a1a56c2a815f8b2fa31e0a8ebb diff --git a/debian/changelog b/debian/changelog index ddea47c..ab0d425 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +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 diff --git a/doc/sciteco.7.template b/doc/sciteco.7.template index cb718e0..87e8574 100644 --- a/doc/sciteco.7.template +++ b/doc/sciteco.7.template @@ -910,9 +910,17 @@ The \fBSTYLE_CALLTIP\fP (38) style is also accessed by \*(ST to style the \fIpopup area\fP and is initialized to black on white by \*(ST. .LP -On curses user interfaces, only a selection of 16 terminal -colors can be used, although it is possible to request changing -the default color mapping (see \fBEJ\fP command). +On curses user interfaces the Scintilla RGB color values +can refer to the terminal's color palette, initialize new +colors on-demand or refer to the terminal's default foreground +and background colors (see \fBEJ\fP command). +The availability of colors, the size of the terminal's +palette, the availability of arbitrary RGB colors, the +number of concurrent colors on screen and the maximum +number of foreground-background combinations on screen +is subject to terminal-specific limits. +\*(ST supports monochrome terminals by mapping non-black +(non-null Scintilla RGB codes) to reverse video. .LP Scintilla styles will usually be set up in the profile macro or \fBED\fP hook (for syntax highlighting). diff --git a/freebsd/Makefile b/freebsd/Makefile index e18b7bc..fd8e284 100644 --- a/freebsd/Makefile +++ b/freebsd/Makefile @@ -1,5 +1,6 @@ PORTNAME= sciteco -DISTVERSION= 2.5.1 +DISTVERSION= 2.5.2 +PORTREVISION= 1 CATEGORIES= editors textproc devel MASTER_SITES= https://sciteco.fmsbw.de/downloads/v${DISTVERSION}/ \ SOURCEFORGE/${PORTNAME}/v${DISTVERSION}/ @@ -24,12 +25,17 @@ gtk_BUILD_DEPENDS= mcookie:devel/util-linux \ Xvfb:x11-servers/xorg-server@xvfb USES= compiler:c11 gmake gnome groff pkgconfig +# Only required because we patch configure.ac: +USES+= autoreconf USE_GNOME= glib20 GNU_CONFIGURE= yes CONFIGURE_ARGS= CONFIGURE_OUTSOURCE= yes +EXTRA_PATCHES= ${FILESDIR}/opener.check-recovery.patch:-p1 \ + ${FILESDIR}/sbrk.patch:-p1 + MAKEFILE= GNUmakefile TEST_TARGET= check diff --git a/freebsd/distinfo b/freebsd/distinfo index 65d7d37..9396b55 100644 --- a/freebsd/distinfo +++ b/freebsd/distinfo @@ -1,3 +1,3 @@ -TIMESTAMP = 1768082190 -SHA256 (sciteco-2.5.1.tar.gz) = cc99c6855f844f0514f2ed4879bf6a02f11eb489a3b77d88ad7f0bcfe1379fbf -SIZE (sciteco-2.5.1.tar.gz) = 4118025 +TIMESTAMP = 1776638070 +SHA256 (sciteco-2.5.2.tar.gz) = 94149fa3371e3adff723ae933502b5983804c4e06a681c6d00b0af19c439a978 +SIZE (sciteco-2.5.2.tar.gz) = 4113973 diff --git a/freebsd/files/opener.check-recovery.patch b/freebsd/files/opener.check-recovery.patch new file mode 100644 index 0000000..aef3c19 --- /dev/null +++ b/freebsd/files/opener.check-recovery.patch @@ -0,0 +1,30 @@ +From 14b7d1fc5621c3251115ccf577beaabf8ab0eccc Mon Sep 17 00:00:00 2001 +From: Robin Haberkorn <rhaberkorn@fmsbw.de> +Date: Mon, 27 Apr 2026 18:48:54 +0200 +Subject: [PATCH] opener.check-recovery: don't pollute Q-register `_` + +This has been broken since v2.5.2. +--- + lib/opener.tes | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/lib/opener.tes b/lib/opener.tes +index 8bcd896..66a97e7 100644 +--- a/lib/opener.tes ++++ b/lib/opener.tes +@@ -44,10 +44,10 @@ + *! + @[opener.check-recovery]{ + :Q*"= ' +- [* ++ [*[_ + EQ.f G* I# R + <-A"I 1; ' :R;> + I# + 1:EN*Q.f"S 2Detected recovery file "Q.f" ' +- ]* ++ ]_]* + } +-- +2.53.0 + diff --git a/freebsd/files/sbrk.patch b/freebsd/files/sbrk.patch new file mode 100644 index 0000000..66b94aa --- /dev/null +++ b/freebsd/files/sbrk.patch @@ -0,0 +1,76 @@ +From 62177d06bd9d31242e67995d4e33a755a3447ca2 Mon Sep 17 00:00:00 2001 +From: Robin Haberkorn <rhaberkorn@fmsbw.de> +Date: Tue, 19 May 2026 21:21:52 +0400 +Subject: [PATCH] check for sbrk() even on UNIX + +* Turns out that not all UNIXes support sbrk(). + FreeBSD arm64 and riscv ports don't implement sbrk(). + It's also apparently not in POSIX - so other systems might also + be affected. + This needs to be passed on to dlmalloc. +* We now use DLMALLOC_CPPFLAGS instead of conditionals to pass + on flags to dlmalloc. +* Should be backported to the FreeBSD v2.5.2 package + to fix Poudriere fallout. +--- + configure.ac | 9 ++++++++- + contrib/dlmalloc/Makefile.am | 7 ++----- + 2 files changed, 10 insertions(+), 6 deletions(-) + +diff --git a/configure.ac b/configure.ac +index b5cac0b..43b4d1b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -18,10 +18,10 @@ AC_CANONICAL_BUILD + AC_CANONICAL_HOST + + AX_CHECK_ENABLE_DEBUG +-AM_CONDITIONAL(DEBUG, [test x$ax_enable_debug != xno]) + if [[ x$ax_enable_debug = xno ]]; then + # glib does not look at NDEBUG + AC_DEFINE(G_DISABLE_ASSERT, 1, [Disable g_assert()]) ++ DLMALLOC_CPPFLAGS="$DLMALLOC_CPPFLAGS -DINSECURE=1" + fi + + # Use the user provided CXXFLAGS for Scintilla as well. +@@ -224,6 +224,13 @@ case $host in + ;; + esac + ++# Optional UNIX libc functions. ++# FreeBSD arm64 and riscv are missing sbrk(), which can be used by dlmalloc. ++AC_CHECK_FUNCS([sbrk], , [ ++ DLMALLOC_CPPFLAGS="$DLMALLOC_CPPFLAGS -DHAVE_MORECORE=0" ++]) ++AC_SUBST(DLMALLOC_CPPFLAGS) ++ + # + # Config options + # +diff --git a/contrib/dlmalloc/Makefile.am b/contrib/dlmalloc/Makefile.am +index 223ed13..73232d7 100644 +--- a/contrib/dlmalloc/Makefile.am ++++ b/contrib/dlmalloc/Makefile.am +@@ -1,6 +1,6 @@ + # Source: http://gee.cs.oswego.edu/dl/html/malloc.html + # +-# FIXME: On FreeBSD, we might implement a compatible mremap() based on the BSD mremap() and pass ++# FIXME: On NetBSD, we might implement a compatible mremap() based on the NetBSD mremap() and pass + # in -DHAVE_MREMAP=1 -DMREMAP=mremap_bsd. We'll have to add a declaration to malloc.c or + # use -include mremap_bsd.h in CPPFLAGS. + # +@@ -8,10 +8,7 @@ + # for increased portability. There is also AC_LIBOBJ, but it's usually for defining sources of + # replacement libraries. + +-AM_CPPFLAGS = -DNO_MALLINFO=1 -DNO_MALLOC_STATS=1 -DUSE_LOCKS=1 -DUSE_DL_PREFIX +-if !DEBUG +-AM_CPPFLAGS += -DINSECURE=1 +-endif ++AM_CPPFLAGS = @DLMALLOC_CPPFLAGS@ -DNO_MALLINFO=1 -DNO_MALLOC_STATS=1 -DUSE_LOCKS=1 -DUSE_DL_PREFIX + + # FIXME: This optimization is still broken as of GCC v9.3.0. + # This is a known GCC bug, triggered by memset() in calloc(). +-- +2.53.0 + diff --git a/lib/colors/contrast.tes b/lib/colors/contrast.tes index 256faad..c75ec9c 100644 --- a/lib/colors/contrast.tes +++ b/lib/colors/contrast.tes @@ -3,6 +3,8 @@ * This only highlights strings and comments (if supported by the terminal). * This is also the recommended color scheme on monochrome displays. *! +-1,-1,7EJ !* no default colors *! + [color.default] 0,Q[color.black],Q[color.white] [[color.default]][color.linenumber] Q[color.black]U[color.caretline] diff --git a/lib/colors/solarized.tes b/lib/colors/solarized.tes index 11567d9..aeb9cb9 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 -1,-1,7EJ !* no palette & no default colors *! + +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..b4ba88d 100644 --- a/lib/colors/terminal.tes +++ b/lib/colors/terminal.tes @@ -1,4 +1,6 @@ !* Default terminal color scheme *! +-1,-1,7EJ !* no default colors *! + [color.default] 0,Q[color.black],Q[color.white] [color.linenumber] 0,Q[color.black],Q[color.white] Q[color.black]U[color.caretline] @@ -9,6 +11,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/opener.tes b/lib/opener.tes index 59af79a..66a97e7 100644 --- a/lib/opener.tes +++ b/lib/opener.tes @@ -44,13 +44,10 @@ *! @[opener.check-recovery]{ :Q*"= ' - [* + [*[_ EQ.f G* I# R <-A"I 1; ' :R;> I# - 1:EN*Q.f"S - !* FIXME: log as a true warning once supports it *! - Warning: Detected recovery file "Q.f"^J - ' - ]* + 1:EN*Q.f"S 2Detected recovery file "Q.f" ' + ]_]* } diff --git a/lib/tank.tes b/lib/tank.tes index 8573e22..0894009 100644 --- a/lib/tank.tes +++ b/lib/tank.tes @@ -125,7 +125,7 @@ > M[tank.explode] - %[tank.score] SCORE: \[tank.score] + %[tank.score] 1SCORE: \[tank.score] } @[tank.selfdestruct]{ @@ -170,7 +170,7 @@ ESGETTABDRAWMODE-2"N > ' -CONTROL: W A S D. SHOOT: SPACE. QUIT: ESCAPE. PRESS ANY KEY TO START. +1CONTROL: W A S D. SHOOT: SPACE. QUIT: ESCAPE. PRESS ANY KEY TO START. ESGETCARETSTYLEU.[caretstyle] 0ESSETCARETSTYLE diff --git a/src/core-commands.c b/src/core-commands.c index 2dc1da7..5ca508c 100644 --- a/src/core-commands.c +++ b/src/core-commands.c @@ -2084,7 +2084,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) /*$ EJ properties * [key]EJ -> value -- Get and set system properties * value,keyEJ - * rgb,color,3EJ + * [fore,]back,7EJ * -EJ -> event * -2EJ -> y, x * @@ -2110,6 +2110,7 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * of buffers in the ring. * (\fBread-only\fP) * .IP 2: + * .SCITECO_TOPIC "memory limit" * The current memory limit in bytes. * This limit helps to prevent dangerous out-of-memory * conditions (e.g. resulting from infinite loops) by @@ -2134,49 +2135,40 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * requirements are too large for the new limit \(em if * this happens you may have to clear your command-line * first. - * Memory limiting is enabled by default. + * Memory limiting is enabled by default (default: 500MB). * .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. + * .SCITECO_TOPIC palette + * 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: + * .SCITECO_TOPIC caretx * The column after the last horizontal movement. * This is only used by \fBfnkeys.tes\fP and is similar to the Scintilla-internal * setting \fBSCI_CHOOSECARETX\fP. @@ -2199,6 +2191,41 @@ teco_state_ecommand_flags(teco_machine_main_t *ctx, GError **error) * After changing the interval, the new value may become * active only after the previous interval expires. * Recovery files are not dumped in batch mode. + * .IP 7: + * .SCITECO_TOPIC "default colors" + * This \fBwrite-only\fP property allows configuring the default + * foreground and background colors, given as two + * Scintilla-encoded RGB (0xBBGGRR) numbers. + * When asked to draw these colors, the terminal's native + * default colors \(em which may be some kind of white on black or + * black on white \(em are used instead. + * Therefore this command does not change any colors on screen + * \(em it just defines placeholder values for the terminal's + * default colors. + * This may be used e.g. to get a true black background where + * the terminal emulator's palette uses a different hue for + * Curses color COLOR_BLACK without having to resort to + * color redefinitions. + * With caution this may even be used to build color schemes that + * work both on white on black and black on white terminals. + * Use -1 to disable default foreground or background colors. + * It has no effect when using the GTK interface. + * .IP 8: + * .SCITECO_TOPIC "block size" + * Block size during backwards searches (e.g. \(lq-S\(rq). + * When searching backwards \*(ST will perform forward searches + * in within blocks before the search range's end. + * If not enough matches could be produced, it moves back + * another block. + * The smaller the block size the more likely that a match + * will not be the leftmost longest, but the less matching overhead. + * A negative or 0 value (\(lq0,8EJ\(rq) is the maximum block size and + * ensures forward searches over the entire search range. + * Only this setting guarantees leftmost longest matches that + * are entirely symmetric to forward searches, but can be + * unpractically slow on huge files. + * The default is 0. + * \# FIXME: Feature is currently broken! * . * .IP -1: * Type of the last mouse event (\fBread-only\fP). @@ -2250,10 +2277,12 @@ 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 + EJ_RECOVERY_INTERVAL, + EJ_DEFAULT_COLORS, + EJ_SEARCH_BLOCK_SIZE }; static teco_int_t caret_x = 0; @@ -2274,19 +2303,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: @@ -2315,6 +2338,19 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) /* FIXME: Perhaps signal the interface to reprogram timers */ break; + case EJ_DEFAULT_COLORS: { + teco_int_t fore; + + if (!teco_expressions_pop_num_calc(&fore, -1, error)) + return; + teco_interface_set_default_colors(fore, value); + break; + } + + case EJ_SEARCH_BLOCK_SIZE: + teco_undo_gsize(teco_search_block_size) = MAX(0, value); + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Cannot set property %" TECO_INT_FORMAT " " @@ -2378,6 +2414,10 @@ teco_state_ecommand_properties(teco_machine_main_t *ctx, GError **error) teco_expressions_push(teco_ring_recovery_interval); break; + case EJ_SEARCH_BLOCK_SIZE: + teco_expressions_push(teco_search_block_size); + break; + default: g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Invalid property %" TECO_INT_FORMAT " " diff --git a/src/interface-curses/interface.c b/src/interface-curses/interface.c index 569b13a..ff6a0f2 100644 --- a/src/interface-curses/interface.c +++ b/src/interface-curses/interface.c @@ -140,6 +140,16 @@ 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 * curses colors. @@ -156,6 +166,12 @@ static inline gboolean teco_interface_check_key(gint key); * 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) @@ -168,30 +184,28 @@ static inline gboolean teco_interface_check_key(gint key); 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]; - + gboolean use_palette; /** - * 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. + * Scintilla RGB values which should be rendered + * as the terminal's default foreground/background colors. + * This is -1 if colors haven't been configured. */ - struct { - gshort r, g, b; - } orig_color_table[16]; + gint32 default_fg, default_bg; int stdin_orig, stdout_orig, stderr_orig; SCREEN *screen; @@ -229,6 +243,76 @@ static struct { } teco_interface; /** + * Translate a Scintilla-compatible RGB color value + * (0xBBGGRR) to a Curses color triple (0 to 1000 + * for each component). + */ +static inline void +teco_rgb2curses_triple(guint32 rgb, gshort *r, gshort *g, gshort *b) +{ + /* NOTE: We could also use 200/51 */ + *r = ((rgb & 0x0000FF) >> 0)*1000/0xFF; + *g = ((rgb & 0x00FF00) >> 8)*1000/0xFF; + *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 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) +{ + static guint colors = 0; + + if (!has_colors()) + 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; +} + +/** * Returns the curses color pair for the given curses foreground and background colors. * Initializes a new pair if necessary. * @@ -239,12 +323,12 @@ static struct { * 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 + * @param fg curses foreground color (0xBBGGRR) + * @param bg curses background color (0xBBGGRR) * @return curses color pair number */ static gshort -teco_color_pair(attr_t *attr, gshort fg, gshort bg) +teco_color_pair(attr_t *attr, guint32 fg, guint32 bg) { static guint next_pair = 1; @@ -256,78 +340,44 @@ teco_color_pair(attr_t *attr, gshort fg, gshort bg) * color schemes. */ if (!has_colors()) { - if (bg != COLOR_BLACK) - *attr |= WA_REVERSE; + if (bg != 0x000000) + *attr ^= WA_REVERSE; return 0; } + gshort fg_id, bg_id; + + /* + * Prefer to use reverse attribute since it will work with + * the terminals default colors. + */ + if (fg == teco_interface.default_bg && bg == teco_interface.default_fg) { + fg_id = bg_id = -1; + *attr ^= WA_REVERSE; + } else { + fg_id = fg == teco_interface.default_fg ? -1 : teco_rgb2curses(fg); + bg_id = bg == teco_interface.default_bg ? -1 : teco_rgb2curses(bg); + } + G_STATIC_ASSERT(sizeof(gshort)*2 <= sizeof(guint)); - gpointer key = GUINT_TO_POINTER(((guint)fg << 8*sizeof(bg)) | bg); + gpointer key = GUINT_TO_POINTER(((guint)fg_id << 8*sizeof(bg_id)) | bg_id); gpointer value = g_hash_table_lookup(teco_interface.pair_table, key); if (G_LIKELY(value != NULL)) return GPOINTER_TO_UINT(value); - if (G_UNLIKELY(next_pair >= COLOR_PAIRS || next_pair > G_MAXSHORT)) + if (G_UNLIKELY(next_pair >= MAX_PAIRS)) return 0; - init_pair(next_pair, fg, bg); + init_pair(next_pair, fg_id, bg_id); g_hash_table_insert(teco_interface.pair_table, key, GUINT_TO_POINTER(next_pair)); return next_pair++; } static inline gint -teco_wattr_set(WINDOW *win, attr_t attr, gshort fg, gshort bg) +teco_wattr_set(WINDOW *win, attr_t attr, guint32 fg, guint32 bg) { gshort pair = teco_color_pair(&attr, fg, bg); return wattr_set(win, attr, pair, NULL); } -/** - * Translate a Scintilla-compatible RGB color value - * (0xBBGGRR) to a Curses color triple (0 to 1000 - * for each component). - */ -static inline void -teco_rgb2curses_triple(guint32 rgb, gshort *r, gshort *g, gshort *b) -{ - /* NOTE: We could also use 200/51 */ - *r = ((rgb & 0x0000FF) >> 0)*1000/0xFF; - *g = ((rgb & 0x00FF00) >> 8)*1000/0xFF; - *b = ((rgb & 0xFF0000) >> 16)*1000/0xFF; -} - -/** - * 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. - */ -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; - } - - return COLOR_WHITE; -} - static gint teco_xterm_version(void) { @@ -416,9 +466,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); @@ -433,10 +480,9 @@ static void teco_interface_draw_info(void); 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.default_fg = 0xC0C0C0; /* white */ + teco_interface.default_bg = 0x000000; /* black */; teco_interface.stdin_orig = teco_interface.stdout_orig = teco_interface.stderr_orig = -1; @@ -478,133 +524,17 @@ 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) +void +teco_interface_disable_palette(void) { - /* - * No way to restore the palette, or it's - * unnecessary (e.g. XCurses) - */ + teco_interface.use_palette = FALSE; } -#endif - void -teco_interface_init_color(guint color, guint32 rgb) +teco_interface_set_default_colors(gint32 fg, gint32 bg) { - 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.default_fg = fg; + teco_interface.default_bg = bg; } #ifdef CURSES_TTY @@ -746,14 +676,41 @@ 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(); - /* - * 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. - */ - scintilla_set_color_offsets(0, MIN(COLOR_PAIRS, G_MAXSHORT)/2); + scintilla_set_color_offsets(MAX_COLORS, MAX_PAIRS); + if (!teco_interface.use_palette) + scintilla_disable_color_palette(); +#ifdef HAVE_USE_DEFAULT_COLORS + /* color -1 will be the terminal's default foreground or background color */ + use_default_colors(); +#endif + scintilla_set_default_colors(teco_interface.default_fg, + teco_interface.default_bg); + + 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 @@ -835,19 +792,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 @@ -879,7 +823,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 @@ -944,23 +887,23 @@ teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) teco_interface_stdio_msg(type, str, len); #endif - gshort fg, bg; + guint32 fg, bg; - fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); + fg = teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0); switch (type) { default: case TECO_MSG_USER: - bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + bg = teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0); break; case TECO_MSG_INFO: - bg = COLOR_GREEN; + bg = 0x008000; /* green */ break; case TECO_MSG_WARNING: - bg = COLOR_YELLOW; + bg = 0x008080; /* yellow */ break; case TECO_MSG_ERROR: - bg = COLOR_RED; + bg = 0x000080; /* red */ beep(); break; } @@ -977,8 +920,8 @@ teco_interface_msg_clear(void) if (!teco_interface.input_pad) /* batch mode */ return; - gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + guint32 fg = teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0); + guint32 bg = teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0); wmove(teco_interface.msg_window, 0, 0); teco_wattr_set(teco_interface.msg_window, 0, fg, bg); @@ -1170,8 +1113,8 @@ teco_interface_draw_info(void) * the current buffer's STYLE_DEFAULT. * The same style is used for MSG_USER messages. */ - gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0)); - gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0)); + guint32 fg = teco_interface_ssm(SCI_STYLEGETBACK, STYLE_DEFAULT, 0); + guint32 bg = teco_interface_ssm(SCI_STYLEGETFORE, STYLE_DEFAULT, 0); wmove(teco_interface.info_window, 0, 0); teco_wattr_set(teco_interface.info_window, 0, fg, bg); @@ -1743,8 +1686,8 @@ teco_interface_popup_show(gsize prefix_len) /* batch mode */ return; - gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); - gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + guint32 fg = teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0); + guint32 bg = teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0); teco_interface.popup_prefix_len = prefix_len; attr_t attr = 0; @@ -1949,8 +1892,8 @@ teco_interface_process_mevent(MEVENT *event, GError **error) else if (event->bstate & BUTTON_NUM(5)) teco_curses_info_popup_scroll(&teco_interface.popup, +2); - gshort fg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0)); - gshort bg = teco_rgb2curses(teco_interface_ssm(SCI_STYLEGETBACK, STYLE_CALLTIP, 0)); + guint32 fg = teco_interface_ssm(SCI_STYLEGETFORE, STYLE_CALLTIP, 0); + guint32 bg = 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); @@ -2403,6 +2346,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/interface.c b/src/interface-gtk/interface.c index a31790f..1f6259f 100644 --- a/src/interface-gtk/interface.c +++ b/src/interface-gtk/interface.c @@ -404,7 +404,8 @@ 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_set_default_colors(gint32 fg, gint32 bg) {} void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len) diff --git a/src/interface.h b/src/interface.h index 54f807b..a7968d1 100644 --- a/src/interface.h +++ b/src/interface.h @@ -57,13 +57,17 @@ void teco_interface_init(gint argc, gchar **argv); 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); + +/** @pure makes sense only on Curses */ +void teco_interface_set_default_colors(gint32 fg, gint32 bg); 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; @@ -388,7 +388,21 @@ main(int argc, char **argv) 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); +#endif /* * Important for Unicode handling in curses and glib. diff --git a/src/search.c b/src/search.c index 856d079..92be012 100644 --- a/src/search.c +++ b/src/search.c @@ -20,6 +20,7 @@ #endif #include <string.h> +#include <stdlib.h> #include <glib.h> #include <glib/gprintf.h> @@ -573,8 +574,46 @@ teco_get_ranges(const GMatchInfo *match_info, gsize offset, guint *count) return ranges; } +/* only keep the last `count' matches, in a circular stack */ +typedef struct { + guint num_ranges; + teco_range_t *ranges; +} teco_match_t; + +typedef struct { + guint count; + teco_match_t matches[]; +} teco_matches_t; + +static inline void +teco_matches_free(teco_matches_t *p) +{ + for (int i = 0; i < p->count; i++) + g_free(p->matches[i].ranges); + g_free(p); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(teco_matches_t, teco_matches_free); + +/** + * Search forwards in the current buffer. + * + * If `*count == 0` after the call, the n-th match was found in + * the current document and teco_ranges/teco_ranges_count is + * set accordingly but must still be converted into glyph + * offsets. + * + * @param re Compiled regular expression. + * @param from Beginning of the range to search (in bytes). + * @param to End of the range to search (in bytes). + * @param count Pointer to an integer (n-th match to find). + * This is updated to reflect how many matches are still outstanding. + * A negative value indicates backwards searches. + * @param error A GError + * @return FALSE if an error occurred + */ static gboolean -teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) +teco_do_search_forward(GRegex *re, gsize from, gsize to, gint *count, GError **error) { g_autoptr(GMatchInfo) info = NULL; /* NOTE: can return NULL pointer for completely new and empty documents */ @@ -590,63 +629,175 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) return FALSE; } - guint num_ranges = 0; - teco_range_t *matched_ranges = NULL; + g_assert(*count > 0); + while (g_match_info_matches(info) && --(*count)) { + /* + * NOTE: The return boolean does NOT signal whether an error was generated. + */ + g_match_info_next(info, &tmp_error); + if (tmp_error) { + g_propagate_error(error, tmp_error); + return FALSE; + } - if (*count >= 0) { - while (g_match_info_matches(info) && --(*count)) { - /* - * NOTE: The return boolean does NOT signal whether an error was generated. - */ - g_match_info_next(info, &tmp_error); - if (tmp_error) { - g_propagate_error(error, tmp_error); - return FALSE; - } + /* + * FIXME: A single pathological match could already be excessively slow. + */ + if (G_UNLIKELY(teco_interface_is_interrupted())) { + teco_error_interrupted_set(error); + return FALSE; } + } - if (!*count) - /* successful */ - matched_ranges = teco_get_ranges(info, from, &num_ranges); - } else { - /* only keep the last `count' matches, in a circular stack */ - typedef struct { - guint num_ranges; - teco_range_t *ranges; - } teco_match_t; + if (!*count) { + /* successful */ + teco_undo_guint(teco_ranges_count); + teco_undo_ranges_own(teco_ranges) = teco_get_ranges(info, from, &teco_ranges_count); + } - guint matched_num = -*count; - gsize matched_size = sizeof(teco_match_t[matched_num]); + return TRUE; +} +/** + * Block size for backwards scanning or 0 + * + * @bug Block-wise matching is currently broken, + * so we disable this by default - see below. + */ +gsize teco_search_block_size = 0; //4*1024; + +/** + * Search backwards, in blocks of teco_search_block_size + * (within each block a forward scan is performed). + * + * This cannot always guarantee longest matches if a match is + * found at the beginning of a block. + * Only if teco_search_block_size is 0 the entire range + * if forward scanned, which ensures longest matches + * entirely symmetrical to forward searches. + * For large block sizes (or 0) even trivial backwards searches + * can be painstakingly slow on large files, though. + * + * @see teco_do_search_forward + */ +static gboolean +teco_do_search_backwards(GRegex *re, gsize from, gsize to, gint *count, GError **error) +{ + /* + * NOTE: can return NULL pointer for completely new and empty documents. + * + * Theoretically we could also get range pointers on demand which would + * be more likely to preserve the buffer gap. + */ + const gchar *buffer = (const gchar *)teco_interface_ssm(SCI_GETRANGEPOINTER, from, to-from) ? : ""; + + g_assert(*count < 0); + guint matched_num = -*count; + gsize total_size = sizeof(teco_matches_t) + sizeof(teco_match_t[matched_num]); + + /* + * matched_size could overflow. + * NOTE: Glib 2.48 has g_size_checked_mul() which uses + * compiler intrinsics. + */ + if ((gsize)matched_num > (G_MAXSIZE - sizeof(teco_matches_t))/sizeof(teco_match_t)) + /* guaranteed to fail either teco_memory_check() or g_malloc() */ + total_size = G_MAXSIZE; + + /* + * NOTE: It's theoretically possible that the allocation of the `matched` + * array causes an OOM if (-count) is large enough and regular + * memory limiting in teco_machine_main_step() wouldn't help. + * That's why we exceptionally have to check before allocating. + */ + if (!teco_memory_check(total_size, error)) + return FALSE; + + g_autoptr(teco_matches_t) matched = g_malloc0(total_size); + matched->count = matched_num; + + gint matched_total = 0; + gint i = 0; /* ring buffer pointer into the `matched->matches` array */ + + gsize to_block = to-from; + + while (to_block > 0) { + g_autoptr(GMatchInfo) info = NULL; + + gsize from_block = teco_search_block_size > 0 + ? MAX(0, to_block - teco_search_block_size) : 0; /* - * matched_size could overflow. - * NOTE: Glib 2.48 has g_size_checked_mul() which uses - * compiler intrinsics. + * FIXME: DEC TECO search semantics could actually demand + * allowing matches to extend beyond the [from,to] range. */ - if (matched_size / sizeof(teco_match_t) != matched_num) - /* guaranteed to fail either teco_memory_check() or g_malloc() */ - matched_size = G_MAXSIZE; + GRegexMatchFlags flags = to_block != to-from ? G_REGEX_MATCH_PARTIAL_HARD : 0; + + GError *tmp_error = NULL; /* - * NOTE: It's theoretically possible that the allocation of the `matched` - * array causes an OOM if (-count) is large enough and regular - * memory limiting in teco_machine_main_step() wouldn't help. - * That's why we exceptionally have to check before allocating. + * NOTE: The return boolean does NOT signal whether an error was generated. + * FIXME: Why isn't it possible to specify a start_position != 0? */ - if (!teco_memory_check(matched_size, error)) + g_regex_match_full(re, buffer+from_block, to_block-from_block, 0, + flags, &info, &tmp_error); + if (tmp_error) { + g_propagate_error(error, tmp_error); return FALSE; + } - /* - * NOTE: This needs to be deep-freed, which does not currently - * happen automatically. - */ - g_autofree teco_match_t *matched = g_malloc0(matched_size); + for (;;) { + if (G_UNLIKELY(teco_interface_is_interrupted())) { + teco_error_interrupted_set(error); + return FALSE; + } - gint matched_total = 0, i = 0; + if (g_match_info_is_partial_match(info)) { + /* + * Match may fall on the block boundary, + * so retry matching the rest of the document. + * This is the only case where we have to rescan + * the same memory more than once. + * + * FIXME FIXME FIXME: We cannot retrieve the position here + * since g_match_info_fetch_pos() treats partial matches as errors. + * This is a confirmed glib bug and fast backwards searches + * will continue to be broken until we switch to a custom regexp + * engine. + */ + gint partial_start, partial_end; + G_GNUC_UNUSED gboolean rc; + rc = g_match_info_fetch_pos(info, 0, &partial_start, &partial_end); + //g_assert(rc == TRUE); + if (!rc) + abort(); + + if (partial_end == to_block-from_block) { + g_autoptr(GMatchInfo) partial_info = NULL; + + g_regex_match_full(re, buffer+partial_start, to-from-partial_start, 0, + G_REGEX_MATCH_ANCHORED, &partial_info, &tmp_error); + if (tmp_error) { + g_propagate_error(error, tmp_error); + return FALSE; + } + + if (g_match_info_matches(partial_info)) { + g_free(matched->matches[i].ranges); + matched->matches[i].ranges = teco_get_ranges(partial_info, from+partial_start, + &matched->matches[i].num_ranges); + i = ++matched_total % matched_num; + } + } - while (g_match_info_matches(info)) { - g_free(matched[i].ranges); - matched[i].ranges = teco_get_ranges(info, from, &matched[i].num_ranges); + /* there might still be other matches within the current block */ + } else if (g_match_info_matches(info)) { + g_free(matched->matches[i].ranges); + matched->matches[i].ranges = teco_get_ranges(info, from+from_block, + &matched->matches[i].num_ranges); + i = ++matched_total % matched_num; + } else { + break; + } /* * NOTE: The return boolean does NOT signal whether an error was generated. @@ -654,30 +805,50 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) g_match_info_next(info, &tmp_error); if (tmp_error) { g_propagate_error(error, tmp_error); - for (int i = 0; i < matched_num; i++) - g_free(matched[i].ranges); return FALSE; } - - i = ++matched_total % -(*count); } - *count = MIN(*count + matched_total, 0); - if (!*count) { + if (matched_total >= matched_num) { /* successful -> i points to stack bottom */ - num_ranges = matched[i].num_ranges; - matched_ranges = matched[i].ranges; - matched[i].ranges = NULL; + teco_undo_guint(teco_ranges_count) = matched->matches[i].num_ranges; + teco_undo_ranges_own(teco_ranges) = matched->matches[i].ranges; + matched->matches[i].ranges = NULL; + + *count = 0; + return TRUE; } - for (gint i = 0; i < matched_num; i++) - g_free(matched[i].ranges); + /* + * We got less matches than the required number. + * We know that the result won't be any of the existing + * matches, so we can discard them and shrink our + * circular buffer. + * This avoids merging the result lists of two blocks. + * Elements are freed when being overwritten just like + * when the buffer overflows. + */ + matched_num -= matched_total; + i = 0; + + to_block = from_block; } - if (matched_ranges) { + *count += matched_total; + g_assert(*count < 0); + return TRUE; +} + +static gboolean +teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) +{ + gboolean rc = *count >= 0 ? teco_do_search_forward(re, from, to, count, error) + : teco_do_search_backwards(re, from, to, count, error); + if (!rc) + return FALSE; + + if (!*count) { /* match success */ - teco_undo_ranges_own(teco_ranges) = matched_ranges; - teco_undo_guint(teco_ranges_count) = num_ranges; g_assert(teco_ranges_count > 0); teco_interface_ssm(SCI_SETSEL, teco_ranges[0].from, teco_ranges[0].to); @@ -685,6 +856,8 @@ teco_do_search(GRegex *re, gsize from, gsize to, gint *count, GError **error) /* * teco_get_ranges() returned byte positions, * while everything else expects glyph offsets. + * They are fixed up only now to avoid unnecessary + * possibly costly teco_interface_bytes2glyphs() calls. */ for (guint i = 0; i < teco_ranges_count; i++) { teco_ranges[i].from = teco_interface_bytes2glyphs(teco_ranges[i].from); @@ -777,6 +950,7 @@ teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new teco_ring_undo_edit(); if (count > 0) { + /* search forward */ do { buffer = teco_buffer_next(buffer) ? : teco_ring_first(); teco_buffer_edit(buffer); @@ -792,6 +966,7 @@ teco_state_search_process(teco_machine_main_t *ctx, teco_string_t str, gsize new return FALSE; } while (count); } else /* count < 0 */ { + /* search backwards */ do { buffer = teco_buffer_prev(buffer) ? : teco_ring_last(); teco_buffer_edit(buffer); diff --git a/src/search.h b/src/search.h index 9bd62f7..dc6413e 100644 --- a/src/search.h +++ b/src/search.h @@ -20,6 +20,8 @@ #include "parser.h" +extern gsize teco_search_block_size; + void teco_state_control_search_mode(teco_machine_main_t *ctx, GError **error); extern teco_state_t teco_state_search; 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. @@ -109,12 +109,12 @@ teco_view_setup(teco_view_t *ctx) teco_view_ssm(ctx, SCI_SETCARETSTYLE, CARETSTYLE_BLOCK | CARETSTYLE_OVERSTRIKE_BLOCK | CARETSTYLE_BLOCK_AFTER, 0); teco_view_ssm(ctx, SCI_SETCARETPERIOD, 0, 0); - teco_view_ssm(ctx, SCI_SETCARETFORE, 0xFFFFFF, 0); + teco_view_ssm(ctx, SCI_SETCARETFORE, 0xC0C0C0, 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); @@ -132,7 +132,7 @@ teco_view_setup(teco_view_t *ctx) * default if no color-scheme is applied (and --no-profile). */ teco_view_ssm(ctx, SCI_STYLESETFORE, STYLE_CALLTIP, 0x000000); - teco_view_ssm(ctx, SCI_STYLESETBACK, STYLE_CALLTIP, 0xFFFFFF); + teco_view_ssm(ctx, SCI_STYLESETBACK, STYLE_CALLTIP, 0xC0C0C0); /* * Since we have patched out Scintilla's original SetRepresentations(), @@ -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/testsuite.at b/tests/testsuite.at index 6bc33e4..fc8ab37 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]) @@ -663,11 +670,10 @@ AT_CLEANUP #AT_XFAIL_IF(true) #AT_CLEANUP -AT_SETUP([Recursion overflow]) -# Should no longer dump core. -# It could fail because the memory limit is exceeed, -# but not in this case since we limit the recursion. -TE_CHECK([[@^Um{U.a Q.a-100000"<%.aMm'} 0Mm]], 0, ignore, ignore) +AT_SETUP([Block-wise backwards search]) +# Crashes are caused by a glib bug when a match falls on block boundaries. +# See teco_do_search_backwards() +TE_CHECK([[2,8EJ @I/ABCD/ -:@S/BC/"F(0/0)' .-3"N(0/0)' ^S+2"N(0/0)']], 0, ignore, ignore) AT_XFAIL_IF(true) AT_CLEANUP @@ -677,6 +683,14 @@ TE_CHECK([[@I/ /J :@S/^ES^X/"S(0/0)']], 0, ignore, ignore) AT_XFAIL_IF(true) AT_CLEANUP +AT_SETUP([Recursion overflow]) +# Should no longer dump core. +# It could fail because the memory limit is exceeed, +# but not in this case since we limit the recursion. +TE_CHECK([[@^Um{U.a Q.a-100000"<%.aMm'} 0Mm]], 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 `:@{`. |
