diff -Nru dte-1.9.1/.builds/debian-sid-arm64.yml dte-1.10/.builds/debian-sid-arm64.yml --- dte-1.9.1/.builds/debian-sid-arm64.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/.builds/debian-sid-arm64.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,11 @@ +image: debian/unstable +arch: arm64 +packages: [make, gcc] +sources: [https://git.sr.ht/~xcb/dte] +tasks: + - build: | + cd dte + make vars + make check DEBUG=3 WERROR=1 V=1 + cat build/feature.h + ./dte -V diff -Nru dte-1.9.1/.builds/freebsd-11.yml dte-1.10/.builds/freebsd-11.yml --- dte-1.9.1/.builds/freebsd-11.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/.builds/freebsd-11.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,10 @@ +image: freebsd/11.x +packages: [gmake, gcc] +sources: [https://git.sr.ht/~xcb/dte] +tasks: + - build: | + cd dte + make vars + make check DEBUG=3 WERROR=1 V=1 + cat build/feature.h + ./dte -V diff -Nru dte-1.9.1/.builds/freebsd-12.yml dte-1.10/.builds/freebsd-12.yml --- dte-1.9.1/.builds/freebsd-12.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/.builds/freebsd-12.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,10 @@ +image: freebsd/12.x +packages: [gmake, gcc] +sources: [https://git.sr.ht/~xcb/dte] +tasks: + - build: | + cd dte + make vars + make check DEBUG=3 WERROR=1 V=1 + cat build/feature.h + ./dte -V diff -Nru dte-1.9.1/.builds/openbsd.yml dte-1.10/.builds/openbsd.yml --- dte-1.9.1/.builds/openbsd.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/.builds/openbsd.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,10 @@ +image: openbsd/latest +packages: [gmake, gcc] +sources: [https://git.sr.ht/~xcb/dte] +tasks: + - build: | + cd dte + make vars + make check DEBUG=3 WERROR=1 V=1 + cat build/feature.h + ./dte -V diff -Nru dte-1.9.1/CHANGELOG.md dte-1.10/CHANGELOG.md --- dte-1.9.1/CHANGELOG.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/CHANGELOG.md 2021-04-03 21:08:53.000000000 +0000 @@ -1,6 +1,85 @@ Releases ======== +v1.10 (not yet released) +------------------------ + +**Additions:** + +* Added 7 new commands: + * [`blkdown`] + * [`blkup`] + * [`delete-line`] + * [`exec-open`] + * [`exec-tag`] + * [`macro`] + * [`match-bracket`] +* Added 12 new command flags: + * [`include -q`][`include`] + * [`hi -c`][`hi`] + * [`filter -l`][`filter`] + * [`pipe-to -l`][`pipe-to`] + * [`close -p`][`close`] + * [`wclose -p`][`wclose`] + * [`open -t`][`open`] + * [`save -b`][`save`] + * [`save -B`][`save`] + * [`wsplit -t`][`wsplit`] + * [`wsplit -g`][`wsplit`] + * [`wsplit -n`][`wsplit`] +* Added 2 new global options: + * [`select-cursor-char`] + * [`utf8-bom`] +* Added an optional *exitcode* argument to the [`quit`] command. +* Added `color`, `command`, `env`, `errorfmt`, `ft`, `macro`, `option`, + `search` and `wsplit` arguments to the [`show`] command. +* Added support for the `\e` escape sequence in [double-quoted] command + arguments. +* Added syntax highlighting for Lisp and Scheme files. +* Added an Alt+Enter key binding to search mode, for performing + plain-text searches. +* Added a Shift+Tab key binding to command mode, for iteratating + auto-completions in reverse order. +* Added `%b`, `%N` and `%S` [statusline] format specifiers. +* Added a large confirmation dialog, shown when [`quit -p`][`quit`] is + run with unsaved changes. +* Added the ability to exclude individual commands from command history + (by prepending a space character when in command mode). + +**Improvements:** + +* Updated Unicode support to version 13. +* Bound Ctrl+c to [`copy -k`][`copy`] by default. +* Re-introduced built-in support for rxvt Ctrl/Alt/Shift key combinations. +* Fixed the handling of optional capture groups in [`errorfmt`] patterns. +* Improved the legibility of the default color scheme on a wider range + of terminals. +* Changed the `filter` and `pipe-from` commands to set `$LINES`/`$COLUMNS` + to the current window height/width, before running the specified program. +* Clarified which command flags in the [`dterc`] man page are mutually + exclusive (by separating them with `|`). +* Fixed signal handling, to allow interrupting unresponsive/deadlocked + child processes with Ctrl+c. +* Fixed command-line auto-completion to work properly when option flags + are present. +* Improved the documentation for [`tag`], [`replace`], and [`errorfmt`]. +* Various syntax highlighting improvements. +* Various terminal compatibility improvements. +* Various performance improvements. + +**Breaking changes:** + +* Removed support for linking to the system terminfo library. The + terminfo database has only been used as a last resort source of + information for several releases now. Most terminals that people + are likely to be using already have built-in support in the editor, + including several capabilities not available from terminfo. This is + listed as a breaking change because it may break support for a few + archaic hardware terminals (primarily those that aren't ECMA-48 + compatible or whose terminfo strings contain mandatory padding). +* Removed support for vertical tab bars (the `tab-bar` option was + changed from an enum to a Boolean). + v1.9.1 (latest release) ----------------------- @@ -345,8 +424,52 @@ A list of SHA256 checksums for all release tarballs and signatures is available at . +Portable Builds for Linux +========================= + +Some pre-built, portable binaries are available for Linux. They're +statically-linked with [musl] libc and require nothing of the host +system except a somewhat recent kernel. + +* [`dte-master-linux-x86_64.tar.gz`](https://craigbarnes.gitlab.io/dte/dte-master-linux-x86_64.tar.gz) +* [`dte-1.9.1-linux-x86_64.tar.gz`](https://craigbarnes.gitlab.io/dist/dte/dte-1.9.1-linux-x86_64.tar.gz) + +*Note*: only `x86_64` builds are available for now. Feel free to open an +[issue] if you need builds for other architectures. + [website]: https://craigbarnes.gitlab.io/dte/ [dex]: https://github.com/tihirvon/dex [dex v1.0]: https://github.com/tihirvon/dex/releases/tag/v1.0 [ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm +[musl]: https://www.musl-libc.org/ +[issue]: https://gitlab.com/craigbarnes/dte/-/issues +[`dterc`]: https://craigbarnes.gitlab.io/dte/dterc.html + +[`blkdown`]: https://craigbarnes.gitlab.io/dte/dterc.html#blkdown +[`blkup`]: https://craigbarnes.gitlab.io/dte/dterc.html#blkup +[`close`]: https://craigbarnes.gitlab.io/dte/dterc.html#close +[`copy`]: https://craigbarnes.gitlab.io/dte/dterc.html#copy +[`delete-line`]: https://craigbarnes.gitlab.io/dte/dterc.html#delete-line +[`errorfmt`]: https://craigbarnes.gitlab.io/dte/dterc.html#errorfmt +[`exec-open`]: https://craigbarnes.gitlab.io/dte/dterc.html#exec-open +[`exec-tag`]: https://craigbarnes.gitlab.io/dte/dterc.html#exec-tag +[`filter`]: https://craigbarnes.gitlab.io/dte/dterc.html#filter +[`hi`]: https://craigbarnes.gitlab.io/dte/dterc.html#hi +[`include`]: https://craigbarnes.gitlab.io/dte/dterc.html#include +[`macro`]: https://craigbarnes.gitlab.io/dte/dterc.html#macro +[`match-bracket`]: https://craigbarnes.gitlab.io/dte/dterc.html#match-bracket +[`open`]: https://craigbarnes.gitlab.io/dte/dterc.html#open +[`pipe-to`]: https://craigbarnes.gitlab.io/dte/dterc.html#pipe-to +[`quit`]: https://craigbarnes.gitlab.io/dte/dterc.html#quit +[`replace`]: https://craigbarnes.gitlab.io/dte/dterc.html#replace +[`save`]: https://craigbarnes.gitlab.io/dte/dterc.html#save +[`show`]: https://craigbarnes.gitlab.io/dte/dterc.html#show +[`tag`]: https://craigbarnes.gitlab.io/dte/dterc.html#tag +[`wclose`]: https://craigbarnes.gitlab.io/dte/dterc.html#wclose +[`wsplit`]: https://craigbarnes.gitlab.io/dte/dterc.html#wsplit + +[double-quoted]: https://craigbarnes.gitlab.io/dte/dterc.html#double-quoted-strings +[`select-cursor-char`]: https://craigbarnes.gitlab.io/dte/dterc.html#select-cursor-char +[`utf8-bom`]: https://craigbarnes.gitlab.io/dte/dterc.html#utf8-bom +[statusline]: https://craigbarnes.gitlab.io/dte/dterc.html#statusline-left diff -Nru dte-1.9.1/completion.bash dte-1.10/completion.bash --- dte-1.9.1/completion.bash 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/completion.bash 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,38 @@ +#!/usr/bin/bash + +_dte() { + local dte="$1" + local cur="$2" + local prev="$3" + + case "$cur" in + -) + COMPREPLY=($(compgen -W "-h -H -K -B -R -V -c -t -r -b -s" -- "$cur")) + return;; + -[bcrstHR]) + COMPREPLY=("$cur") + return;; + -*) # -[hBKV] + return;; + esac + + case "$prev" in + -b) + local rcnames="$($dte -B)" + COMPREPLY=($(compgen -W "$rcnames" -- "$cur")) + return;; + -t) + COMPREPLY=($( + readtags -Q "(prefix? \$name \"$cur\")" -l 2>/dev/null | + cut -f1 | + head -n50000 + )) + return;; + -[cBhKV]) + return;; + esac + + compopt -o bashdefault -o default +} + +complete -F _dte dte diff -Nru dte-1.9.1/config/binding/default dte-1.10/config/binding/default --- dte-1.9.1/config/binding/default 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/binding/default 2021-04-03 21:08:53.000000000 +0000 @@ -16,6 +16,8 @@ bind M-right eol bind M-up pgup bind M-down pgdown +bind M-delete 'delete-word -s' +bind M-C-delete 'delete-line' bind S-left 'left -c' bind S-right 'right -c' @@ -48,13 +50,16 @@ bind C-M-S-pgdown 'pgdown -l' bind C-M-S-delete 'delete-eol' -# Either of these 2 may be equivalent to Backspace, depending on the -# terminal, so they're both bound to erase by default for portability. -# On some terminals one may be Backspace and the other Ctrl+Backspace. -bind ^H erase -bind ^? erase +# Either Ctrl+H or Ctrl+? may be equivalent to Backspace, depending +# on the terminal, so they're both bound to "erase" by default for +# portability. On some terminals one may be equivalent to Backspace +# and the other Ctrl+Backspace. +bind C-H erase +bind C-? erase +bind M-C-H 'erase-word -s' +bind M-C-? 'erase-word -s' -bind ^C copy +bind ^C 'copy -k' bind ^F search bind ^G 'search -n' # ^I == Tab @@ -67,7 +72,7 @@ bind ^S 'save -p' bind ^T open bind ^V 'paste -c' -bind ^W 'close -wq' +bind ^W 'close -pwq' bind ^X cut bind ^Y redo bind ^Z undo @@ -103,8 +108,6 @@ bind M-> wnext bind M-- 'shift -- -1' bind M-= 'shift +1' -bind M-C-? 'erase-word -s' -bind M-delete 'delete-word -s' bind M-\; command bind M-: command bind M-/ search @@ -113,7 +116,12 @@ bind M-! 'move-tab left' bind M-@ 'move-tab right' -bind F1 'run man dte' +bind F1 'run man dterc' bind F3 'search -n' bind F4 'search -p' bind F5 refresh + +# Most terminals don't support sending these modifer+key combinations +# as distinct sequences, so don't be surprised if they don't work. +bind C-tab next +bind C-S-tab prev diff -Nru dte-1.9.1/config/binding/shift-select dte-1.10/config/binding/shift-select --- dte-1.9.1/config/binding/shift-select 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/binding/shift-select 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -# This file is kept for backwards compatibility purposes. -# It was used to bind Shift+move selection keys, until they were -# integrated into the editor as a built-in feature (e.g `left -c`). diff -Nru dte-1.9.1/config/color/default dte-1.10/config/color/default --- dte-1.9.1/config/color/default 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/color/default 2021-04-03 21:08:53.000000000 +0000 @@ -3,7 +3,7 @@ # compatibility and remain suitable for both light and dark terminals. hi -hi linenumber gray darkgray +hi linenumber reverse hi selection default blue hi text @@ -11,7 +11,7 @@ hi ident hi keyword bold hi type green -hi comment darkgray +hi comment cyan hi numeric blue hi string yellow hi variable bold yellow @@ -21,13 +21,15 @@ hi label bold red hi notice bold red hi error black yellow +hi -c comment 245 +hi -c linenumber 250 237 hi tex.macro bold yellow hi ini.section bold hi make.builtin bold magenta hi make.special-target green hi markdown.heading bold magenta -hi markdown.emphasis italic cyan +hi markdown.emphasis cyan hi markdown.strong-emphasis bold cyan hi markdown.code-block yellow hi markdown.code-span yellow diff -Nru dte-1.9.1/config/color/reset dte-1.10/config/color/reset --- dte-1.9.1/config/color/reset 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/color/reset 2021-04-03 21:08:53.000000000 +0000 @@ -13,3 +13,4 @@ hi tabbar black gray hi activetab bold hi inactivetab black gray +hi dialog white red bold diff -Nru dte-1.9.1/config/color/reset-basic dte-1.10/config/color/reset-basic --- dte-1.9.1/config/color/reset-basic 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/color/reset-basic 2021-04-03 21:08:53.000000000 +0000 @@ -13,3 +13,4 @@ hi tabbar reverse hi activetab bold hi inactivetab reverse +hi dialog reverse diff -Nru dte-1.9.1/config/compiler/gcc dte-1.10/config/compiler/gcc --- dte-1.9.1/config/compiler/gcc 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/compiler/gcc 2021-04-03 21:08:53.000000000 +0000 @@ -2,10 +2,15 @@ errorfmt -i gcc 'error: for each function it appears in.\)' errorfmt -i gcc '^([^:]+):([0-9]+): (error|warning): \(near ' errorfmt -i gcc '^([^:]+):([0-9]+):([0-9]+):$' + errorfmt gcc '^([^:]+):([0-9]+):([0-9]+): (.*)' file line column message errorfmt gcc '^([^:]+):([0-9]+): (.*)' file line message errorfmt gcc '^.* at (.+):([0-9]+):$' file line errorfmt gcc " *inlined from '.*' at (.*):([0-9]+):" file line + +# ASan stack trace (e.g. " #12 0x55af25320630 in func_name src/filename.c:1142") +errorfmt gcc '^ *(#[0-9]+ 0x[0-9A-Fa-f]+ in [[:alnum:]_]+) ([^:]+):([0-9]+)$' message file line + errorfmt -i gcc '^In file included from (.+):([0-9]+)[,:]' errorfmt -i gcc '^ +from (.+):([0-9]+)[,:]' errorfmt -i gcc '^([^:]+): (In function .*:)$' diff -Nru dte-1.9.1/config/rc dte-1.10/config/rc --- dte-1.9.1/config/rc 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/rc 2021-04-03 21:08:53.000000000 +0000 @@ -4,7 +4,7 @@ include -b compiler/go alias builtin 'include -b' -alias help 'run man dte' +alias help 'run man dterc' alias make 'compile gcc make' alias man 'run man' alias read 'pipe-from cat' @@ -13,9 +13,11 @@ alias split-words 'replace -g "[ \t]+" "\n"' alias trim-lines 'replace "[ \t]+$" ""' alias xsel 'pipe-to xsel -b' +alias exit quit -errorfmt gitgrep '^([^:]+):([0-9]+):' file line -alias git-grep 'compile -1s gitgrep git grep -n' +errorfmt basic '^([^:]+):([0-9]+):([0-9]+):' file line column +errorfmt basic '^([^:]+):([0-9]+):' file line +alias git-grep 'compile -1s basic git grep -n' # Remove possible 'F' from $LESS so that less will always wait # for keypress and "run" can be used without "-p". @@ -58,3 +60,5 @@ # Aliases for renamed built-in commands (for backwards compatibility) alias format-paragraph wrap-paragraph alias pass-through pipe-from +alias git-open 'exec-open sh -c "git ls-files $(git rev-parse --show-cdup) | fzf -m --reverse"' +alias insert-builtin 'show include' diff -Nru dte-1.9.1/config/syntax/c dte-1.10/config/syntax/c --- dte-1.9.1/config/syntax/c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/c 2021-04-03 21:08:53.000000000 +0000 @@ -1,11 +1,15 @@ +require c-comment +require c-uchar + syntax .c-esc -# TODO: C11 Unicode escapes (e.g. "\u00ff" and "\U00ff99cc") state start special char "abfnrtv'\\\"" END special char 0-3 oct1 char 4-7 oct2 char x hex0 + char u .c-uchar4:END special + char U .c-uchar8:END special # Anything but \n char -n "\n" END error # Don't eat \n @@ -53,16 +57,6 @@ char "'" END char eat END error -syntax .c-comment - -state comment - char '*' star - eat this - -state star comment - char / END comment - noeat comment - syntax .cpp-comment state comment @@ -284,9 +278,9 @@ uchar ushort uint ulong ullong u_char u_short u_int u_long u_llong list posix-type \ - blkcnt_t blksize_t clockid_t dev_t fsblkcnt_t fsfilcnt_t gid_t id_t \ - ino_t key_t mode_t nlink_t off_t pid_t regex_t regmatch_t regoff_t \ - siginfo_t sigjmp_buf sigset_t ssize_t suseconds_t timer_t uid_t DIR \ + blkcnt_t blksize_t clockid_t dev_t DIR fsblkcnt_t fsfilcnt_t gid_t glob_t \ + id_t ino_t key_t locale_t mode_t nlink_t off_t pid_t regex_t regmatch_t \ + regoff_t siginfo_t sigjmp_buf sigset_t ssize_t suseconds_t timer_t uid_t \ trace_attr_t trace_event_id_t trace_event_set_t trace_id_t \ pthread_t pthread_attr_t pthread_barrier_t pthread_barrierattr_t \ pthread_cond_t pthread_condattr_t pthread_key_t pthread_mutex_t \ @@ -314,10 +308,15 @@ ULL UL U LLU LU LL L list cpp-keyword \ - catch class const_cast delete dynamic_cast explicit friend \ - mutable namespace new operator private protected public \ - reinterpret_cast static_cast template this throw try typeid \ - typeof typename using virtual + and and_eq bitand bitor catch class compl const_cast concept \ + consteval constexpr constinit const_cast \ + co_await co_return co_yield \ + decltype delete dynamic_cast explicit export friend \ + mutable namespace new noexcept not not_eq nullptr \ + operator or or_eq \ + private protected public \ + reinterpret_cast requires static_cast template this throw try \ + typeid typename using virtual xor xor_eq default ident class-name default keyword c11-keyword cpp-keyword diff -Nru dte-1.9.1/config/syntax/css dte-1.10/config/syntax/css --- dte-1.9.1/config/syntax/css 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/css 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,4 @@ -syntax .css-comment - -state comment - char "*" star - eat this - -state star comment - char / END comment - noeat comment +require c-comment # Eat erroneous statement; stop at ; or \n syntax .css-statementerror @@ -132,32 +124,6 @@ char ) END url eat END error -# List of words -syntax .css-medialist - -state list code - char "\t\n " this - char -b a-zA-Z0-9_- name - noeat END - -state name code - char -b a-zA-Z0-9_ this - inlist mediatype next - noeat next - -state next code - char "\t\n " this - char , must - noeat END - -state must code - char "\t\n " this - char -b a-zA-Z0-9_ name - eat END error - -list -i mediatype \ - all braille embossed handheld print projection screen speech tty tv - syntax .css-import state import atkeyword @@ -172,11 +138,8 @@ noeat error state medialist - noeat .css-medialist:mediaend - -state mediaend char \; END code - noeat error + eat this state error noeat .css-statementerror:END @@ -238,7 +201,7 @@ char [ .css-attributeselector:this char { block char 0-9_- .css-selectorerror:this - str "/*" .css-comment:this + str "/*" .c-comment:this char -b @ atrule eat this @@ -295,24 +258,19 @@ bufis -i "@import" .css-import:start atkeyword # @namespace [prefix] { URI | string }; bufis -i "@namespace" .css-namespace:start atkeyword - # FIXME: @media not allowed inside @media bufis -i @media mediatypes atkeyword noeat start -state mediatypes - noeat .css-medialist:mediablock - -state mediablock code - char "\t\n " this +state mediatypes code char { start - eat start error + eat this state block code char " \t\n;" this char -b a-zA-Z- property char 0-9_- property-error char } start - str "/*" .css-comment:this + str "/*" .c-comment:this eat this state property code @@ -341,7 +299,7 @@ str -i "url(" .css-url:this char -b a-zA-Z_ value char } start - str "/*" .css-comment:this + str "/*" .c-comment:this eat this state minus numeric @@ -603,5 +561,5 @@ default keyword property default type class id pseudoclass pseudoelement attribute default special expr -default constant value color url mediatype +default constant value color url default special atkeyword diff -Nru dte-1.9.1/config/syntax/d dte-1.10/config/syntax/d --- dte-1.9.1/config/syntax/d 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/d 2021-04-03 21:08:53.000000000 +0000 @@ -1,3 +1,5 @@ +require c-comment + syntax .d-esc state start special @@ -49,16 +51,6 @@ char "'" END char eat END error -syntax .d-long-comment - -state comment - char "*" star - eat this - -state star comment - char / END comment - noeat comment - syntax .d-line-comment state comment @@ -90,7 +82,7 @@ char \' .d-char:this char "\n" start char ';' semicolon - str "/*" .d-long-comment:this + str "/*" .c-comment:this str "//" .d-line-comment:start eat this diff -Nru dte-1.9.1/config/syntax/dte dte-1.10/config/syntax/dte --- dte-1.9.1/config/syntax/dte 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/dte 2021-04-03 21:08:53.000000000 +0000 @@ -1,3 +1,5 @@ +require c-uchar + syntax dte state start code @@ -24,15 +26,19 @@ eat this state dq string - char -b "\\" dq-escape + char "\\" dq-escape char '"' command string char "\n" start eat this state dq-escape special - char "abfnrtv'\\\"" dq special + char "abefnrtv'\\\"" dq special char x hex1 - eat dq error + char u .c-uchar4:dq special + char U .c-uchar8:dq special + recolor error 1 + char -n "\n" dq error + noeat dq state hex1 special char 0-9a-fA-F hex2 diff -Nru dte-1.9.1/config/syntax/html dte-1.10/config/syntax/html --- dte-1.9.1/config/syntax/html 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/html 2021-04-03 21:08:53.000000000 +0000 @@ -142,20 +142,23 @@ eat this list -i tag \ - a abbr acronym address area b base bdo big blockquote body br \ + a abbr address area b base bdo blockquote body br \ button caption cite code col colgroup dd del dfn div dl dt em \ fieldset form h1 h2 h3 h4 h5 h6 head hr html i iframe img input \ ins kbd label legend li link map meta noscript object ol optgroup \ option p param pre q samp script select small span strong style \ - sub sup table tbody td textarea tfoot th thead title tr tt ul var \ + sub sup table tbody td textarea tfoot th thead title tr ul var \ \ - article aside audio canvas command datalist details embed figure \ - footer header hgroup keygen main mark meter nav output progress ruby \ - section time video wbr + article aside audio bdi canvas command data datalist details \ + dialog embed figcaption figure footer header hgroup main mark \ + menu meter nav output picture progress rp rt ruby s section slot \ + source summary template time track u video wbr list -i tag-deprecated \ - acronym applet basefont big blink center dir font frame frameset \ - isindex marquee menu noframes s strike tt u + acronym applet basefont bgsound big blink center dir font frame \ + frameset isindex keygen listing marquee menuitem multicol \ + nextid nobr noembed noframes plaintext rb rtc spacer strike tt \ + xmp default code tag-unknown default error tag-deprecated diff -Nru dte-1.9.1/config/syntax/inc/c-comment dte-1.10/config/syntax/inc/c-comment --- dte-1.9.1/config/syntax/inc/c-comment 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/config/syntax/inc/c-comment 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +syntax .c-comment + +state comment + char '*' star + eat this + +state star comment + char / END comment + noeat comment diff -Nru dte-1.9.1/config/syntax/inc/c-uchar dte-1.10/config/syntax/inc/c-uchar --- dte-1.9.1/config/syntax/inc/c-uchar 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/config/syntax/inc/c-uchar 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,35 @@ +syntax .c-uchar4 + +state 4left + char 0-9a-fA-F 3left special + eat END error + +state 3left + char 0-9a-fA-F 2left special + eat END error + +state 2left + char 0-9a-fA-F 1left special + eat END error + +state 1left + char 0-9a-fA-F END special + eat END error + +syntax .c-uchar8 + +state 8left + char 0-9a-fA-F 7left special + eat END error + +state 7left + char 0-9a-fA-F 6left special + eat END error + +state 6left + char 0-9a-fA-F 5left special + eat END error + +state 5left + char 0-9a-fA-F .c-uchar4:END special + eat END error diff -Nru dte-1.9.1/config/syntax/lisp dte-1.10/config/syntax/lisp --- dte-1.9.1/config/syntax/lisp 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/config/syntax/lisp 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,14 @@ +syntax lisp + +state start code + char ";" comment + char '"' string + eat this + +state comment + char "\n" start + eat this + +state string + char "\"" start string + eat this diff -Nru dte-1.9.1/config/syntax/markdown dte-1.10/config/syntax/markdown --- dte-1.9.1/config/syntax/markdown 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/markdown 2021-04-03 21:08:53.000000000 +0000 @@ -23,7 +23,6 @@ # TODO: All variations of links and references # TODO: Underlined headings -# TODO: Escapes state file-start str -- "---\n" yaml @@ -35,31 +34,46 @@ eat this state line-start - str "\t" code-block - str " " code-block + char "\n" block-start char " " this char # hash-heading char > block-quote char [ maybe-link-def char -b 0-9 digit str "**" double-asterisk + str '__' double-underscore char * bullet-asterisk emphasis + char _ underscore char +- bullet char -b "~" tilde char -b "`" backtick noeat text +state block-start + str " " code-block + char "\t" code-block + char "\n " this + noeat line-start + state text char "\n" line-start + char " \t" space char '`' code-span char [ reference str '**' double-asterisk - str '__' double-underline char * asterisk - char _ underscore char "\\" .markdown-escape:this eat this +# Underscores only begin emphasis if preceded by whitespace. +# This is to avoid mis-highlighting intra-word underscores +# as emphasis (e.g. "x86_64"). +state space + char " \t" this + str '__' double-underscore + char _ underscore + noeat text + state bullet char "\n" line-start char -n " \t" text @@ -73,7 +87,7 @@ noeat text state code-block - char "\n" line-start + char "\n" block-start eat this state backtick code-block @@ -123,7 +137,7 @@ char -n "\n" this noeat line-start -state double-underline strong-emphasis +state double-underscore strong-emphasis char "\n" line-start str '__' text strong-emphasis eat this diff -Nru dte-1.9.1/config/syntax/python dte-1.10/config/syntax/python --- dte-1.9.1/config/syntax/python 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/python 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,14 @@ +require c-uchar + syntax .python-esc -# TODO: \N{name} \uxxxx \Uxxxxxxxx +# TODO: \N{name} state esc special char "abfnrtv'\\\"" END special char 0-7 oct1 char x hex0 + char u .c-uchar4:END special + char U .c-uchar8:END special noeat END state oct1 special diff -Nru dte-1.9.1/config/syntax/scheme dte-1.10/config/syntax/scheme --- dte-1.9.1/config/syntax/scheme 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/config/syntax/scheme 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,14 @@ +syntax scheme + +state start code + char ";" comment + char '"' string + eat this + +state comment + char "\n" start + eat this + +state string + char "\"" start string + eat this diff -Nru dte-1.9.1/config/syntax/sh dte-1.10/config/syntax/sh --- dte-1.9.1/config/syntax/sh 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/sh 2021-04-03 21:08:53.000000000 +0000 @@ -52,13 +52,9 @@ state expression special # FIXME: there can be almost anything in $(( ... )) - char ')' expression-end + str '))' END special eat this -state expression-end special - char ')' END special - eat END error - # Double quoted string. $(cmd) and $((expr) not properly parsed syntax .sh-simple-dq diff -Nru dte-1.9.1/config/syntax/tmux dte-1.10/config/syntax/tmux --- dte-1.9.1/config/syntax/tmux 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/config/syntax/tmux 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,14 @@ +syntax tmux + +state start code + char " \t" this + char # comment + noeat command + +state comment + char "\n" start + eat this + +state command code + char "\n" start + eat this diff -Nru dte-1.9.1/config/syntax/zig dte-1.10/config/syntax/zig --- dte-1.9.1/config/syntax/zig 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/config/syntax/zig 2021-04-03 21:08:53.000000000 +0000 @@ -1,6 +1,6 @@ syntax .zig-esc -# TODO: \xNN \uNNNN \UNNNNNN +# TODO: \xNN \u{...} state esc special char "nrt'\\\"" END special noeat END @@ -35,12 +35,15 @@ noeat start state ident-upper ident - char -b a-z class-name + char -b a-z class-name-start char -b A-Z0-9_ ident noeat start -state class-name +state class-name-start recolor class-name + noeat class-name + +state class-name char a-zA-Z0-9_ this noeat start @@ -58,11 +61,12 @@ eat this list keyword \ - align allowzero and asm async await break cancel catch comptime \ - const continue defer else enum errdefer error export extern fn \ - for if inline linksection nakedcc noalias or orelse packed pub \ - resume return stdcallcc struct suspend switch test threadlocal \ - try union unreachable usingnamespace var volatile while + align allowzero and anyframe anytype asm async await break \ + catch comptime const continue defer else enum errdefer error \ + export extern fn for if inline linksection noalias nosuspend \ + or orelse packed pub resume return struct suspend switch test \ + threadlocal try union unreachable usingnamespace \ + var volatile while list type \ bool f16 f32 f64 f128 void noreturn type anyerror promise \ @@ -74,21 +78,22 @@ null undefined true false list builtin \ - @ArgType @IntType @OpaqueType @TagType @This @Vector \ - @addWithOverflow @alignCast @alignOf @atomicLoad @atomicRmw \ + @Frame @TagType @This @Type @TypeOf @Vector \ + @addWithOverflow @alignCast @alignOf @as @asyncCall \ + @atomicLoad @atomicRmw @atomicStore \ @bitCast @bitOffsetOf @bitReverse @boolToInt @breakpoint \ @byteOffsetOf @byteSwap @bytesToSlice @cDefine @cImport \ - @cInclude @cUndef @ceil @clz @cmpxchgStrong @cmpxchgWeak \ + @cInclude @cUndef @call @ceil @clz @cmpxchgStrong @cmpxchgWeak \ @compileError @compileLog @cos @ctz @divExact @divFloor \ @divTrunc @embedFile @enumToInt @errSetCast @errorName \ @errorReturnTrace @errorToInt @exp @exp2 @export @fabs \ @fence @field @fieldParentPtr @floatCast @floatToInt @floor \ - @frameAddress @handle @hasDecl @import @inlineCall @intCast \ - @intToEnum @intToError @intToFloat @intToPtr @ln @log10 @log2 \ - @memberCount @memberName @memberType @memcpy @memset @mod \ - @mulAdd @mulWithOverflow @newStackCall @noInlineCall @panic \ - @popCount @ptrCast @ptrToInt @rem @returnAddress @round \ + @frame @frameAddress @hasDecl @hasField @import @intCast \ + @intToEnum @intToError @intToFloat @intToPtr @log @log2 @log10 \ + @memcpy @memset @mod @mulAdd @mulWithOverflow @panic \ + @popCount @ptrCast @ptrToInt @reduce @rem @returnAddress @round \ @setAlignStack @setCold @setEvalBranchQuota @setFloatMode \ - @setRuntimeSafety @shlExact @shlWithOverflow @shrExact @sin \ - @sizeOf @sliceToBytes @sqrt @subWithOverflow @tagName @trunc \ - @truncate @typeId @typeInfo @typeName @typeOf + @setRuntimeSafety @shlExact @shlWithOverflow @shrExact \ + @shuffle @splat @sin @src @sizeOf @sliceToBytes @sqrt \ + @subWithOverflow @tagName @trunc @truncate \ + @typeInfo @typeName @unionInit @wasmMemorySize @wasmMemoryGrow diff -Nru dte-1.9.1/debian/changelog dte-1.10/debian/changelog --- dte-1.9.1/debian/changelog 2020-08-01 09:57:14.000000000 +0000 +++ dte-1.10/debian/changelog 2021-05-27 17:28:15.000000000 +0000 @@ -1,3 +1,11 @@ +dte (1.10-1) unstable; urgency=medium + + * New upstream version 1.10 + * Bump debhelper compat and standards version + * Update license file + + -- Mirek Kratochvil Thu, 27 May 2021 19:28:15 +0200 + dte (1.9.1-2) unstable; urgency=medium [ Debian Janitor ] diff -Nru dte-1.9.1/debian/control dte-1.10/debian/control --- dte-1.9.1/debian/control 2020-06-09 07:55:37.000000000 +0000 +++ dte-1.10/debian/control 2021-05-27 07:23:29.000000000 +0000 @@ -2,8 +2,8 @@ Section: editors Priority: optional Maintainer: Mirek Kratochvil -Build-Depends: debhelper-compat (= 12), libncurses-dev -Standards-Version: 4.1.3 +Build-Depends: debhelper-compat (= 13) +Standards-Version: 4.5.1 Homepage: https://craigbarnes.gitlab.io/dte/ Vcs-Browser: https://github.com/exaexa/dte-debian/ Vcs-Git: https://github.com/exaexa/dte-debian.git diff -Nru dte-1.9.1/debian/copyright dte-1.10/debian/copyright --- dte-1.9.1/debian/copyright 2019-09-29 15:39:38.000000000 +0000 +++ dte-1.10/debian/copyright 2021-05-27 17:28:15.000000000 +0000 @@ -3,7 +3,7 @@ Source: https://craigbarnes.gitlab.io/dte/ Files: * -Copyright: 2013-2019 Craig Barnes +Copyright: 2013-2021 Craig Barnes 2008-2015 Timo Hirvonen License: GPL-2 This package is free software; you can redistribute it and/or modify it under @@ -21,35 +21,8 @@ On Debian systems, the complete text of the GNU General Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". -Files: src/editorconfig/ini.* -Copyright: 2019 Craig Barnes - 2009 Brush Technology -License: BSD-3-clause - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - . - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Brush Technology nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - . - THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - Files: debian/* -Copyright: 2019 Mirek Kratochvil +Copyright: 2019-2021 Mirek Kratochvil License: GPL-2+ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff -Nru dte-1.9.1/docs/dte.1 dte-1.10/docs/dte.1 --- dte-1.9.1/docs/dte.1 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/dte.1 2021-04-03 21:08:53.000000000 +0000 @@ -1,279 +1,378 @@ -.TH DTE 1 "November 2017" +.TH DTE 1 "February 2021" .nh .ad l . .SH NAME -. -dte \- A small and flexible text editor -. +dte \- A small, configurable text editor .SH SYNOPSIS -. -.B dte -.RB [ \-hBHKRV ] +\fBdte\fR +[\fB\-HR\fR] [\fB\-c\fR \fIcommand\fR] [\fB\-t\fR \fIctag\fR] [\fB\-r\fR \fIrcfile\fR] -[\fB\-b\fR \fIrcname\fR] -.RI [[+ line ] " file" ]... -. +[[\fB+\fRline] \fIfile\fR]... +.br +\fBdte\fR [\fB\-h\fR|\fB\-B\fR|\fB\-K\fR|\fB\-V\fR|\fB\-b\fR \fIrcname\fR|\fB\-s\fR \fIfile\fR] +.P .SH OPTIONS -. .TP -.BI \-c " command" +\fB\-c\fR \fIcommand\fR Run \fIcommand\fR, after reading the rc file and opening any \fIfile\fR arguments. See \fBdterc\fR(5) for available commands. -. +.PP .TP -.BI \-t " ctag" +\fB\-t\fR \fIctag\fR Jump to source location of \fIctag\fR. Requires \fBtags\fR file generated by \fBctags\fR(1). -. +.PP .TP -.BI \-r " rcfile" +\fB\-r\fR \fIrcfile\fR Read configuration from \fIrcfile\fR instead of \fB~/.dte/rc\fR. -. +.PP .TP -.BI \-s " file" +\fB\-s\fR \fIfile\fR Load \fIfile\fR as a \fBdte\-syntax\fR(5) file and exit. Any errors encountered are printed to \fBstderr\fR(3) and the exit status is set appropriately. -. +.PP .TP -.BI \-b " rcname" +\fB\-b\fR \fIrcname\fR Dump the contents of the built\-in rc or syntax file named \fIrcname\fR and exit. -. +.PP .TP -.B \-B +\fB\-B\fR Print a list of all built\-in config names that can be used with the \fB\-b\fR option and exit. -. +.PP .TP -.B \-H +\fB\-H\fR Don't load history files at startup or save history files on exit (see \fBFILES\fR section below). History features will work as usual but will -be in-memory only and not persisted to the filesystem. -. +be in\-memory only and not persisted to the filesystem. +.PP .TP -.B \-R +\fB\-R\fR Don't read the rc file. -. +.PP .TP -.B \-K +\fB\-K\fR Start in a special mode that continuously reads input and prints the -name and numeric code of each pressed key. -. +symbolic name of each pressed key. +.PP .TP -.B \-h +\fB\-h\fR Display the help summary and exit. -. +.PP .TP -.B \-V +\fB\-V\fR Display the version number and exit. -. -.SH BASIC USAGE -. -Here are some of the default key bindings. \fBM\-x\fR is Alt+x, -\fB^V\fR (or \fBC\-V\fR) is Ctrl+v and \fBS\-left\fR is Shift+left. -. +.PP +.SH KEY BINDINGS +There are 3 editor modes, each having a different set of key bindings. +Normal mode bindings can be customized by using the \fBbind\fR command +(see \fBdterc\fR(5)) or displayed using the \fBshow bind\fR command. +.P +The key bindings listed below are in the same format as accepted by +the \fBbind\fR command. In particular, key combinations are represented +as follows: +.P +\(bu \fBM\-x\fR is Alt+x +.br +\(bu \fBC\-V\fR (or \fB^V\fR) is Ctrl+v +.br +\(bu \fBS\-left\fR is Shift+left +.br +\(bu \fBC\-M\-S\-left\fR is Ctrl+Alt+Shift+left +.br +.P +.SS Normal Mode +Normal mode is the mode the editor starts in. Pressing basic keys +(i.e. without modifiers) simply inserts text into the buffer. There +are also various key combinations bound by default: +.P .TP -.BR S\-up ", " S\-down ", " S\-left ", " S\-right +\fBS\-up\fR, \fBS\-down\fR, \fBS\-left\fR, \fBS\-right\fR Move cursor and select characters -. +.PP .TP -.BR C\-S\-left ", " C\-S\-right +\fBC\-S\-left\fR, \fBC\-S\-right\fR Move cursor and select whole words -. +.PP .TP -.BR C\-S\-up ", " C\-S\-down +\fBC\-S\-up\fR, \fBC\-S\-down\fR Move cursor and select whole lines -. +.PP .TP -.B ^C +\fB^C\fR Copy current line or selection -. +.PP .TP -.B ^X +\fB^X\fR Cut current line or selection -. +.PP .TP -.B ^V +\fB^V\fR Paste -. +.PP .TP -.B ^Z +\fB^Z\fR Undo -. +.PP .TP -.B ^Y +\fB^Y\fR Redo -. +.PP .TP -.B M\-x +\fBM\-x\fR Enter command mode -. +.PP .TP -.B ^F +\fB^F\fR Enter search mode -. +.PP .TP -.B F3 +\fBF3\fR Search next -. +.PP .TP -.B F4 +\fBF4\fR Search previous -. +.PP .TP -.B ^T -Open new tab -. +\fB^T\fR +Open new buffer +.PP .TP -.BI M\- N -Activate \fIN\fRth tab -. +\fBM\-1\fR, \fBM\-2\fR ... \fBM\-9\fR +Switch to buffer 1 (or 2, 3, 4, etc.) +.PP .TP -.B ^W -Close tab -. +\fB^W\fR +Close current buffer +.PP .TP -.B ^S -Save file -. +\fB^S\fR +Save +.PP .TP -.B ^Q +\fB^Q\fR Quit -. -.SH COMMAND MODE -. -Command mode allows you to run various editor commands by using a -language similar to Unix shell. The \fBnext\fR and \fBprev\fR commands -switch to the next/previous file. The \fBopen\fR, \fBsave\fR and -\fBquit\fR commands should be self\-explanatory. For a full list of -available commands, see \fBdterc\fR(5). +.PP +.SS Command Mode +Command mode allows running various editor commands using a language +similar to Unix shell. The \fBnext\fR and \fBprev\fR commands switch +to the next/previous file. The \fBopen\fR, \fBsave\fR and \fBquit\fR +commands should be self\-explanatory. For a full list of available +commands, see \fBdterc\fR(5). +.P +The key bindings for command mode are: .P -The default key bindings for command mode are: -. .TP -.BR up ", " down +\fBup\fR, \fBdown\fR Browse previous command history. -. +.PP .TP -.B tab +\fBtab\fR Auto\-complete current command or argument -. +.PP .TP -.B ^A +\fB^A\fR, \fBhome\fR Go to beginning of command line -. +.PP .TP -.B ^B +\fB^B\fR, \fBleft\fR Move left -. +.PP .TP -.B ^C +\fB^C\fR, \fB^G\fR, \fBEsc\fR Exit command mode -. +.PP .TP -.B ^D +\fB^D\fR, \fBdelete\fR Delete -. +.PP .TP -.B ^E +\fB^E\fR, \fBend\fR Go to end of command line -. +.PP .TP -.B ^F +\fB^F\fR, \fBright\fR Move right -. +.PP .TP -.B ^K +\fB^K\fR, \fBM\-delete\fR Delete to end of command line -. +.PP .TP -.B ^U +\fB^U\fR Delete to beginning of command line -. +.PP .TP -.B ^W +\fB^W\fR, \fBM\-C\-?\fR (Alt+Backspace) Erase word -. -.TP -.B Esc -Exit command mode -. -.SH SEARCH MODE -. -Search mode allows you to enter an extended regular expression to search -in the current buffer. +.PP +.SS Search Mode +Search mode allows entering a regular expression to search in the +current buffer. .P The key bindings for search mode are mostly the same as in command mode, plus these additional keys: -. +.P .TP -.B M\-c +\fBM\-c\fR Toggle case sensitive search option. -. +.PP .TP -.B M\-r +\fBM\-r\fR Reverse search direction. -. +.PP +.TP +\fBEnter\fR +Perform regex search. +.PP +.TP +\fBM\-Enter\fR +Perform plain\-text search (escapes the regex). +.PP .SH ENVIRONMENT -. +The following environment variables are inspected at startup: +.P .TP -.B DTE_HOME +\fBDTE_HOME\fR User configuration directory. Defaults to \fB$HOME/.dte\fR if not set. -. -.TP -.B DTE_FORCE_TERMINFO -Force \fBdte\fR to use \fBterminfo\fR(5) database, even for terminals -with built-in support. Enabled if set. -. +.PP .TP -.B TERM -Identifier used to determine which \fBterminfo\fR(5) entry or built-in -terminal support to use. -. +\fBHOME\fR +User home directory. Used when expanding \fB~/\fR in filenames and also +to determine the default value for \fBDTE_HOME\fR. +.PP +.TP +\fBXDG_RUNTIME_DIR\fR +Directory used to store lock files. Defaults to \fB$DTE_HOME\fR if not set. +.PP +.TP +\fBTERM\fR +Terminal identifier. Used to determine which terminal capabilities are +supported. +.PP +.TP +\fBCOLORTERM\fR +Enables support for 24\-bit terminal colors, if set to \fBtruecolor\fR. +.PP .SH FILES -. .TP -.B $DTE_HOME/rc -Your personal configuration file. See \fBdterc\fR(5) for a full list of +\fB$DTE_HOME/rc\fR +User configuration file. See \fBdterc\fR(5) for a full list of available commands and options or run "dte \-b rc" to see the built\-in, default config. -. +.PP .TP -.B $DTE_HOME/syntax/* -Your personal syntax files. These override the syntax files that come -with the program. See \fBdte\-syntax\fR(5) for more information or run +\fB$DTE_HOME/syntax/*\fR +User syntax files. These override the syntax files that come with +the program. See \fBdte\-syntax\fR(5) for more information or run "dte \-b syntax/dte" for a basic example. -. -.TP -.B $DTE_HOME/file\-locks -Records open files to protect you from accidentally editing files opened -in another process. Used only if the \fBlock\-files\fR option is -enabled. -. +.PP .TP -.B $DTE_HOME/file\-history +\fB$DTE_HOME/file\-history\fR History of edited files and cursor positions. Used only if the \fBfile\-history\fR option is enabled. -. +.PP .TP -.B $DTE_HOME/command\-history +\fB$DTE_HOME/command\-history\fR History of \fBdterc\fR(5) commands used while in command mode. -. +.PP .TP -.B $DTE_HOME/search\-history +\fB$DTE_HOME/search\-history\fR History of search patterns used while in search mode. +.PP +.TP +\fB$XDG_RUNTIME_DIR/dte\-locks\fR +List of files currently open in a dte process (if the \fBlock\-files\fR +option is enabled). +.PP +.SH EXIT STATUS +.TP +\fB0\fR +Program exited normally. +.PP +.TP +\fB64\fR +Command\-line usage error (see "synopsis" above). +.PP +.TP +\fB65\fR +Input data error (e.g. data specified by the \fB\-s\fR option). +.PP +.TP +\fB71\fR +Operating system error. +.PP +.TP +\fB74\fR +Input/output error. +.PP +Note: the above exit codes are set by the editor itself, with values in +accordance with \fBsysexits\fR(3). The exit code may also be set to values +in the range \fB0\fR..\fB125\fR by the \fBquit\fR command. +.P +.SH EXAMPLES +Open \fB/etc/passwd\fR with cursor on line 3: +.P +.IP +.nf +\f[C] +dte\ +3\ /etc/passwd +\f[] +.fi +.PP +Run several commands at startup: +.P +.IP +.nf +\f[C] +dte\ \-c\ 'set\ filetype\ sh;\ insert\ \-m\ "#!/bin/sh\\n"' +\f[] +.fi +.PP +Read a buffer from standard input: +.P +.IP +.nf +\f[C] +echo\ 'Hello,\ World!'\ |\ dte +\f[] +.fi +.PP +Interactively filter a shell pipeline: +.P +.IP +.nf +\f[C] +echo\ 'A\ B\ C\ D\ E\ F'\ |\ tr\ '\ '\ '\\n'\ |\ dte\ |\ tac +\f[] +.fi +.PP +.SH NOTES +It's advised to NOT run shell pipelines with multiple interactive +programs that try to control the terminal. For example: +.P +.IP +.nf +\f[C] +echo\ 'Don't\ run\ this\ example!!'\ |\ dte\ |\ less +\f[] +.fi +.PP +A shell will run these processes in parallel and both \fBdte\fR(1) and \fBless\fR +will then try to control the terminal at the same time; clobbering the +input/output of both. +.P . .SH SEE ALSO -. -.BR dterc (5), -.BR dte\-syntax (5) -. +\fBdterc\fR(5), +\fBdte\-syntax\fR(5) .SH AUTHORS -. -Craig Barnes +Craig Barnes .br -Timo Hirvonen +Timo Hirvonen diff -Nru dte-1.9.1/docs/dte.md dte-1.10/docs/dte.md --- dte-1.9.1/docs/dte.md 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/dte.md 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,309 @@ +--- +title: dte +section: 1 +date: February 2021 +description: A small, configurable text editor +author: [Craig Barnes, Timo Hirvonen] +seealso: ["`dterc`", "`dte-syntax`"] +--- + +# Synopsis + +**dte** +\[**-HR**] +\[**-c** _command_] +\[**-t** _ctag_] +\[**-r** _rcfile_] +\[\[**+**line] _file_]... +**dte** \[**-h**|**-B**|**-K**|**-V**|**-b** _rcname_|**-s** _file_] + +# Options + +`-c` _command_ +: Run _command_, after reading the rc file and opening any _file_ + arguments. See [`dterc`] for available commands. + +`-t` _ctag_ +: Jump to source location of _ctag_. Requires `tags` file generated + by [`ctags`]. + +`-r` _rcfile_ +: Read configuration from _rcfile_ instead of `~/.dte/rc`. + +`-s` _file_ +: Load _file_ as a [`dte-syntax`] file and exit. Any errors + encountered are printed to `stderr` and the exit status is + set appropriately. + +`-b` _rcname_ +: Dump the contents of the built-in rc or syntax file named _rcname_ + and exit. + +`-B` +: Print a list of all built-in config names that can be used with the + `-b` option and exit. + +`-H` +: Don't load history files at startup or save history files on exit (see + `FILES` section below). History features will work as usual but will + be in-memory only and not persisted to the filesystem. + +`-R` +: Don't read the rc file. + +`-K` +: Start in a special mode that continuously reads input and prints the + symbolic name of each pressed key. + +`-h` +: Display the help summary and exit. + +`-V` +: Display the version number and exit. + +# Key Bindings + +There are 3 editor modes, each having a different set of key bindings. +Normal mode bindings can be customized by using the [`bind`] command +(see [`dterc`]) or displayed using the [`show bind`] command. + +The key bindings listed below are in the same format as accepted by +the [`bind`] command. In particular, key combinations are represented +as follows: + +* `M-x` is Alt+x +* `C-V` (or `^V`) is Ctrl+v +* `S-left` is Shift+left +* `C-M-S-left` is Ctrl+Alt+Shift+left + +## Normal Mode + +Normal mode is the mode the editor starts in. Pressing basic keys +(i.e. without modifiers) simply inserts text into the buffer. There +are also various key combinations bound by default: + +`S-up`, `S-down`, `S-left`, `S-right` +: Move cursor and select characters + +`C-S-left`, `C-S-right` +: Move cursor and select whole words + +`C-S-up`, `C-S-down` +: Move cursor and select whole lines + +`^C` +: Copy current line or selection + +`^X` +: Cut current line or selection + +`^V` +: Paste + +`^Z` +: Undo + +`^Y` +: Redo + +`M-x` +: Enter command mode + +`^F` +: Enter search mode + +`F3` +: Search next + +`F4` +: Search previous + +`^T` +: Open new buffer + +`M-1`, `M-2` ... `M-9` +: Switch to buffer 1 (or 2, 3, 4, etc.) + +`^W` +: Close current buffer + +`^S` +: Save + +`^Q` +: Quit + +## Command Mode + +Command mode allows running various editor commands using a language +similar to Unix shell. The [`next`] and [`prev`] commands switch +to the next/previous file. The [`open`], [`save`] and [`quit`] +commands should be self-explanatory. For a full list of available +commands, see [`dterc`]. + +The key bindings for command mode are: + +`up`, `down` +: Browse previous command history. + +`tab` +: Auto-complete current command or argument + +`^A`, `home` +: Go to beginning of command line + +`^B`, `left` +: Move left + +`^C`, `^G`, `Esc` +: Exit command mode + +`^D`, `delete` +: Delete + +`^E`, `end` +: Go to end of command line + +`^F`, `right` +: Move right + +`^K`, `M-delete` +: Delete to end of command line + +`^U` +: Delete to beginning of command line + +`^W`, `M-C-?` (Alt+Backspace) +: Erase word + +## Search Mode + +Search mode allows entering a regular expression to search in the +current buffer. + +The key bindings for search mode are mostly the same as in command mode, +plus these additional keys: + +`M-c` +: Toggle case sensitive search option. + +`M-r` +: Reverse search direction. + +`Enter` +: Perform regex search. + +`M-Enter` +: Perform plain-text search (escapes the regex). + +# Environment + +The following environment variables are inspected at startup: + +`DTE_HOME` +: User configuration directory. Defaults to `$HOME/.dte` if not set. + +`HOME` +: User home directory. Used when expanding `~/` in filenames and also + to determine the default value for `DTE_HOME`. + +`XDG_RUNTIME_DIR` +: Directory used to store lock files. Defaults to `$DTE_HOME` if not set. + +`TERM` +: Terminal identifier. Used to determine which terminal capabilities are + supported. + +`COLORTERM` +: Enables support for 24-bit terminal colors, if set to `truecolor`. + +# Files + +`$DTE_HOME/rc` +: User configuration file. See [`dterc`] for a full list of + available commands and options or run "dte -b rc" to see the built-in, + default config. + +`$DTE_HOME/syntax/*` +: User syntax files. These override the syntax files that come with + the program. See [`dte-syntax`] for more information or run + "dte -b syntax/dte" for a basic example. + +`$DTE_HOME/file-history` +: History of edited files and cursor positions. Used only if the + [`file-history`] option is enabled. + +`$DTE_HOME/command-history` +: History of `dterc` commands used while in command mode. + +`$DTE_HOME/search-history` +: History of search patterns used while in search mode. + +`$XDG_RUNTIME_DIR/dte-locks` +: List of files currently open in a dte process (if the [`lock-files`] + option is enabled). + +# Exit Status + +`0` +: Program exited normally. + +`64` +: Command-line usage error (see "synopsis" above). + +`65` +: Input data error (e.g. data specified by the `-s` option). + +`71` +: Operating system error. + +`74` +: Input/output error. + +Note: the above exit codes are set by the editor itself, with values in +accordance with [`sysexits`]. The exit code may also be set to values +in the range `0`..`125` by the [`quit`] command. + +# Examples + +Open `/etc/passwd` with cursor on line 3: + + dte +3 /etc/passwd + +Run several commands at startup: + + dte -c 'set filetype sh; insert -m "#!/bin/sh\n"' + +Read a buffer from standard input: + + echo 'Hello, World!' | dte + +Interactively filter a shell pipeline: + + echo 'A B C D E F' | tr ' ' '\n' | dte | tac + +# Notes + +It's advised to NOT run shell pipelines with multiple interactive +programs that try to control the terminal. For example: + + echo 'Don't run this example!!' | dte | less + +A shell will run these processes in parallel and both `dte` and `less` +will then try to control the terminal at the same time; clobbering the +input/output of both. + + +[`dterc`]: dterc.html +[`dte-syntax`]: dte-syntax.html +[`bind`]: dterc.html#bind +[`show bind`]: dterc.html#show +[`next`]: dterc.html#next +[`prev`]: dterc.html#prev +[`open`]: dterc.html#open +[`save`]: dterc.html#save +[`quit`]: dterc.html#quit +[`lock-files`]: dterc.html#lock-files +[`file-history`]: dterc.html#file-history +[`ctags`]: https://en.wikipedia.org/wiki/Ctags +[`sysexits`]: https://www.freebsd.org/cgi/man.cgi?query=sysexits diff -Nru dte-1.9.1/docs/dterc.5 dte-1.10/docs/dterc.5 --- dte-1.9.1/docs/dterc.5 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/dterc.5 2021-04-03 21:08:53.000000000 +0000 @@ -1,4 +1,4 @@ -.TH DTERC 5 "March 2019" +.TH DTERC 5 "March 2021" .nh .ad l . @@ -17,24 +17,24 @@ .br \fBset\fR [\fB\-gl\fR] \fIoption\fR [\fIvalue\fR] ... .br - \fBsetenv\fR \fIname\fR \fIvalue\fR + \fBsetenv\fR \fIname\fR [\fIvalue\fR] .br - \fBhi\fR \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... + \fBhi\fR [\fB\-c\fR] \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... .br - \fBft\fR [\fB\-bcfi\fR] \fIfiletype\fR \fIstring\fR... + \fBft\fR [\fB\-b\fR|\fB\-c\fR|\fB\-f\fR|\fB\-i\fR] \fIfiletype\fR \fIstring\fR... .br \fBoption\fR [\fB\-r\fR] \fIfiletype\fR \fIoption\fR \fIvalue\fR... .br - \fBinclude\fR [\fB\-b\fR] \fIfile\fR + \fBinclude\fR [\fB\-bq\fR] \fIfile\fR .br - \fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message]... + \fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message|_]... .br \fBload\-syntax\fR \fIfilename\fR|\fIfiletype\fR .br .P Editor Commands: .br - \fBquit\fR [\fB\-fp\fR] + \fBquit\fR [\fB\-f\fR|\fB\-p\fR] [\fIexitcode\fR] .br \fBsuspend\fR .br @@ -44,39 +44,37 @@ .br \fBsearch\fR [\fB\-Hnprw\fR] [\fIpattern\fR] .br - \fBgit\-open\fR -.br \fBrefresh\fR .br .P Buffer Management Commands: .br - \fBopen\fR [\fB\-g\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... + \fBopen\fR [\fB\-g\fR|\fB\-t\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... .br - \fBsave\fR [\fB\-dfup\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] + \fBsave\fR [\fB\-fp\fR] [\fB\-d\fR|\fB\-u\fR] [\fB\-b\fR|\fB\-B\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] .br - \fBclose\fR [\fB\-fqw\fR] + \fBclose\fR [\fB\-qw\fR] [\fB\-f\fR|\fB\-p\fR] .br \fBnext\fR .br \fBprev\fR .br - \fBview\fR \fIN\fR|last + \fBview\fR \fIN\fR|\fBlast\fR .br - \fBmove\-tab\fR \fIN\fR|left|right + \fBmove\-tab\fR \fIN\fR|\fBleft\fR|\fBright\fR .br .P Window Management Commands: .br - \fBwsplit\fR [\fB\-bhr\fR] [\fIfile\fR]... + \fBwsplit\fR [\fB\-bghnrt\fR] [\fIfilename\fR]... .br - \fBwclose\fR [\fB\-f\fR] + \fBwclose\fR [\fB\-f\fR|\fB\-p\fR] .br \fBwnext\fR .br \fBwprev\fR .br - \fBwresize\fR [\fB\-hv\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] + \fBwresize\fR [\fB\-h\fR|\fB\-v\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] .br \fBwflip\fR .br @@ -89,13 +87,17 @@ .br \fBright\fR [\fB\-c\fR] .br - \fBup\fR [\fB\-cl\fR] + \fBup\fR [\fB\-c\fR|\fB\-l\fR] +.br + \fBdown\fR [\fB\-c\fR|\fB\-l\fR] .br - \fBdown\fR [\fB\-cl\fR] + \fBpgup\fR [\fB\-c\fR|\fB\-l\fR] .br - \fBpgup\fR [\fB\-cl\fR] + \fBpgdown\fR [\fB\-c\fR|\fB\-l\fR] .br - \fBpgdown\fR [\fB\-cl\fR] + \fBblkup\fR [\fB\-c\fR|\fB\-l\fR] +.br + \fBblkdown\fR [\fB\-c\fR|\fB\-l\fR] .br \fBword\-fwd\fR [\fB\-cs\fR] .br @@ -123,11 +125,13 @@ .br \fBcenter\-view\fR .br + \fBmatch\-bracket\fR +.br \fBline\fR \fInumber\fR .br \fBtag\fR [\fB\-r\fR] [\fItag\fR] .br - \fBmsg\fR [\fB\-np\fR] + \fBmsg\fR [\fB\-n\fR|\fB\-p\fR] .br .P Editing Commands: @@ -160,7 +164,9 @@ .br \fBerase\-word\fR [\fB\-s\fR] .br - \fBcase\fR [\fB\-lu\fR] + \fBdelete\-line\fR +.br + \fBcase\fR [\fB\-l\fR|\fB\-u\fR] .br \fBinsert\fR [\fB\-km\fR] \fItext\fR .br @@ -177,11 +183,11 @@ .P External Commands: .br - \fBfilter\fR \fIcommand\fR [\fIparameter\fR]... + \fBfilter\fR [\fB\-l\fR] \fIcommand\fR [\fIparameter\fR]... .br \fBpipe\-from\fR [\fB\-ms\fR] \fIcommand\fR [\fIparameter\fR]... .br - \fBpipe\-to\fR \fIcommand\fR [\fIparameter\fR]... + \fBpipe\-to\fR [\fB\-l\fR] \fIcommand\fR [\fIparameter\fR]... .br \fBrun\fR [\fB\-ps\fR] \fIcommand\fR [\fIparameters\fR]... .br @@ -189,6 +195,10 @@ .br \fBeval\fR \fIcommand\fR [\fIparameter\fR]... .br + \fBexec\-open\fR [\fB\-s\fR] \fIcommand\fR [\fIparameter\fR]... +.br + \fBexec\-tag\fR [\fB\-s\fR] \fIcommand\fR [\fIparameter\fR]... +.br .P Other Commands: .br @@ -198,6 +208,8 @@ .br \fBshow\fR [\fB\-c\fR] \fItype\fR [\fIkey\fR] .br + \fBmacro\fR \fIaction\fR +.br .P Options: .br @@ -218,6 +230,8 @@ .br \fBnewline\fR [unix] .br + \fBselect\-cursor\-char\fR [true] +.br \fBscroll\-margin\fR [0] .br \fBset\-window\-title\fR [false] @@ -226,13 +240,11 @@ .br \fBstatusline\-left\fR [" %f%s%m%r%s%M"] .br - \fBstatusline\-right\fR [" %y,%X %u %E %n %t %p "] -.br - \fBtab\-bar\fR [horizontal] + \fBstatusline\-right\fR [" %y,%X %u %E%s%b%s%n %t %p "] .br - \fBtab\-bar\-max\-components\fR [0] + \fBtab\-bar\fR [true] .br - \fBtab\-bar\-width\fR [25] + \fButf8\-bom\fR [false] .br .P Local options: @@ -321,6 +333,12 @@ The selected text or the word under the cursor. .P .RE +\fB$DTE_HOME\fR +.RS +The user configuration directory. This is either the value of \fB$DTE_HOME\fR +when the editor first started, or the default value (\fB$HOME/.dte\fR). +.P +.RE .SS Single quoted strings Single quoted strings can't contain single quotes or escaped characters. .P @@ -332,8 +350,16 @@ Control characters (same as in C) .PP .TP +\fB\\e\fR +Escape character +.PP +.TP \fB\\\\\fR -Escaped backslash +Backslash +.PP +.TP +\fB\\"\fR +Double quote .PP .TP \fB\\x0a\fR @@ -398,6 +424,8 @@ .br \(bu \fBpgdown\fR .br +\(bu \fBbegin\fR (keypad "5" with Num Lock off) +.br \(bu \fBenter\fR .br \(bu \fBtab\fR @@ -463,12 +491,12 @@ See also: \fBtoggle\fR and \fBoption\fR commands. .P .RE -\fBsetenv\fR \fIname\fR \fIvalue\fR +\fBsetenv\fR \fIname\fR [\fIvalue\fR] .RS -Set environment variable. +Set (or unset) environment variable. .P .RE -\fBhi\fR \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... +\fBhi\fR [\fB\-c\fR] \fIname\fR [\fIfg\-color\fR [\fIbg\-color\fR]] [\fIattribute\fR]... .RS Set highlight color. .P @@ -503,6 +531,8 @@ .br \(bu \fBinactivetab\fR .br +\(bu \fBdialog\fR +.br .P The \fIfg\-color\fR and \fIbg\-color\fR arguments can be one of the following: .P @@ -555,8 +585,8 @@ are grayscale values. .P If the terminal has limited support for rendering colors, the \fIfg\-color\fR -and \fIbg\-color\fR arguments will fall back to the nearest supported color, -which may be less precise than the value specified. +and \fIbg\-color\fR arguments will fall back to the nearest supported color +(unless the \fB\-c\fR flag is used). .P The \fIattribute\fR argument(s) can be any combination of the following: .P @@ -590,8 +620,13 @@ If you don't set fg/bg for the highlight color \fBdefault\fR then terminal's default fg/bg is used. .P +.TP +\fB\-c\fR +Do nothing at all if the terminal can't display \fIfg\-color\fR and/or +\fIbg\-color\fR with full precision +.PP .RE -\fBft\fR [\fB\-bcfi\fR] \fIfiletype\fR \fIstring\fR... +\fBft\fR [\fB\-b\fR|\fB\-c\fR|\fB\-f\fR|\fB\-i\fR] \fIfiletype\fR \fIstring\fR... .RS Add a filetype association. Filetypes are used to determine which syntax highlighter and local options to use when opening files. @@ -604,17 +639,17 @@ .PP .TP \fB\-c\fR -Interpret \fIstring\fR as a regex pattern and match against the +Interpret \fIstring\fR as a \fBregex\fR(7) pattern and match against the contents of the first line of the file .PP .TP \fB\-f\fR -Interpret \fIstring\fR as a regex pattern and match against the +Interpret \fIstring\fR as a \fBregex\fR(7) pattern and match against the full (absolute) filename .PP .TP \fB\-i\fR -Interpret \fIstring\fR as a command interpretter name and match against +Interpret \fIstring\fR as a command interpreter name and match against the Unix shebang line (after removing any path prefix and/or version suffix) .PP @@ -649,11 +684,11 @@ .P .TP \fB\-r\fR -Interpret \fIfiletype\fR argument as a regex pattern instead of a +Interpret \fIfiletype\fR argument as a \fBregex\fR(7) pattern instead of a filetype and match against full filenames .PP .RE -\fBinclude\fR [\fB\-b\fR] \fIfile\fR +\fBinclude\fR [\fB\-bq\fR] \fIfile\fR .RS Read and execute commands from \fIfile\fR. .P @@ -661,18 +696,38 @@ \fB\-b\fR Read built\-in \fIfile\fR instead of reading from the filesystem .PP +.TP +\fB\-q\fR +Don't show an error message if \fIfile\fR doesn't exist +.PP Note: "built\-in files" are config files bundled into the program binary. See the \fB\-B\fR and \fB\-b\fR flags in the \fBdte\fR(1) man page for more information. .P .RE -\fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message]... +\fBerrorfmt\fR [\fB\-i\fR] \fIcompiler\fR \fIregexp\fR [file|line|column|message|_]... .RS +Register a \fBregex\fR(7) pattern, for later use with the \fBcompile\fR command. +.P +When the \fBcompile\fR command is invoked with a specific \fIcompiler\fR name, +the \fIregexp\fR pattern(s) previously registered with that name are used to +parse messages from it's program output. +.P +The \fIregexp\fR pattern should contain as many capture groups as there are +extra arguments. These capture groups are used to parse the file, line, +message, etc. from the output and, if possible, jump to the corresponding +file position. To use parentheses in \fIregexp\fR but ignore the capture, use +\fB_\fR as the extra argument. +.P +Running \fBerrorfmt\fR multiple times with the same \fIcompiler\fR name appends +each \fIregexp\fR to a list. When running \fBcompile\fR, the entries in the +specified list are checked for a match in the same order they were added. +.P +For a basic example of usage, see the output of \fBdte \-b compiler/go\fR. +.P .TP \fB\-i\fR Ignore this error .PP -See \fBcompile\fR and \fBmsg\fR commands for more information. -.P .RE \fBload\-syntax\fR \fIfilename\fR|\fIfiletype\fR .RS @@ -686,10 +741,13 @@ .P .RE .SS Editor Commands -\fBquit\fR [\fB\-fp\fR] +\fBquit\fR [\fB\-f\fR|\fB\-p\fR] [\fIexitcode\fR] .RS Quit the editor. .P +The exit status of the process is set to \fIexitcode\fR, which can be +in the range \fB0\fR..\fB125\fR, or defaults to \fB0\fR if unspecified. +.P .TP \fB\-f\fR Force quit, even if there are unsaved files @@ -742,44 +800,13 @@ Search word under cursor .PP .RE -\fBgit\-open\fR -.RS -Interactive file opener. Lists all files in a git repository. -.P -Same keys work as in command mode, but with these changes: -.P -.TP -\fBup\fR -Move up in file list. -.PP -.TP -\fBdown\fR -Move down in file list. -.PP -.TP -\fBenter\fR -Open file. -.PP -.TP -\fB^O\fR -Open file but don't close git\-open. -.PP -.TP -\fBM\-e\fR -Go to end of file list. -.PP -.TP -\fBM\-t\fR -Go to top of file list. -.PP -.RE \fBrefresh\fR .RS Trigger a full redraw of the screen. .P .RE .SS Buffer Management Commands -\fBopen\fR [\fB\-g\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... +\fBopen\fR [\fB\-g\fR|\fB\-t\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR]... .RS Open file. If \fIfilename\fR is omitted, a new file is opened. .P @@ -789,14 +816,27 @@ .PP .TP \fB\-g\fR -Perform \fBglob\fR(3) expansion on \fIfilename\fR. +Perform \fBglob\fR(7) expansion on \fIfilename\fR. +.PP +.TP +\fB\-t\fR +Mark buffer as "temporary" (always closeable, without warnings for +"unsaved changes") .PP .RE -\fBsave\fR [\fB\-dfup\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] +\fBsave\fR [\fB\-fp\fR] [\fB\-d\fR|\fB\-u\fR] [\fB\-b\fR|\fB\-B\fR] [\fB\-e\fR \fIencoding\fR] [\fIfilename\fR] .RS -Save file. By default line\-endings (LF vs CRLF) are preserved. +Save current buffer. .P .TP +\fB\-b\fR +Write byte order mark (BOM) +.PP +.TP +\fB\-B\fR +Don't write byte order mark +.PP +.TP \fB\-d\fR Save with DOS/CRLF line\-endings .PP @@ -816,14 +856,20 @@ \fB\-e\fR \fIencoding\fR Set file \fIencoding\fR. See \fBiconv \-l\fR for list of supported encodings. .PP +See also: \fBnewline\fR and \fButf8\-bom\fR global options +.P .RE -\fBclose\fR [\fB\-fqw\fR] +\fBclose\fR [\fB\-qw\fR] [\fB\-f\fR|\fB\-p\fR] .RS Close file. .P .TP \fB\-f\fR -Close file even if it hasn't been saved after last modification +Force close file, even if it has unsaved changes +.PP +.TP +\fB\-p\fR +Prompt for confirmation if the file has unsaved changes .PP .TP \fB\-q\fR @@ -844,41 +890,62 @@ Display previous file. .P .RE -\fBview\fR \fIN\fR|last +\fBview\fR \fIN\fR|\fBlast\fR .RS -Display _N_th or last open file. +Display \fIN\fRth or last open file. .P .RE -\fBmove\-tab\fR \fIN\fR|left|right +\fBmove\-tab\fR \fIN\fR|\fBleft\fR|\fBright\fR .RS Move current tab to position \fIN\fR or 1 position left or right. .P .RE .SS Window Management Commands -\fBwsplit\fR [\fB\-bhr\fR] [\fIfile\fR]... +\fBwsplit\fR [\fB\-bghnrt\fR] [\fIfilename\fR]... .RS -Like \fBopen\fR but at first splits current window vertically. +Split the current window. +.P +\fIfilename\fR arguments will be opened in a manner similar to the \fBopen\fR +command. If there are no arguments, the contents of the new window will +be an additional view of the current buffer. .P .TP \fB\-b\fR Add new window before current instead of after. .PP .TP +\fB\-g\fR +Perform \fBglob\fR(7) expansion on \fIfilename\fR. +.PP +.TP \fB\-h\fR Split horizontally instead of vertically. .PP .TP +\fB\-n\fR +Create a new, empty buffer. +.PP +.TP \fB\-r\fR Split root instead of current window. .PP +.TP +\fB\-t\fR +Mark buffer as "temporary" (always closeable, without warnings +for "unsaved changes") +.PP .RE -\fBwclose\fR [\fB\-f\fR] +\fBwclose\fR [\fB\-f\fR|\fB\-p\fR] .RS Close window. .P .TP \fB\-f\fR -Close even if there are unsaved files in the window +Force close window, even if it contains unsaved files +.PP +.TP +\fB\-p\fR +Prompt for confirmation if there are unsaved files in the window .PP .RE \fBwnext\fR @@ -891,7 +958,7 @@ Previous window. .P .RE -\fBwresize\fR [\fB\-hv\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] +\fBwresize\fR [\fB\-h\fR|\fB\-v\fR] [\fIN\fR|+\fIN\fR|\-\- \-\fIN\fR] .RS If no parameter given, equalize window sizes in current frame. .P @@ -929,111 +996,86 @@ .P .RE .SS Movement Commands -\fBleft\fR [\fB\-c\fR] -.RS -Move left. +Movement commands are used to move the cursor position. +.P +Several of these commands also have \fB\-c\fR and \fB\-l\fR flags to allow +creating character/line selections. These 2 flags are noted in the +command summaries below, but are only described once, as follows: .P .TP \fB\-c\fR Select characters .PP +.TP +\fB\-l\fR +Select whole lines +.PP +\fBleft\fR [\fB\-c\fR] +.RS +Move one column left. +.P .RE \fBright\fR [\fB\-c\fR] .RS -Move right. +Move one column right. .P -.TP -\fB\-c\fR -Select characters -.PP .RE -\fBup\fR [\fB\-cl\fR] +\fBup\fR [\fB\-c\fR|\fB\-l\fR] .RS -Move cursor up. +Move one line up. .P -.TP -\fB\-c\fR -Select characters -.PP -.TP -\fB\-l\fR -Select whole lines -.PP .RE -\fBdown\fR [\fB\-cl\fR] +\fBdown\fR [\fB\-c\fR|\fB\-l\fR] .RS -Move cursor down. +Move one line down. .P -.TP -\fB\-c\fR -Select characters -.PP -.TP -\fB\-l\fR -Select whole lines -.PP .RE -\fBpgup\fR [\fB\-cl\fR] +\fBpgup\fR [\fB\-c\fR|\fB\-l\fR] .RS -Move cursor page up. See also \fBscroll\-pgup\fR. +Move one page up. .P -.TP -\fB\-c\fR -Select characters -.PP -.TP -\fB\-l\fR -Select whole lines -.PP .RE -\fBpgdown\fR [\fB\-cl\fR] +\fBpgdown\fR [\fB\-c\fR|\fB\-l\fR] .RS -Move cursor page down. See also \fBscroll\-pgdown\fR. +Move one page down. +.P +.RE +\fBblkup\fR [\fB\-c\fR|\fB\-l\fR] +.RS +Move one block up. +.P +Note: a "block", in this context, is somewhat akin to a paragraph. +Blocks are delimited by one or more blank lines +.P +.RE +\fBblkdown\fR [\fB\-c\fR|\fB\-l\fR] +.RS +Move one block down. .P -.TP -\fB\-c\fR -Select characters -.PP -.TP -\fB\-l\fR -Select whole lines -.PP .RE \fBword\-fwd\fR [\fB\-cs\fR] .RS -Move cursor forward one word. +Move forward one word. .P .TP -\fB\-c\fR -Select characters -.PP -.TP \fB\-s\fR Skip special characters .PP .RE \fBword\-bwd\fR [\fB\-cs\fR] .RS -Move cursor backward one word. +Move backward one word. .P .TP -\fB\-c\fR -Select characters -.PP -.TP \fB\-s\fR Skip special characters .PP .RE \fBbol\fR [\fB\-cs\fR] .RS -Move to beginning of line. +Move to beginning of current line. .P .TP -\fB\-c\fR -Select characters -.PP -.TP \fB\-s\fR Move to beginning of indented text or beginning of line, depending on current cursor position. @@ -1041,12 +1083,8 @@ .RE \fBeol\fR [\fB\-c\fR] .RS -Move cursor to end of line. +Move to end of current line. .P -.TP -\fB\-c\fR -Select characters -.PP .RE \fBbof\fR .RS @@ -1055,19 +1093,18 @@ .RE \fBeof\fR .RS -Move cursor to end of file. +Move to end of file. .P .RE \fBbolsf\fR .RS -Incrementally move cursor to beginning of line, then beginning -of screen, then beginning of file. +Incrementally move to beginning of line, then beginning of screen, then +beginning of file. .P .RE \fBeolsf\fR .RS -Incrementally move cursor to end of line, then end of screen, then -end of file. +Incrementally move to end of line, then end of screen, then end of file. .P .RE \fBscroll\-up\fR @@ -1082,13 +1119,13 @@ .RE \fBscroll\-pgup\fR .RS -Scroll page up. Cursor position relative to top of screen is +Scroll one page up. Cursor position relative to top of screen is maintained. See also \fBpgup\fR. .P .RE \fBscroll\-pgdown\fR .RS -Scroll page down. Cursor position relative to top of screen is +Scroll one page down. Cursor position relative to top of screen is maintained. See also \fBpgdown\fR. .P .RE @@ -1097,6 +1134,12 @@ Center view to cursor. .P .RE +\fBmatch\-bracket\fR +.RS +Move to the bracket character paired with the one under the cursor. +The character under the cursor should be one of \fB{}[]()<>\fR. +.P +.RE \fBline\fR \fInumber\fR .RS Go to line. @@ -1104,21 +1147,21 @@ .RE \fBtag\fR [\fB\-r\fR] [\fItag\fR] .RS -Save current location to stack and go to the location of \fItag\fR. -Requires tags file generated by Exuberant Ctags. If no \fItag\fR is -given then word under cursor is used as a tag instead. +Save the current location and jump to the location of \fItag\fR. If no \fItag\fR +argument is specified then the word under the cursor is used instead. +.P +This command requires a \fBtags\fR file generated by \fBctags\fR(1). \fBtags\fR files +are searched for in the current working directory and its parent +directories. .P .TP \fB\-r\fR -return back to previous location +jump back to the previous location .PP -Tag files are searched from current working directory and its -parent directories. -.P -See also \fBmsg\fR command. +See also: \fBmsg\fR command. .P .RE -\fBmsg\fR [\fB\-np\fR] +\fBmsg\fR [\fB\-n\fR|\fB\-p\fR] .RS Show latest, next (\fB\-n\fR) or previous (\fB\-p\fR) message. If its location is known (compile error or tag message) then the file will be @@ -1132,7 +1175,7 @@ \fB\-p\fR Previous message .PP -See also \fBcompile\fR and \fBtag\fR commands. +See also: \fBcompile\fR and \fBtag\fR commands. .P .RE .SS Editing Commands @@ -1156,7 +1199,9 @@ .P .TP \fB\-c\fR -Paste at the cursor position +Paste at the cursor position, even when the text was copied as +a whole\-line selection (where the usual default is to paste at +the start of the next line) .PP .RE \fBundo\fR @@ -1237,7 +1282,12 @@ Be more "aggressive" .PP .RE -\fBcase\fR [\fB\-lu\fR] +\fBdelete\-line\fR +.RS +Delete current line. +.P +.RE +\fBcase\fR [\fB\-l\fR|\fB\-u\fR] .RS Change text case. The default is to change lower case to upper case and vice versa. @@ -1257,7 +1307,10 @@ .P .TP \fB\-k\fR -Insert one character at a time as if it has been typed +Insert one character at a time, as if manually typed. Normally +\fItext\fR is inserted exactly as specified, but this option allows +it to be affected by special input handling like auto\-indents, +whitespace trimming, line\-by\-line undo, etc. .PP .TP \fB\-m\fR @@ -1269,7 +1322,26 @@ Replace all instances of text matching \fIpattern\fR with the \fIreplacement\fR text. .P -The \fIpattern\fR is a POSIX extended \fBregex\fR(7). +The \fIpattern\fR argument is a POSIX extended \fBregex\fR(7). +.P +The \fIreplacement\fR argument is treated like a template and may contain +several, special substitutions: +.P +\(bu Backslash sequences \fB\\1\fR through \fB\\9\fR are replaced by sub\-strings +that were "captured" (via parentheses) in the \fIpattern\fR. +.br +\(bu The special character \fB&\fR is replaced by the full string that was +matched by \fIpattern\fR. +.br +\(bu Literal \fB\\\fR and \fB&\fR characters can be inserted in \fIreplacement\fR +by escaping them (as \fB\\\\\fR and \fB\\&\fR). +.br +\(bu All other characters in \fIreplacement\fR represent themselves. +.br +.P +Note: extra care must be taken when using double quotes for the +\fIpattern\fR argument, since double quoted arguments have their own +(higher precedence) backslash sequences. .P .TP \fB\-b\fR @@ -1287,6 +1359,17 @@ \fB\-i\fR Ignore case .PP +Examples: +.P +.IP +.nf +\f[C] +replace\ 'Hello\ World'\ '&\ (Hallo\ Welt)' +replace\ "[\ \\t]+$"\ '' +replace\ \-cg\ '([^\ ]+)\ +([^\ ]+)'\ '\\2\ \\1' +\f[] +.fi +.PP .RE \fBshift\fR \fIcount\fR .RS @@ -1336,7 +1419,7 @@ .P .RE .SS External Commands -\fBfilter\fR \fIcommand\fR [\fIparameter\fR]... +\fBfilter\fR [\fB\-l\fR] \fIcommand\fR [\fIparameter\fR]... .RS Filter selected text or whole file through external \fIcommand\fR. .P @@ -1360,6 +1443,10 @@ \f[] .fi .PP +.TP +\fB\-l\fR +Operate on current line instead of whole file, if there's no selection +.PP .RE \fBpipe\-from\fR [\fB\-ms\fR] \fIcommand\fR [\fIparameter\fR]... .RS @@ -1374,7 +1461,7 @@ Strip newline from end of output .PP .RE -\fBpipe\-to\fR \fIcommand\fR [\fIparameter\fR]... +\fBpipe\-to\fR [\fB\-l\fR] \fIcommand\fR [\fIparameter\fR]... .RS Run external \fIcommand\fR and pipe the selected text (or whole file) to its standard input. @@ -1388,6 +1475,10 @@ \f[] .fi .PP +.TP +\fB\-l\fR +Operate on current line instead of whole file, if there's no selection +.PP .RE \fBrun\fR [\fB\-ps\fR] \fIcommand\fR [\fIparameters\fR]... .RS @@ -1399,7 +1490,7 @@ .PP .TP \fB\-s\fR -Silent \-\- both \fBstderr\fR and \fBstdout\fR are redirected to \fB/dev/null\fR +Silent \-\- both \fBstderr\fR(3) and \fBstdout\fR(3) are redirected to \fB/dev/null\fR .PP .RE \fBcompile\fR [\fB\-1ps\fR] \fIerrorfmt\fR \fIcommand\fR [\fIparameters\fR]... @@ -1423,7 +1514,7 @@ .PP .TP \fB\-s\fR -Silent. Both \fBstderr\fR and \fBstdout\fR are redirected to \fB/dev/null\fR +Silent. Both \fBstderr\fR(3) and \fBstdout\fR(3) are redirected to \fB/dev/null\fR .PP See also: \fBerrorfmt\fR and \fBmsg\fR commands. .P @@ -1434,6 +1525,47 @@ commands. .P .RE +\fBexec\-open\fR [\fB\-s\fR] \fIcommand\fR [\fIparameter\fR]... +.RS +Run external \fIcommand\fR and open all filenames listed on its standard +output. +.P +.TP +\fB\-s\fR +Don't yield terminal control to the child process +.PP +Example uses: +.P +.IP +.nf +\f[C] +exec\-open\ \-s\ find\ .\ \-type\ f\ \-name\ *.h +exec\-open\ \-s\ git\ ls\-files\ \-\-modified +exec\-open\ fzf\ \-m\ \-\-reverse +\f[] +.fi +.PP +.RE +\fBexec\-tag\fR [\fB\-s\fR] \fIcommand\fR [\fIparameter\fR]... +.RS +Run external \fIcommand\fR and then execute the \fBtag\fR command with its +first line of standard output as the argument. +.P +.TP +\fB\-s\fR +Don't yield terminal control to the child process +.PP +Example uses: +.P +.IP +.nf +\f[C] +exec\-tag\ \-s\ echo\ main +exec\-tag\ sh\ \-c\ 'readtags\ \-l\ |\ cut\ \-f1\ |\ sort\ |\ uniq\ |\ fzf\ \-\-reverse' +\f[] +.fi +.PP +.RE .SS Other Commands \fBrepeat\fR \fIcount\fR \fIcommand\fR [\fIparameters\fR]... .RS @@ -1465,22 +1597,91 @@ .P .TP \fBalias\fR -show command aliases +Show command aliases .PP .TP \fBbind\fR -show key bindings +Show key bindings +.PP +.TP +\fBcolor\fR +Show highlight colors +.PP +.TP +\fBcommand\fR +Show command history +.PP +.TP +\fBenv\fR +Show environment variables +.PP +.TP +\fBerrorfmt\fR +Show compiler error formats +.PP +.TP +\fBft\fR +Show filetype associations .PP -The \fIkey\fR argument is the name of the entry to lookup (i.e. alias -name or key string). If this argument is specified, the value will -be displayed in the status line. If omitted, a pager will be opened -displaying all entries of the specified type. +.TP +\fBinclude\fR +Show built\-in configs +.PP +.TP +\fBmacro\fR +Show last recorded macro +.PP +.TP +\fBoption\fR +Show option values +.PP +.TP +\fBsearch\fR +Show search history +.PP +.TP +\fBwsplit\fR +Show window dimensions +.PP +The \fIkey\fR argument is the name of the entry to look up (e.g. the alias +name). If this argument is omitted, the full list of entries of the +specified \fItype\fR will be displayed in a new buffer. .P .TP \fB\-c\fR -write value to command line instead of status line +write value to command line (if possible) .PP .RE +\fBmacro\fR \fIaction\fR +.RS +Record and replay command macros. +.P +The \fIaction\fR argument can be one of: +.P +.TP +\fBrecord\fR +Begin recording +.PP +.TP +\fBstop\fR +Stop recording +.PP +.TP +\fBtoggle\fR +Toggle recording on/off +.PP +.TP +\fBcancel\fR +Stop recording, without overwriting the previous macro +.PP +.TP +\fBplay\fR +Replay the previously recorded macro +.PP +Once a macro has been recorded, it can be viewed in text form +by running \fBshow macro\fR. +.P +.RE .SH OPTIONS Options can be changed using the \fBset\fR command. Enumerated options can also be \fBtoggle\fRd. To see which options are enumerated, type "toggle " @@ -1534,14 +1735,24 @@ .RE \fBlock\-files\fR [true] .RS -Lock files using \fB$DTE_HOME/file\-locks\fR. Only protects from your -own mistakes (two processes editing same file). +Keep a record of open files, so that a warning can be shown if the +same file is accidentally opened in multiple dte processes. +.P +See also: the \fBFILES\fR section in the \fBdte\fR(1) man page. .P .RE \fBnewline\fR [unix] .RS -Whether to use LF (\fIunix\fR) or CRLF (\fIdos\fR) line\-endings. This is -just a default value for new files. +Whether to use LF (\fBunix\fR) or CRLF (\fBdos\fR) line\-endings in newly +created files. +.P +Note: buffers opened from existing files will have their newline +type detected automatically. +.P +.RE +\fBselect\-cursor\-char\fR [true] +.RS +Whether to include the character under the cursor in selections. .P .RE \fBscroll\-margin\fR [0] @@ -1574,7 +1785,7 @@ .PP .TP \fB%r\fR -Prints \fBRO\fR if file is read\-only. +Prints \fBRO\fR for read\-only buffers or \fBTMP\fR for temporary buffers. .PP .TP \fB%y\fR @@ -1607,11 +1818,20 @@ .PP .TP \fB%n\fR -Line\-ending (LF or CRLF). +Line\-ending (\fBLF\fR or \fBCRLF\fR). +.PP +.TP +\fB%N\fR +Line\-ending (only if \fBCRLF\fR). .PP .TP \fB%s\fR -Add separator. +Separator (a single space, unless the preceding format character +expanded to an empty string). +.PP +.TP +\fB%S\fR +Like \fB%s\fR, but 3 spaces instead of 1. .PP .TP \fB%t\fR @@ -1626,42 +1846,24 @@ Literal \fB%\fR. .PP .RE -\fBstatusline\-right\fR [" %y,%X %u %E %n %t %p "] +\fBstatusline\-right\fR [" %y,%X %u %E%s%b%s%n %t %p "] .RS Format string for the right aligned part of status line. .P .RE -\fBtab\-bar\fR [horizontal] +\fBtab\-bar\fR [true] .RS -.TP -\fBhidden\fR -Hide tab bar. -.PP -.TP -\fBhorizontal\fR -Show tab bar on top. -.PP -.TP -\fBvertical\fR -Show tab bar on left if there's enough space, hide otherwise. -.PP -.TP -\fBauto\fR -Show tab bar on left if there's enough space, on top otherwise. -.PP -.RE -\fBtab\-bar\-max\-components\fR [0] -.RS -Maximum number of path components displayed in vertical tab bar. -Set to \fB0\fR to disable. +Whether to show the tab\-bar at the top of each window. .P .RE -\fBtab\-bar\-width\fR [25] +\fButf8\-bom\fR [false] .RS -Width of vertical tab bar. Note that width of tab bar is -automatically reduced to keep editing area at least 80 -characters wide. Vertical tab bar is shown only if there's -enough space. +Whether to write a byte order mark (BOM) in newly created UTF\-8 +files. +.P +Note: buffers opened from existing UTF\-8 files will have their BOM +(or lack thereof) preserved as it was, unless overridden by the +\fBsave\fR command. .P .RE .SS Local options @@ -1679,7 +1881,7 @@ .RE \fBindent\-regex\fR [""] .RS -If this regular expression matches current line when enter is +If this \fBregex\fR(7) pattern matches the current line when enter is pressed and \fBauto\-indent\fR is true then indentation is increased. Set to \fB""\fR to disable. .P @@ -1728,7 +1930,9 @@ .RE \fBfile\-history\fR [true] .RS -Save line and column for each file to \fB$DTE_HOME/file\-history\fR. +Save and restore cursor positions for previously opened files. +.P +See also: the \fBFILES\fR section in the \fBdte\fR(1) man page. .P .RE \fBindent\-width\fR [8] diff -Nru dte-1.9.1/docs/dterc.md dte-1.10/docs/dterc.md --- dte-1.9.1/docs/dterc.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/dterc.md 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,8 @@ --- title: dterc section: 5 -date: March 2019 -description: Command and configuration language used by `dte` +date: March 2021 +description: Command and configuration language used by dte author: [Craig Barnes, Timo Hirvonen] seealso: ["`dte`", "`dte-syntax`"] --- @@ -45,7 +45,7 @@ ### **$FILETYPE** -The value of the `filetype` option for the current buffer. +The value of the [`filetype`] option for the current buffer. ### **$LINENO** @@ -55,6 +55,11 @@ The selected text or the word under the cursor. +### **$DTE_HOME** + +The user configuration directory. This is either the value of [`$DTE_HOME`] +when the editor first started, or the default value (`$HOME/.dte`). + ## Single quoted strings Single quoted strings can't contain single quotes or escaped characters. @@ -66,8 +71,14 @@ `\a`, `\b`, `\t`, `\n`, `\v`, `\f`, `\r` : Control characters (same as in C) +`\e` +: Escape character + `\\` -: Escaped backslash +: Backslash + +`\"` +: Double quote `\x0a` : Hexadecimal byte value 0x0a. Note that `\x00` is not supported @@ -115,6 +126,7 @@ * `end` * `pgup` * `pgdown` +* `begin` (keypad "5" with Num Lock off) * `enter` * `tab` * `space` @@ -160,13 +172,13 @@ In configuration files only global options can be set (no need to specify the `-g` flag). -See also: `toggle` and `option` commands. +See also: [`toggle`] and [`option`] commands. -### **setenv** _name_ _value_ +### **setenv** _name_ [_value_] -Set environment variable. +Set (or unset) environment variable. -### **hi** _name_ [_fg-color_ [_bg-color_]] [_attribute_]... +### **hi** [**-c**] _name_ [_fg-color_ [_bg-color_]] [_attribute_]... Set highlight color. @@ -187,6 +199,7 @@ * `tabbar` * `activetab` * `inactivetab` +* `dialog` The _fg-color_ and _bg-color_ arguments can be one of the following: @@ -217,8 +230,8 @@ are grayscale values. If the terminal has limited support for rendering colors, the _fg-color_ -and _bg-color_ arguments will fall back to the nearest supported color, -which may be less precise than the value specified. +and _bg-color_ arguments will fall back to the nearest supported color +(unless the `-c` flag is used). The _attribute_ argument(s) can be any combination of the following: @@ -243,7 +256,11 @@ If you don't set fg/bg for the highlight color `default` then terminal's default fg/bg is used. -### **ft** [**-bcfi**] _filetype_ _string_... +`-c` +: Do nothing at all if the terminal can't display _fg-color_ and/or + _bg-color_ with full precision + +### **ft** [**-b**|**-c**|**-f**|**-i**] _filetype_ _string_... Add a filetype association. Filetypes are used to determine which syntax highlighter and local options to use when opening files. @@ -254,15 +271,15 @@ : Interpret _string_ as a file basename `-c` -: Interpret _string_ as a regex pattern and match against the +: Interpret _string_ as a [`regex`] pattern and match against the contents of the first line of the file `-f` -: Interpret _string_ as a regex pattern and match against the +: Interpret _string_ as a [`regex`] pattern and match against the full (absolute) filename `-i` -: Interpret _string_ as a command interpretter name and match against +: Interpret _string_ as a command interpreter name and match against the Unix shebang line (after removing any path prefix and/or version suffix) @@ -276,53 +293,75 @@ See also: -* The `option` command (below) -* The `filetype` option (below) +* The [`option`] command (below) +* The [`filetype`] option (below) * The [`dte-syntax`] man page ### **option** [**-r**] _filetype_ _option_ _value_... Add automatic _option_ for _filetype_ (as previously registered -with the `ft` command). Automatic options are set when files are +with the [`ft`] command). Automatic options are set when files are are opened. `-r` -: Interpret _filetype_ argument as a regex pattern instead of a +: Interpret _filetype_ argument as a [`regex`] pattern instead of a filetype and match against full filenames -### **include** [**-b**] _file_ +### **include** [**-bq**] _file_ Read and execute commands from _file_. `-b` : Read built-in _file_ instead of reading from the filesystem +`-q` +: Don't show an error message if _file_ doesn't exist + Note: "built-in files" are config files bundled into the program binary. -See the `-B` and `-b` flags in the `dte` man page for more information. +See the `-B` and `-b` flags in the [`dte`] man page for more information. + +### **errorfmt** [**-i**] _compiler_ _regexp_ [file|line|column|message|_]... -### **errorfmt** [**-i**] _compiler_ _regexp_ [file|line|column|message]... +Register a [`regex`] pattern, for later use with the [`compile`] command. + +When the `compile` command is invoked with a specific _compiler_ name, +the _regexp_ pattern(s) previously registered with that name are used to +parse messages from it's program output. + +The _regexp_ pattern should contain as many capture groups as there are +extra arguments. These capture groups are used to parse the file, line, +message, etc. from the output and, if possible, jump to the corresponding +file position. To use parentheses in _regexp_ but ignore the capture, use +`_` as the extra argument. + +Running `errorfmt` multiple times with the same _compiler_ name appends +each _regexp_ to a list. When running `compile`, the entries in the +specified list are checked for a match in the same order they were added. + +For a basic example of usage, see the output of `dte -b compiler/go`. `-i` : Ignore this error -See `compile` and `msg` commands for more information. - ### **load-syntax** _filename_|_filetype_ -Load a `dte-syntax` file into the editor. If the argument contains a +Load a [`dte-syntax`] file into the editor. If the argument contains a `/` character it's considered a filename. Note: this command only loads a syntax file ready for later use. To actually apply a syntax highlighter to the current buffer, use the -`set` command to change the `filetype` of the buffer instead, e.g. +[`set`] command to change the [`filetype`] of the buffer instead, e.g. `set filetype html`. ## Editor Commands -### **quit** [**-fp**] +### **quit** [**-f**|**-p**] [_exitcode_] Quit the editor. +The exit status of the process is set to _exitcode_, which can be +in the range `0`..`125`, or defaults to `0` if unspecified. + `-f` : Force quit, even if there are unsaved files @@ -363,37 +402,13 @@ `-w` : Search word under cursor -### **git-open** - -Interactive file opener. Lists all files in a git repository. - -Same keys work as in command mode, but with these changes: - -`up` -: Move up in file list. - -`down` -: Move down in file list. - -`enter` -: Open file. - -`^O` -: Open file but don't close git-open. - -`M-e` -: Go to end of file list. - -`M-t` -: Go to top of file list. - ### **refresh** Trigger a full redraw of the screen. ## Buffer Management Commands -### **open** [**-g**] [**-e** _encoding_] [_filename_]... +### **open** [**-g**|**-t**] [**-e** _encoding_] [_filename_]... Open file. If _filename_ is omitted, a new file is opened. @@ -403,9 +418,19 @@ `-g` : Perform [`glob`] expansion on _filename_. -### **save** [**-dfup**] [**-e** _encoding_] [_filename_] +`-t` +: Mark buffer as "temporary" (always closeable, without warnings for + "unsaved changes") -Save file. By default line-endings (LF vs CRLF) are preserved. +### **save** [**-fp**] [**-d**|**-u**] [**-b**|**-B**] [**-e** _encoding_] [_filename_] + +Save current buffer. + +`-b` +: Write byte order mark (BOM) + +`-B` +: Don't write byte order mark `-d` : Save with DOS/CRLF line-endings @@ -422,12 +447,17 @@ `-e` _encoding_ : Set file _encoding_. See `iconv -l` for list of supported encodings. -### **close** [**-fqw**] +See also: [`newline`] and [`utf8-bom`] global options + +### **close** [**-qw**] [**-f**|**-p**] Close file. `-f` -: Close file even if it hasn't been saved after last modification +: Force close file, even if it has unsaved changes + +`-p` +: Prompt for confirmation if the file has unsaved changes `-q` : Quit if closing the last open file @@ -443,35 +473,52 @@ Display previous file. -### **view** _N_|last +### **view** _N_|**last** -Display _N_th or last open file. +Display *N*th or last open file. -### **move-tab** _N_|left|right +### **move-tab** _N_|**left**|**right** Move current tab to position _N_ or 1 position left or right. ## Window Management Commands -### **wsplit** [**-bhr**] [_file_]... +### **wsplit** [**-bghnrt**] [_filename_]... + +Split the current window. -Like `open` but at first splits current window vertically. +_filename_ arguments will be opened in a manner similar to the [`open`] +command. If there are no arguments, the contents of the new window will +be an additional view of the current buffer. `-b` : Add new window before current instead of after. +`-g` +: Perform [`glob`] expansion on _filename_. + `-h` : Split horizontally instead of vertically. +`-n` +: Create a new, empty buffer. + `-r` : Split root instead of current window. -### **wclose** [**-f**] +`-t` +: Mark buffer as "temporary" (always closeable, without warnings + for "unsaved changes") + +### **wclose** [**-f**|**-p**] Close window. `-f` -: Close even if there are unsaved files in the window +: Force close window, even if it contains unsaved files + +`-p` +: Prompt for confirmation if there are unsaved files in the window ### **wnext** @@ -481,7 +528,7 @@ Previous window. -### **wresize** [**-hv**] [_N_|+_N_|-- -_N_] +### **wresize** [**-h**|**-v**] [_N_|+_N_|-- -_N_] If no parameter given, equalize window sizes in current frame. @@ -512,86 +559,70 @@ ## Movement Commands -### **left** [**-c**] +Movement commands are used to move the cursor position. -Move left. +Several of these commands also have `-c` and `-l` flags to allow +creating character/line selections. These 2 flags are noted in the +command summaries below, but are only described once, as follows: `-c` : Select characters -### **right** [**-c**] - -Move right. +`-l` +: Select whole lines -`-c` -: Select characters +### **left** [**-c**] -### **up** [**-cl**] +Move one column left. -Move cursor up. +### **right** [**-c**] -`-c` -: Select characters +Move one column right. -`-l` -: Select whole lines +### **up** [**-c**|**-l**] -### **down** [**-cl**] +Move one line up. -Move cursor down. +### **down** [**-c**|**-l**] -`-c` -: Select characters +Move one line down. -`-l` -: Select whole lines +### **pgup** [**-c**|**-l**] -### **pgup** [**-cl**] +Move one page up. -Move cursor page up. See also `scroll-pgup`. +### **pgdown** [**-c**|**-l**] -`-c` -: Select characters +Move one page down. -`-l` -: Select whole lines +### **blkup** [**-c**|**-l**] -### **pgdown** [**-cl**] +Move one block up. -Move cursor page down. See also `scroll-pgdown`. +Note: a "block", in this context, is somewhat akin to a paragraph. +Blocks are delimited by one or more blank lines -`-c` -: Select characters +### **blkdown** [**-c**|**-l**] -`-l` -: Select whole lines +Move one block down. ### **word-fwd** [**-cs**] -Move cursor forward one word. - -`-c` -: Select characters +Move forward one word. `-s` : Skip special characters ### **word-bwd** [**-cs**] -Move cursor backward one word. - -`-c` -: Select characters +Move backward one word. `-s` : Skip special characters ### **bol** [**-cs**] -Move to beginning of line. - -`-c` -: Select characters +Move to beginning of current line. `-s` : Move to beginning of indented text or beginning of line, depending @@ -599,10 +630,7 @@ ### **eol** [**-c**] -Move cursor to end of line. - -`-c` -: Select characters +Move to end of current line. ### **bof** @@ -610,17 +638,16 @@ ### **eof** -Move cursor to end of file. +Move to end of file. ### **bolsf** -Incrementally move cursor to beginning of line, then beginning -of screen, then beginning of file. +Incrementally move to beginning of line, then beginning of screen, then +beginning of file. ### **eolsf** -Incrementally move cursor to end of line, then end of screen, then -end of file. +Incrementally move to end of line, then end of screen, then end of file. ### **scroll-up** @@ -632,37 +659,42 @@ ### **scroll-pgup** -Scroll page up. Cursor position relative to top of screen is -maintained. See also `pgup`. +Scroll one page up. Cursor position relative to top of screen is +maintained. See also [`pgup`]. ### **scroll-pgdown** -Scroll page down. Cursor position relative to top of screen is -maintained. See also `pgdown`. +Scroll one page down. Cursor position relative to top of screen is +maintained. See also [`pgdown`]. ### **center-view** Center view to cursor. +### **match-bracket** + +Move to the bracket character paired with the one under the cursor. +The character under the cursor should be one of `{}[]()<>`. + ### **line** _number_ Go to line. ### **tag** [**-r**] [_tag_] -Save current location to stack and go to the location of _tag_. -Requires tags file generated by Exuberant Ctags. If no _tag_ is -given then word under cursor is used as a tag instead. +Save the current location and jump to the location of _tag_. If no _tag_ +argument is specified then the word under the cursor is used instead. -`-r` -: return back to previous location +This command requires a `tags` file generated by [`ctags`]. `tags` files +are searched for in the current working directory and its parent +directories. -Tag files are searched from current working directory and its -parent directories. +`-r` +: jump back to the previous location -See also `msg` command. +See also: [`msg`] command. -### **msg** [**-np**] +### **msg** [**-n**|**-p**] Show latest, next (`-n`) or previous (`-p`) message. If its location is known (compile error or tag message) then the file will be @@ -674,7 +706,7 @@ `-p` : Previous message -See also `compile` and `tag` commands. +See also: [`compile`] and [`tag`] commands. ## Editing Commands @@ -691,10 +723,12 @@ ### **paste** [**-c**] -Paste text previously copied by the `copy` or `cut` commands. +Paste text previously copied by the [`copy`] or [`cut`] commands. `-c` -: Paste at the cursor position +: Paste at the cursor position, even when the text was copied as + a whole-line selection (where the usual default is to paste at + the start of the next line) ### **undo** @@ -702,7 +736,7 @@ ### **redo** [_choice_] -Redo changes done by the `undo` command. If there are multiple +Redo changes done by the [`undo`] command. If there are multiple possibilities a message is displayed: Redoing newest (2) of 2 possible changes. @@ -755,7 +789,11 @@ `-s` : Be more "aggressive" -### **case** [**-lu**] +### **delete-line** + +Delete current line. + +### **case** [**-l**|**-u**] Change text case. The default is to change lower case to upper case and vice versa. @@ -771,7 +809,10 @@ Insert _text_ into the buffer. `-k` -: Insert one character at a time as if it has been typed +: Insert one character at a time, as if manually typed. Normally + _text_ is inserted exactly as specified, but this option allows + it to be affected by special input handling like auto-indents, + whitespace trimming, line-by-line undo, etc. `-m` : Move after inserted text @@ -781,7 +822,22 @@ Replace all instances of text matching _pattern_ with the _replacement_ text. -The _pattern_ is a POSIX extended **regex**(7). +The _pattern_ argument is a POSIX extended [`regex`]. + +The _replacement_ argument is treated like a template and may contain +several, special substitutions: + +* Backslash sequences `\1` through `\9` are replaced by sub-strings + that were "captured" (via parentheses) in the _pattern_. +* The special character `&` is replaced by the full string that was + matched by _pattern_. +* Literal `\` and `&` characters can be inserted in _replacement_ + by escaping them (as `\\` and `\&`). +* All other characters in _replacement_ represent themselves. + +Note: extra care must be taken when using [double quotes] for the +_pattern_ argument, since double quoted arguments have their own +(higher precedence) backslash sequences. `-b` : Use basic instead of extended regex syntax @@ -795,6 +851,12 @@ `-i` : Ignore case +Examples: + + replace 'Hello World' '& (Hallo Welt)' + replace "[ \t]+$" '' + replace -cg '([^ ]+) +([^ ]+)' '\2 \1' + ### **shift** _count_ Shift current or selected lines by _count_ indentation levels. @@ -806,12 +868,12 @@ ### **wrap-paragraph** [_width_] Format the current selection or paragraph under the cursor. If -paragraph _width_ is not given then the `text-width` option is +paragraph _width_ is not given then the [`text-width`] option is used. This command merges the selection into one paragraph. To format -multiple paragraphs use the external `fmt`(1) program with the -`filter` command, e.g. `filter fmt -w 60`. +multiple paragraphs use the external `fmt` program with the +[`filter`] command, e.g. `filter fmt -w 60`. ### **select** [**-bkl**] @@ -837,7 +899,7 @@ ## External Commands -### **filter** _command_ [_parameter_]... +### **filter** [**-l**] _command_ [_parameter_]... Filter selected text or whole file through external _command_. @@ -851,6 +913,9 @@ filter sh -c 'tr a-z A-Z | sed s/foo/bar/' +`-l` +: Operate on current line instead of whole file, if there's no selection + ### **pipe-from** [**-ms**] _command_ [_parameter_]... Run external _command_ and insert its standard output. @@ -861,7 +926,7 @@ `-s` : Strip newline from end of output -### **pipe-to** _command_ [_parameter_]... +### **pipe-to** [**-l**] _command_ [_parameter_]... Run external _command_ and pipe the selected text (or whole file) to its standard input. @@ -870,6 +935,9 @@ pipe-to xsel -b +`-l` +: Operate on current line instead of whole file, if there's no selection + ### **run** [**-ps**] _command_ [_parameters_]... Run external _command_. @@ -887,9 +955,9 @@ etc. and then jump to a file/line position for each message. The _errorfmt_ argument corresponds to a regex capture pattern -previously specified by the `errorfmt` command. After _command_ +previously specified by the [`errorfmt`] command. After _command_ exits successfully, parsed messages can be navigated using the -`msg` command. +[`msg`] command. `-1` : Read error messages from stdout instead of stderr @@ -900,13 +968,40 @@ `-s` : Silent. Both `stderr` and `stdout` are redirected to `/dev/null` -See also: `errorfmt` and `msg` commands. +See also: [`errorfmt`] and [`msg`] commands. ### **eval** _command_ [_parameter_]... Run external _command_ and execute its standard output text as dterc commands. +### **exec-open** [**-s**] _command_ [_parameter_]... + +Run external _command_ and open all filenames listed on its standard +output. + +`-s` +: Don't yield terminal control to the child process + +Example uses: + + exec-open -s find . -type f -name *.h + exec-open -s git ls-files --modified + exec-open fzf -m --reverse + +### **exec-tag** [**-s**] _command_ [_parameter_]... + +Run external _command_ and then execute the `tag` command with its +first line of standard output as the argument. + +`-s` +: Don't yield terminal control to the child process + +Example uses: + + exec-tag -s echo main + exec-tag sh -c 'readtags -l | cut -f1 | sort | uniq | fzf --reverse' + ## Other Commands ### **repeat** _count_ _command_ [_parameters_]... @@ -934,24 +1029,77 @@ The _type_ argument can be one of: `alias` -: show command aliases +: Show [command aliases][`alias`] `bind` -: show key bindings +: Show [key bindings][`bind`] + +`color` +: Show [highlight colors][`hi`] + +`command` +: Show [command history][`command`] + +`env` +: Show environment variables + +`errorfmt` +: Show [compiler error formats][`errorfmt`] + +`ft` +: Show [filetype associations][`ft`] + +`include` +: Show [built-in configs][`include`] + +`macro` +: Show last recorded [macro][`macro`] -The _key_ argument is the name of the entry to lookup (i.e. alias -name or key string). If this argument is specified, the value will -be displayed in the status line. If omitted, a pager will be opened -displaying all entries of the specified type. +`option` +: Show [option values](#options) + +`search` +: Show [search history][`search`] + +`wsplit` +: Show [window dimensions][`wsplit`] + +The _key_ argument is the name of the entry to look up (e.g. the alias +name). If this argument is omitted, the full list of entries of the +specified _type_ will be displayed in a new buffer. `-c` -: write value to command line instead of status line +: write value to command line (if possible) + +### **macro** _action_ + +Record and replay command macros. + +The _action_ argument can be one of: + +`record` +: Begin recording + +`stop` +: Stop recording + +`toggle` +: Toggle recording on/off + +`cancel` +: Stop recording, without overwriting the previous macro + +`play` +: Replay the previously recorded macro + +Once a macro has been recorded, it can be viewed in text form +by running [`show macro`]. # Options -Options can be changed using the `set` command. Enumerated options can -also be `toggle`d. To see which options are enumerated, type "toggle " -in command mode and press the tab key. You can also use the `option` +Options can be changed using the [`set`] command. Enumerated options can +also be [`toggle`]d. To see which options are enumerated, type "toggle " +in command mode and press the tab key. You can also use the [`option`] command to set default options for specific file types. ## Global options @@ -994,13 +1142,22 @@ ### **lock-files** [true] -Lock files using `$DTE_HOME/file-locks`. Only protects from your -own mistakes (two processes editing same file). +Keep a record of open files, so that a warning can be shown if the +same file is accidentally opened in multiple dte processes. + +See also: the `FILES` section in the [`dte`] man page. ### **newline** [unix] -Whether to use LF (_unix_) or CRLF (_dos_) line-endings. This is -just a default value for new files. +Whether to use LF (**unix**) or CRLF (**dos**) line-endings in newly +created files. + +Note: buffers opened from existing files will have their newline +type detected automatically. + +### **select-cursor-char** [true] + +Whether to include the character under the cursor in selections. ### **scroll-margin** [0] @@ -1026,7 +1183,7 @@ : Prints `*` if file is has been modified since last save. `%r` -: Prints `RO` if file is read-only. +: Prints `RO` for read-only buffers or `TMP` for temporary buffers. `%y` : Cursor row. @@ -1051,10 +1208,17 @@ : Miscellaneous status information. `%n` -: Line-ending (LF or CRLF). +: Line-ending (`LF` or `CRLF`). + +`%N` +: Line-ending (only if `CRLF`). `%s` -: Add separator. +: Separator (a single space, unless the preceding format character + expanded to an empty string). + +`%S` +: Like `%s`, but 3 spaces instead of 1. `%t` : File type. @@ -1065,52 +1229,39 @@ `%%` : Literal `%`. -### **statusline-right** [" %y,%X %u %E %n %t %p "] +### **statusline-right** [" %y,%X %u %E%s%b%s%n %t %p "] Format string for the right aligned part of status line. -### **tab-bar** [horizontal] - -`hidden` -: Hide tab bar. - -`horizontal` -: Show tab bar on top. - -`vertical` -: Show tab bar on left if there's enough space, hide otherwise. +### **tab-bar** [true] -`auto` -: Show tab bar on left if there's enough space, on top otherwise. - -### **tab-bar-max-components** [0] +Whether to show the tab-bar at the top of each window. -Maximum number of path components displayed in vertical tab bar. -Set to `0` to disable. +### **utf8-bom** [false] -### **tab-bar-width** [25] +Whether to write a byte order mark (BOM) in newly created UTF-8 +files. -Width of vertical tab bar. Note that width of tab bar is -automatically reduced to keep editing area at least 80 -characters wide. Vertical tab bar is shown only if there's -enough space. +Note: buffers opened from existing UTF-8 files will have their BOM +(or lack thereof) preserved as it was, unless overridden by the +[`save`] command. ## Local options ### **brace-indent** [false] Scan for `{` and `}` characters when calculating indentation size. -Depends on the `auto-indent` option. +Depends on the [`auto-indent`] option. ### **filetype** [none] -Type of file. Value must be previously registered using the `ft` +Type of file. Value must be previously registered using the [`ft`] command. ### **indent-regex** [""] -If this regular expression matches current line when enter is -pressed and `auto-indent` is true then indentation is increased. +If this [`regex`] pattern matches the current line when enter is +pressed and [`auto-indent`] is true then indentation is increased. Set to `""` to disable. ## Local and global options @@ -1122,7 +1273,7 @@ Automatically insert indentation when pressing enter. Indentation is copied from previous non-empty line. If also the -`indent-regex` local option is set then indentation is +[`indent-regex`] local option is set then indentation is automatically increased if the regular expression matches current line. @@ -1131,8 +1282,8 @@ Comma-separated list of indent widths (`1`-`8`) to detect automatically when a file is opened. Set to `""` to disable. Tab indentation is detected if the value is not `""`. Adjusts the following options if -indentation style is detected: `emulate-tab`, `expand-tab`, -`indent-width`. +indentation style is detected: [`emulate-tab`], [`expand-tab`], +[`indent-width`]. Example: @@ -1140,7 +1291,7 @@ ### **emulate-tab** [false] -Make `delete`, `erase` and moving `left` and `right` inside +Make [`delete`], [`erase`] and moving [`left`] and [`right`] inside indentation feel as if there were tabs instead of spaces. ### **expand-tab** [false] @@ -1149,7 +1300,9 @@ ### **file-history** [true] -Save line and column for each file to `$DTE_HOME/file-history`. +Save and restore cursor positions for previously opened files. + +See also: the `FILES` section in the [`dte`] man page. ### **indent-width** [8] @@ -1167,7 +1320,7 @@ ### **text-width** [72] Preferred width of text. Used as the default argument for the -`wrap-paragraph` command. +[`wrap-paragraph`] command. ### **ws-error** [special] @@ -1175,7 +1328,7 @@ errors should be highlighted. Set to `""` to disable. `auto-indent` -: If the `expand-tab` option is enabled then this is the +: If the [`expand-tab`] option is enabled then this is the same as `tab-after-indent,tab-indent`. Otherwise it's the same as `space-indent`. @@ -1185,7 +1338,7 @@ `space-indent` : Highlight space indents as errors. Note that this still allows - using less than `tab-width` spaces at the end of indentation + using less than [`tab-width`] spaces at the end of indentation for alignment. `tab-after-indent` @@ -1205,7 +1358,57 @@ errors. +[`dte`]: dte.html [`dte-syntax`]: dte-syntax.html +[`$DTE_HOME`]: dte.html#environment [`execvp`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html [`glob`]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/glob.html +[`regex`]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_04 [`xterm`]: https://invisible-island.net/xterm/ +[`ctags`]: https://ctags.io/ +[double quotes]: #double-quoted-strings + +[`alias`]: #alias +[`bind`]: #bind +[`command`]: #command +[`compile`]: #compile +[`copy`]: #copy +[`cut`]: #cut +[`delete`]: #delete +[`erase`]: #erase +[`errorfmt`]: #errorfmt +[`filetype`]: #filetype +[`filter`]: #filter +[`ft`]: #ft +[`hi`]: #hi +[`include`]: #include +[`left`]: #left +[`macro`]: #macro +[`message`]: #message +[`msg`]: #msg +[`open`]: #open +[`option`]: #option +[`pgdown`]: #pgdown +[`pgup`]: #pgup +[`right`]: #right +[`save`]: #save +[`scroll-pgdown`]: #scroll-pgdown +[`scroll-pgup`]: #scroll-pgup +[`search`]: #search +[`set`]: #set +[`show macro`]: #show +[`tag`]: #tag +[`toggle`]: #toggle +[`undo`]: #undo +[`wrap-paragraph`]: #wrap-paragraph +[`wsplit`]: #wsplit + +[`auto-indent`]: #auto-indent +[`emulate-tab`]: #emulate-tab +[`expand-tab`]: #expand-tab +[`indent-regex`]: #indent-regex +[`indent-width`]: #indent-width +[`newline`]: #newline +[`tab-width`]: #tab-width +[`text-width`]: #text-width +[`utf8-bom`]: #utf8-bom diff -Nru dte-1.9.1/docs/dte-syntax.5 dte-1.10/docs/dte-syntax.5 --- dte-1.9.1/docs/dte-syntax.5 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/dte-syntax.5 2021-04-03 21:08:53.000000000 +0000 @@ -1,4 +1,4 @@ -.TH DTE\-SYNTAX 5 "November 2017" +.TH DTE\-SYNTAX 5 "May 2020" .nh .ad l . diff -Nru dte-1.9.1/docs/dte-syntax.md dte-1.10/docs/dte-syntax.md --- dte-1.9.1/docs/dte-syntax.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/dte-syntax.md 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,8 @@ --- title: dte-syntax section: 5 -date: November 2017 -description: Format of syntax highlighting files used by `dte` +date: May 2020 +description: Format of syntax highlighting files used by dte author: [Craig Barnes, Timo Hirvonen] seealso: ["`dte`", "`dterc`"] --- @@ -58,7 +58,7 @@ list keyword if else for while do continue switch case -Use the `inlist` command to test if a buffered string is in a list. +Use the [`inlist`] command to test if a buffered string is in a list. `-i` : Make list case-insensitive. @@ -191,3 +191,4 @@ [`dterc`]: https://craigbarnes.gitlab.io/dte/dterc.html [built-in syntax files]: https://gitlab.com/craigbarnes/dte/tree/master/config/syntax +[`inlist`]: #inlist Binary files /tmp/tmpqh34gryi/VtjwrJWayv/dte-1.9.1/docs/favicon.ico and /tmp/tmpqh34gryi/Ly0NHN9mTd/dte-1.10/docs/favicon.ico differ diff -Nru dte-1.9.1/docs/fix-anchors.lua dte-1.10/docs/fix-anchors.lua --- dte-1.9.1/docs/fix-anchors.lua 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/fix-anchors.lua 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,37 @@ +-- This Pandoc filter modifies headings, so that the link anchors +-- for dterc commands don't include trailing junk like flags and +-- parameters. For example "include--bq-file" becomes "include". + +local function get_canonical_id(header) + return header.content[1].content[1].text +end + +local section + +local function Header(header) + local level = assert(header.level) + local id = header.attr.identifier + + if level == 2 then + section = assert(id) + return header + end + + if level == 3 and section == "special-variables" then + header.attr.identifier = id:upper() + return header + end + + if level == 3 and id:find("%-") then + local ok, new_id = pcall(get_canonical_id, header) + if ok and #new_id < #id and new_id == id:sub(1, #new_id) then + header.attr.identifier = new_id + end + end + + return header +end + +return { + {Header = Header} +} diff -Nru dte-1.9.1/docs/gitlab.md dte-1.10/docs/gitlab.md --- dte-1.9.1/docs/gitlab.md 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/gitlab.md 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +Source Code +----------- + +The primary git repository and issue tracker is hosted at +. + +The project is also on various other code hosting sites, but these are +only secondary mirrors and may occasionally lag behind GitLab by a few +hours. diff -Nru dte-1.9.1/docs/index.sed dte-1.10/docs/index.sed --- dte-1.9.1/docs/index.sed 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/index.sed 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,3 @@ +#!/usr/bin/sed -f + +/^Online documentation is/,/^Public License/d diff -Nru dte-1.9.1/docs/index.yml dte-1.10/docs/index.yml --- dte-1.9.1/docs/index.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/index.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,4 @@ +--- +title: dte +description: A small, configurable text editor +--- diff -Nru dte-1.9.1/docs/keys.md dte-1.10/docs/keys.md --- dte-1.9.1/docs/keys.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/keys.md 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -Default Key Bindings --------------------- - -### Editor - -`Alt`+`x` -: Enter command mode (see [`dterc`] for available commands) - -`Ctrl`+`q` -: Quit editor (use `quit -f` in command mode to quit with unsaved buffers) - -`Alt`+`z` -: Suspend editor (resume by running `fg` in the shell) - -### Buffer - -`Ctrl`+`n`, `Ctrl`+`t` -: Open new, empty buffer - -`Ctrl`+`o` -: Open file (prompt) - -`Ctrl`+`s` -: Save current buffer - -`Ctrl`+`w` -: Close current buffer - -`Alt`+`1`, `Alt`+`2` ... `Alt`+`9` -: Switch to buffer 1 (or 2, 3, 4, etc.) - -`Alt`+`0` -: Switch to last buffer - -`Alt`+`,` -: Switch to previous buffer - -`Alt`+`.` -: Switch to next buffer - -### Navigation - -`Alt`+`b` -: Move cursor to beginning of file - -`Alt`+`e` -: Move cursor to end of file - -`Ctrl`+`l` -: Go to line (prompt) - -`Ctrl`+`f`, `Alt`+`/` -: Find text (prompt) - -`Ctrl`+`g`, `F3` -: Find next - -`F4` -: Find previous - -`Alt`+`f` -: Find next occurence of word under cursor - -`Alt`+`Shift`+`f` -: Find previous occurence of word under cursor - -### Selection - -`Shift`+`left`, `Shift`+`right` -: Select one character left/right - -`Ctrl`+`Shift`+`left`, `Ctrl`+`Shift`+`right` -: Select one word left/right - -`Shift`+`up`, `Shift`+`down` -: Select to column above/below cursor - -`Ctrl`+`Shift`+`up`, `Ctrl`+`Shift`+`down` -: Select one whole line up/down - -`Alt`+`Shift`+`left`, `Shift`+`Home` -: Select from cursor to beginning of line - -`Alt`+`Shift`+`right`, `Shift`+`End` -: Select from cursor to end of line - -`Shift`+`Page Up`, `Alt`+`Shift`+`up` -: Select one page above cursor - -`Shift`+`Page Down`, `Alt`+`Shift`+`down` -: Select one page below cursor - -`Ctrl`+`Shift`+`Page Up`, `Ctrl`+`Alt`+`Shift`+`up` -: Select whole lines one page up - -`Ctrl`+`Shift`+`Page Down`, `Ctrl`+`Alt`+`Shift`+`down` -: Select whole lines one page down - -`Tab`, `Shift`+`Tab` -: Increase/decrease indentation level of selected (whole) lines - -### Editing - -`Ctrl`+`c` -: Copy selection (or line) - -`Ctrl`+`x` -: Cut selection (or line) - -`Ctrl`+`v` -: Paste previously copied or cut text - -`Ctrl`+`z` -: Undo last action - -`Ctrl`+`y` -: Redo last undone action - -`Alt`+`j` -: Wrap paragraph under cursor to value of `text-width` option - (72 by default) - -`Alt`+`t` -: Insert a tab character (useful when using the `expand-tab` option) - -`Alt`+`-` -: Decrease indent level of selection (or line) - -`Alt`+`=` -: Increase indent level of selection (or line) - - -[`dterc`]: https://craigbarnes.gitlab.io/dte/dterc.html diff -Nru dte-1.9.1/docs/layout.css dte-1.10/docs/layout.css --- dte-1.9.1/docs/layout.css 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/layout.css 2021-04-03 21:08:53.000000000 +0000 @@ -5,6 +5,9 @@ #main-grid > header { grid-area: header; + display: flex; + flex-wrap: wrap; + justify-content: space-between; padding: 10px; margin-bottom: 1em; border-bottom: 1px solid #ddd; @@ -18,12 +21,11 @@ } #top-title { - float: left; font-size: 1.4em; } #top-nav { - float: right; + line-height: 2.5; user-select: none; } @@ -73,3 +75,9 @@ margin-top: 0; } } + +@media only print { + #main-grid > header, #main-grid > footer, #toc { + display: none; + } +} diff -Nru dte-1.9.1/docs/packaging.md dte-1.10/docs/packaging.md --- dte-1.9.1/docs/packaging.md 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/packaging.md 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,85 @@ +Packaging +========= + +Installation variables +---------------------- + +The following Make variables may be useful when packaging `dte`: + +* `prefix`: Top-level installation prefix (defaults to `/usr/local`). +* `bindir`: Installation prefix for program binary (defaults to + `$prefix/bin`). +* `mandir`: Installation prefix for manual pages (defaults to + `$prefix/share/man`). +* `DESTDIR`: Standard variable used for [staged installs]. +* `V=1`: Enable verbose build output. + +Example usage: + + make V=1 + make install V=1 prefix=/usr DESTDIR=pkg + +Other variables +--------------- + +There are some other variables that may be useful in certain cases +(but typically shouldn't be used for general packaging): + +* `ICONV_DISABLE=1`: Disable support for all file encodings except + UTF-8, to avoid the need to link with the system [iconv] library. + This can significantly reduce the size of statically linked builds. +* `BUILTIN_SYNTAX_FILES`: Specify the [syntax highlighters] to compile + into the editor. The default value for this contributes about 100KiB + to the binary size. + +Example usage: + + make ICONV_DISABLE=1 BUILTIN_SYNTAX_FILES='dte config ini sh' + +Persistent configuration +------------------------ + +The above variables can also be configured persistently by adding them +to a `Config.mk` file, for example: + + prefix = /usr + mandir = $(prefix)/man + V = 1 + +The `Config.mk` file should be in the project base directory alongside +`GNUmakefile` and *must* be valid GNU make syntax. + +Stable release tarballs +----------------------- + +The [releases] page contains a short summary of changes for each +stable version and links to the corresponding source tarballs. + +Note: auto-generated tarballs from GitHub/GitLab can (and +[do][libgit issue #4343]) change over time and cannot be guaranteed to +have long-term stable checksums. Use the tarballs from the [releases] +page, unless you're prepared to deal with future checksum failures. + +Desktop menu entry +------------------ + +A desktop menu entry for `dte` can be added by running: + + make install-desktop-file + +Any variable overrides specified for `make install` must also be specified +for `make install-desktop-file`. The easiest way to do this is simply to +run both at the same time, e.g.: + + make install install-desktop-file V=1 prefix=/usr DESTDIR=PKG + +**Note**: the `install-desktop-file` target requires [desktop-file-utils] +to be installed. + + +[staged installs]: https://www.gnu.org/prep/standards/html_node/DESTDIR.html +[iconv]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/iconv.h.html +[syntax highlighters]: https://gitlab.com/craigbarnes/dte/tree/master/config/syntax +[releases]: https://craigbarnes.gitlab.io/dte/releases.html +[libgit issue #4343]: https://github.com/libgit2/libgit2/issues/4343 +[desktop-file-utils]: https://www.freedesktop.org/wiki/Software/desktop-file-utils/ diff -Nru dte-1.9.1/docs/pdman.lua dte-1.10/docs/pdman.lua --- dte-1.9.1/docs/pdman.lua 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/pdman.lua 2021-04-03 21:08:53.000000000 +0000 @@ -3,12 +3,12 @@ Buffer.__index = Buffer function Buffer:write(...) - local length = self.length + local n = self.n for i = 1, select("#", ...) do - length = length + 1 - self[length] = select(i, ...) + n = n + 1 + self[n] = select(i, ...) end - self.length = length + self.n = n end function Buffer:tostring() @@ -16,7 +16,7 @@ end local function new_buffer() - return setmetatable({length = 0}, Buffer) + return setmetatable({n = 0}, Buffer) end setmetatable(Buffer, {__call = new_buffer}) @@ -34,7 +34,6 @@ . .SH NAME %s \- %s -.SH SYNOPSIS %s%s. .SH SEE ALSO %s @@ -50,7 +49,7 @@ local authors = assert(metadata.author) return template:format ( title:upper(), section, date, - title, description, + title, description:gsub(" dte", " \\fBdte\\fR(1)"), toc:tostring(), body, concat(seealso, ",\n"), concat(authors, "\n.br\n") @@ -73,6 +72,7 @@ if level == 1 then if s == "dterc" or s == "dte\\-syntax" then generate_toc = true + toc:write(".SH SYNOPSIS\n") return ".SH DESCRIPTION\n" end if generate_toc then @@ -139,7 +139,14 @@ dterc = "(5)", ["dte-syntax"] = "(5)", execvp = "(3)", - glob = "(3)" + glob = "(7)", + regex = "(7)", + stdout = "(3)", + stderr = "(3)", + sysexits = "(3)", + ctags = "(1)", + fmt = "(1)", + terminfo = "(5)", } return "\\fB" .. escape(s) .. "\\fR" .. (crossrefs[s] or "") end @@ -190,25 +197,25 @@ end }) ---[[ Not implemented: - -function Subscript(s) -function Superscript(s) -function SmallCaps(s) -function Strikeout(s) -function Image(s, src, tit, attr) -function InlineMath(s) -function DisplayMath(s) -function Note(s) -function Span(s, attr) -function RawInline(format, str) -function Table(caption, aligns, widths, headers, rows) -function BlockQuote(s) -function HorizontalRule() -function LineBlock(ls) -function CaptionedImage(src, tit, caption, attr) -function RawBlock(output_format, str) -function Div(s, attr) -function Cite(s, cs) -function DoubleQuoted(s) +--[[ +Not implemented: +* Subscript(s) +* Superscript(s) +* SmallCaps(s) +* Strikeout(s) +* Image(s, src, title, attr) +* InlineMath(s) +* DisplayMath(s) +* Note(s) +* Span(s, attr) +* RawInline(format, str) +* Table(caption, aligns, widths, headers, rows) +* BlockQuote(s) +* HorizontalRule() +* LineBlock(ls) +* CaptionedImage(src, title, caption, attr) +* RawBlock(output_format, str) +* Div(s, attr) +* Cite(s, cs) +* DoubleQuoted(s) --]] diff -Nru dte-1.9.1/docs/releases.yml dte-1.10/docs/releases.yml --- dte-1.9.1/docs/releases.yml 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/docs/releases.yml 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,4 @@ +--- +title: dte Releases +description: Release downloads and changelog for the dte editor +--- diff -Nru dte-1.9.1/docs/style.css dte-1.10/docs/style.css --- dte-1.9.1/docs/style.css 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/style.css 2021-04-03 21:08:53.000000000 +0000 @@ -34,7 +34,7 @@ } a { - color: #4183C4; + color: #0E68C1; text-decoration: none; transition: color 250ms ease-out; } @@ -137,7 +137,6 @@ main code { margin: 0 2px; padding: 0 5px; - white-space: nowrap; border: 1px solid #eaeaea; background-color: #f8f8f8; border-radius: 3px; @@ -148,13 +147,13 @@ margin: 0; padding: 0; white-space: pre-wrap; + word-break: break-all; background: transparent; } pre { background-color: #f8f8f8; border: 1px solid #cccccc; - overflow: auto; padding: 6px 10px; border-radius: 3px; } @@ -164,6 +163,13 @@ border: none; } +/* Make word-break more aggressive if page is narrow, to avoid scrolling: */ +p > a {word-break: break-all} +@media (min-width: 400px) { + p > a {word-break: normal} + p code {white-space: nowrap} +} + kbd { display: inline-block; border-radius: 3px; diff -Nru dte-1.9.1/docs/template.html dte-1.10/docs/template.html --- dte-1.9.1/docs/template.html 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/docs/template.html 2021-04-03 21:08:53.000000000 +0000 @@ -2,8 +2,15 @@ - dte text editor +$if(section)$ + $title$($section$) +$else$ + $title$ +$endif$ + + + @@ -14,6 +21,7 @@ @@ -25,8 +33,14 @@ $body$
-

© Craig Barnes 2017

+

© Craig Barnes 2017-2021

+ + diff -Nru dte-1.9.1/dte.desktop dte-1.10/dte.desktop --- dte-1.9.1/dte.desktop 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/dte.desktop 2021-04-03 21:08:53.000000000 +0000 @@ -4,10 +4,258 @@ Comment = Edit text files Icon = text-editor Categories = Utility;TextEditor; -Keywords = text;editor; +Keywords = Text;Editor;Plaintext;Write; Type = Application Terminal = true StartupNotify = false TryExec = dte Exec = dte %F MimeType = text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++; + +# Translations (originally from gedit): + +GenericName[af]=Teksredigeerder +GenericName[am]=የጽሑፍ ማቀናጃ +GenericName[an]=Editor de texto +GenericName[ar]=محرّر النصوص +GenericName[as]=লিখনি সম্পাদক +GenericName[ast]=Editor de Testu +GenericName[az]=Mətn Editoru +GenericName[be]=Тэкставы рэдактар +GenericName[be@latin]=Tekstavy redaktar +GenericName[bg]=Текстов редактор +GenericName[bn]=টেক্সট সম্পাদক +GenericName[bn_IN]=টেক্সট এডিটার +GenericName[br]=Embanner testennoù +GenericName[bs]=Uređivač teksta +GenericName[ca]=Editor de text +GenericName[ca@valencia]=Editor de text +GenericName[ckb]=دەستکاریکەری دەق +GenericName[crh]=Metin Muharriri +GenericName[cs]=Textový editor +GenericName[cy]=Golygydd Testun +GenericName[da]=Tekstredigering +GenericName[de]=Texteditor +GenericName[dz]=ཚིག་ཡིག་ཞུན་དགཔ། +GenericName[el]=Επεξεργαστής κειμένου +GenericName[en_CA]=Text Editor +GenericName[en_GB]=Text Editor +GenericName[en@shaw]=𐑑𐑧𐑒𐑕𐑑 𐑧𐑛𐑦𐑑𐑼 +GenericName[eo]=Tekstredaktilo +GenericName[es]=Editor de textos +GenericName[et]=Tekstiredaktor +GenericName[eu]=Testu-editorea +GenericName[fa]=ویرایشگر متن +GenericName[fi]=Tekstimuokkain +GenericName[fr]=Éditeur de texte +GenericName[fur]=Editôr di test +GenericName[ga]=Eagarthóir Téacs +GenericName[gd]=Deasaiche teacsa +GenericName[gl]=Editor de texto +GenericName[gu]=લખાણ સંપાદક +GenericName[he]=עורך טקסט +GenericName[hi]=पाठ संपादक +GenericName[hr]=Uređivač teksta +GenericName[hu]=Szövegszerkesztő +GenericName[hy]=Տեքստի խմբագրիչ +GenericName[id]=Penyunting Teks +GenericName[is]=Textaritill +GenericName[it]=Editor di testo +GenericName[ja]=テキストエディター +GenericName[ka]=ტექსტის რედაქტორი +GenericName[kk]=Мәтін түзетушісі +GenericName[km]=កម្មវិធី​​កែសម្រួល​​អត្ថបទ +GenericName[kn]=ಪಠ್ಯ ಸಂಪಾದಕ +GenericName[ko]=텍스트 편집기 +GenericName[ku]=Edîtora Nivîsê +GenericName[la]=Scripta Edere +GenericName[ln]=mobɔngele nkomá ; editɛ́lɛ +GenericName[lt]=Tekstų redaktorius +GenericName[lv]=Teksta redaktors +GenericName[mai]=पाठ संपादक +GenericName[mg]=Fanovana lahabolana +GenericName[mjw]=Text Editor +GenericName[mk]=Уредувач за текст +GenericName[ml]=എഴുത്തിടം +GenericName[mn]=Текст боловсруулагч +GenericName[mr]=पाठ्य संपादक +GenericName[ms]=Penyunting Teks +GenericName[my]=စာသားအယ်ဒီတာ +GenericName[nb]=Tekstredigering +GenericName[nds]=Textbewarker +GenericName[ne]=पाठ सम्पादक +GenericName[nl]=Teksteditor +GenericName[nn]=Tekstredigering +GenericName[oc]=Editor de tèxte +GenericName[or]=ପାଠ୍ଯ ସମ୍ପାଦକ +GenericName[pa]=ਟੈਕਸਟ ਐਡੀਟਰ +GenericName[pl]=Edytor tekstu +GenericName[ps]=ليک سمونګر +GenericName[pt]=Editor de texto +GenericName[pt_BR]=Editor de texto +GenericName[ro]=Editor de text +GenericName[ru]=Текстовый редактор +GenericName[rw]=Umuhinduzi w'inyandiko +GenericName[si]=පෙළ සකසනය +GenericName[sk]=Textový editor +GenericName[sl]=Urejevalnik besedil +GenericName[sq]=Editues teksti +GenericName[sr]=Уређивач текста +GenericName[sr@latin]=Uređivač teksta +GenericName[sv]=Textredigerare +GenericName[ta]=உரை திருத்தி +GenericName[te]=పాఠ్య కూర్పకము +GenericName[th]=เครื่องมือแก้ไขข้อความ +GenericName[tk]=Metin Editçi +GenericName[tr]=Metin Düzenleyici +GenericName[ug]=تېكىست تەھرىرلىگۈچ +GenericName[uk]=Текстовий редактор +GenericName[vi]=Soạn thảo văn bản +GenericName[wa]=Aspougneu di tecse +GenericName[xh]=Umhleli wombhalo +GenericName[zh_CN]=文本编辑器 +GenericName[zh_HK]=文字編輯器 +GenericName[zh_TW]=文字編輯器 + +Comment[af]=Redigeer tekslêers +Comment[am]=የጽሑፍ ፋይሎች ያስተካክሉ +Comment[an]=Edita fichers de texto +Comment[ar]=حرّر الملفات النصية +Comment[as]=লিখনি ফাইল সম্পাদনা +Comment[ast]=Editar ficheros de testu +Comment[az]=Mətn fayllarını redaktə edin +Comment[be]=Рэдагаванне тэкставых файлаў +Comment[be@latin]=Redahuj tekstavyja fajły +Comment[bg]=Редактиране на текстови файлове +Comment[bn]=টেক্সট ফাইল সম্পাদনা করুন +Comment[bn_IN]=টেক্সট ফাইল সম্পাদনা +Comment[br]=Embann restroù testenn +Comment[bs]=Izmijeni tekstualne datoteke +Comment[ca]=Editeu fitxers de text +Comment[ca@valencia]=Editeu fitxers de text +Comment[ckb]=دەق دەستکاریبکە +Comment[crh]=Metin dosyelerini tahrir et +Comment[cs]=Upravujte textové soubory +Comment[cy]=Golygu ffeiliau testun +Comment[da]=Redigér tekstfiler +Comment[de]=Textdateien bearbeiten +Comment[dz]=ཚིག་ཡིག་ཡིག་སྣོད་ཚུ་ ཞུན་དག་རྐྱབས་ +Comment[el]=Επεξεργασία αρχείων κειμένου +Comment[en_CA]=Edit text files +Comment[en_GB]=Edit text files +Comment[en@shaw]=𐑧𐑛𐑦𐑑 𐑑𐑧𐑒𐑕𐑑 𐑓𐑲𐑤𐑟 +Comment[eo]=Redakti tekstdosierojn +Comment[es]=Edite archivos de texto +Comment[et]=Tekstifailide redigeerimine +Comment[eu]=Editatu testu-fitxategiak +Comment[fa]=ویرایش پرونده‌های متنی +Comment[fi]=Muokkaa tekstitiedostoja +Comment[fr]=Éditer des fichiers texte +Comment[fur]=Modifiche file di test +Comment[ga]=Cuir comhaid téacs in eagar +Comment[gd]=Deasaich faidhlichean teacsa +Comment[gl]=Editar ficheiros de texto +Comment[gu]=લખાણ ફાઇલોમાં ફેરફાર કરો +Comment[he]=עריכת קובצי טקסט +Comment[hi]=पाठ फ़ाइलें संपादित करें +Comment[hr]=Uređivanje tekstovnih datoteka +Comment[hu]=Szövegfájlok szerkesztése +Comment[id]=Menyunting berkas teks +Comment[is]=Sýsla með textaskrár +Comment[it]=Modifica file di testo +Comment[ja]=テキストファイルを編集します +Comment[ka]=ტექსტური ფაილების რედაქტირება +Comment[kk]=Мәтіндік файлдарды түзету +Comment[km]=កែ​សម្រួល​ឯកសារ​អត្ថបទ +Comment[kn]=ಪಠ್ಯ ಕಡತಗಳನ್ನು ಸಂಪಾದಿಸು +Comment[ko]=텍스트 파일을 편집합니다 +Comment[ku]=Pelên nivîsê sererast bike +Comment[ln]=Bɔngisa kásá ya nkomá +Comment[lt]=Redaguoti tekstinius failus +Comment[lv]=Rediģēt teksta datnes +Comment[mai]=पाठ फाइलकेँ संपादित करू +Comment[mg]=Hanova raki-dahabolana +Comment[mk]=Уредувај текстуални датотеки +Comment[ml]=പദാവലി ഫയലുകളില്‍ മാറ്റം വരുത്തുക +Comment[mn]=Текст файл боловсруулах +Comment[mr]=मजकूर फाइल्स् संपादीत करा +Comment[ms]=Sunting fail teks +Comment[my]=စာသားဖိုင်များပြင်ဆင်ရန် +Comment[nb]=Rediger tekstfiler +Comment[nds]=Textdateien bewarken +Comment[ne]=पाठ फाइल सम्पादन गर्नुहोस् +Comment[nl]=Tekstbestanden bewerken +Comment[nn]=Rediger tekstfiler +Comment[oc]=Editar de fichièrs tèxte +Comment[or]=ପାଠ୍ଯ ଫାଇଲଗୁଡ଼ିକୁ ସମ୍ପାଦନ କରନ୍ତୁ +Comment[pa]=ਲਿਖਤ ਫਾਇਲਾਂ ਸੋਧੋ +Comment[pl]=Edytor plików tekstowych +Comment[ps]=ليکن دوتنې سمول +Comment[pt]=Editar ficheiros de texto +Comment[pt_BR]=Edite arquivos de texto +Comment[ro]=Editare fișiere text +Comment[ru]=Редактор текстовых файлов +Comment[si]=පෙළ ගොනු සකසන්න +Comment[sk]=Úprava textových súborov +Comment[sl]=Enostavni urejevalnik besedilnih datotek +Comment[sq]=Ndrysho file teksti +Comment[sr]=Уређујте текстуалне документе +Comment[sr@latin]=Uređujte tekstualne dokumente +Comment[sv]=Redigera textfiler +Comment[ta]=உரை கோப்புகளை திருத்தவும் +Comment[te]=పాఠ్య ఫైళ్ళను సవరించు +Comment[th]=แก้ไขแฟ้มข้อความ +Comment[tk]=Metin faýllary editle +Comment[tr]=Metin dosyalarını düzenle +Comment[ug]=تېكىست ھۆججەتلىرىنى تەھرىرلەش +Comment[uk]=Редактор текстових файлів +Comment[vi]=Soạn thảo tập tin văn bản +Comment[wa]=Asspougnî des fitchîs tecses +Comment[xh]=Hlela iifayili zombhalo +Comment[zh_CN]=编辑文本文件 +Comment[zh_HK]=編輯文字檔 +Comment[zh_TW]=編輯文字檔 + +Keywords[ca]=Text;Editor;text simple;escriure; +Keywords[cs]=text;editor;prostý text;psát;psaní; +Keywords[da]=Tekst;Editor;Redigering;Tekstredigering;Skrive;Skriv; +Keywords[de]=Text;Editor;Klartext;Schreiben; +Keywords[el]=Text;Editor;Plaintext;Write;κείμενο;επεξεργαστής;εγγραφή;γράψιμο; +Keywords[en_GB]=Text;Editor;Plaintext;Write; +Keywords[eo]=Teksto;Redaktilo;Plata teksto;Skribi; +Keywords[es]=texto;editor;plano;escribir; +Keywords[eu]=Testua;Editorea;Testu soila;Idatzi; +Keywords[fa]=Text;Editor;Plaintext;Write;ویرایشگر متن;متن;ویرایشگر;نوشتار; +Keywords[fi]=Text;Editor;Plaintext;Write;teksti;muokkain;editori;tekstieditori;tekstimuokkain; +Keywords[fr]=texte brut;éditeur;écrire; +Keywords[fur]=Text;Editor;Test sempliç;Scrivi; +Keywords[gl]=Texto;Editor;Texto plano;Escribir; +Keywords[he]=טקסט;עורך;עריכה;טקסט פשוט;כתיבה; +Keywords[hr]=Tekst;Uređivač;Izvoran tekst;Piši; +Keywords[hu]=Szöveg;Szerkesztő;Egyszerű szöveg;Írás; +Keywords[id]=Teks;Penyunting;Teks Polos;Tulis; +Keywords[it]=Editor;Testo;Text;Scrivere; +Keywords[ja]=Text;Editor;Plaintext;Write;テキスト;エディター;プレーンテキスト; +Keywords[kk]=Text;Editor;Plaintext;Write;Мәтін;Түзеткіш;Ашық мәтін;Жазу; +Keywords[ko]=Text;텍스트;Editor;편집기;Plaintext;일반텍스트;Write;글쓰기;작성;지에디트; +Keywords[lt]=Tekstas;Redaktorius;Grynasis tekstas;Rašymas; +Keywords[lv]=Teksts;Redaktors;Vienkāršs teksts;Rakstīt; +Keywords[mjw]=Text;Editor;Plaintext;Write; +Keywords[ms]=Teks;Penyunting;Plaintext;Menulis; +Keywords[nb]=Tekst;Redigering;Vanlig tekst;Skriv; +Keywords[nl]=Text;tekst;Editor;teksteditor;tekstverwerker;Plaintext;platte tekst;Write;schrijven; +Keywords[pa]=ਸ਼ਬਦ;ਟੈਕਸਟ;ਸੰਪਾਦਕ;ਐਡੀਟਰ;ਪਲੇਨਟੈਕਸਟ;ਜੀਸੰਪਾਦਕ; +Keywords[pl]=Tekst;Text;Edytor;Zwykły tekst;Pisanie;Pisz;Napisz;Write; +Keywords[pt]=Texto;Editor;Texto simples;Escrever; +Keywords[pt_BR]=Texto;Editor;Texto simples;Plaintext;Escrever;Write; +Keywords[ro]=Text;Editor;Plaintext;Write;Text simplu;Scrie; +Keywords[ru]=Текст;Редактор;Запись;Текстовый; +Keywords[sk]=Text;Editor;Čistý text;Písať;Napísať;Písanie; +Keywords[sl]=Besedilo;Urejevalnik;tekst;Write;Pisanje; +Keywords[sr]=Text;Editor;Plaintext;Write;текст;уређивач;обичан текст;писање;гедит;tekst;uređivač;običan tekst;pisanje; +Keywords[sv]=Text;Redigerare;Vanlig text;Skriv; +Keywords[tr]=Metin;Düzenleyici;Düzyazı;Yaz; +Keywords[uk]=Текст;Редактор;Запис;Написання;ґедіт;Text;Editor;Plaintext;Write; +Keywords[vi]=Text;văn;bản;van;ban;Editor;sửa;sua;soạn;thảo;soan;thao;chữ;chu;Plaintext;Chữ thường;Chu thuong;Write;Ghi;Lưu;Luu; +Keywords[zh_CN]=Text;Editor;Plaintext;Write;文本;文字;编辑器;纯文本;编写; +Keywords[zh_TW]=Text;Editor;Plaintext;Write;文字;編輯器;純文字;撰寫;編寫; diff -Nru dte-1.9.1/GNUmakefile dte-1.10/GNUmakefile --- dte-1.9.1/GNUmakefile 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/GNUmakefile 2021-04-03 21:08:53.000000000 +0000 @@ -6,13 +6,16 @@ include mk/gen.mk -include mk/dev.mk +# https://www.gnu.org/prep/standards/html_node/Directory-Variables.html prefix ?= /usr/local -bindir ?= $(prefix)/bin -datadir ?= $(prefix)/share -mandir ?= $(datadir)/man +datarootdir ?= $(prefix)/share +exec_prefix ?= $(prefix) +bindir ?= $(exec_prefix)/bin +mandir ?= $(datarootdir)/man man1dir ?= $(mandir)/man1 man5dir ?= $(mandir)/man5 -appdir ?= $(datadir)/applications +appdir ?= $(datarootdir)/applications +bashcompletiondir ?= $(call pkg-var, bash-completion, completionsdir) INSTALL = install INSTALL_PROGRAM = $(INSTALL) @@ -21,17 +24,18 @@ RM = rm -f all: $(dte) +check: check-tests check-opts +install: install-bin install-man +uninstall: uninstall-bin uninstall-man -check: $(test) all - $(E) TEST $< - $(Q) ./$< - -install: all +install-bin: all $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(bindir)' - $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man1dir)' - $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man5dir)' $(E) INSTALL '$(DESTDIR)$(bindir)/$(dte)' $(Q) $(INSTALL_PROGRAM) '$(dte)' '$(DESTDIR)$(bindir)' + +install-man: + $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man1dir)' + $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(man5dir)' $(E) INSTALL '$(DESTDIR)$(man1dir)/dte.1' $(Q) $(INSTALL_DATA) docs/dte.1 '$(DESTDIR)$(man1dir)' $(E) INSTALL '$(DESTDIR)$(man5dir)/dterc.5' @@ -39,12 +43,24 @@ $(E) INSTALL '$(DESTDIR)$(man5dir)/dte-syntax.5' $(Q) $(INSTALL_DATA) docs/dte-syntax.5 '$(DESTDIR)$(man5dir)' -uninstall: +uninstall-bin: $(RM) '$(DESTDIR)$(bindir)/$(dte)' + +uninstall-man: $(RM) '$(DESTDIR)$(man1dir)/dte.1' $(RM) '$(DESTDIR)$(man5dir)/dterc.5' $(RM) '$(DESTDIR)$(man5dir)/dte-syntax.5' +install-bash-completion: + @$(if $(bashcompletiondir),, $(error $${bashcompletiondir} is unset)) + $(Q) $(INSTALL) -d -m755 '$(DESTDIR)$(bashcompletiondir)' + $(E) INSTALL '$(DESTDIR)$(bashcompletiondir)/$(dte)' + $(Q) $(INSTALL_DATA) completion.bash '$(DESTDIR)$(bashcompletiondir)/$(dte)' + +uninstall-bash-completion: + @$(if $(bashcompletiondir),, $(error $${bashcompletiondir} is unset)) + $(RM) '$(DESTDIR)$(bashcompletiondir)/$(dte)' + install-desktop-file: $(E) INSTALL '$(DESTDIR)$(appdir)/dte.desktop' $(Q) $(INSTALL_DESKTOP_FILE) \ @@ -58,8 +74,20 @@ $(RM) '$(DESTDIR)$(appdir)/dte.desktop' $(if $(DESTDIR),, update-desktop-database -q '$(appdir)' || :) +check-tests: $(test) all + $(E) EXEC '$(test)' + $(Q) ./$(test) + +check-opts: $(dte) + $(E) EXEC 'test/check-opts.sh' + $(Q) test/check-opts.sh './$<' '$(VERSION)' + +installcheck: install + $(E) EXEC '$(DESTDIR)$(bindir)/$(dte) -V' + $(Q) '$(DESTDIR)$(bindir)/$(dte)' -V >/dev/null + tags: - ctags $$(find src/ test/ -type f -name '*.[ch]') + ctags src/*.[ch] src/*/*.[ch] test/*.[ch] clean: $(RM) $(CLEANFILES) @@ -67,6 +95,17 @@ .DEFAULT_GOAL = all -.PHONY: all check install uninstall tags clean +.PHONY: all install install-bin install-man +.PHONY: uninstall uninstall-bin uninstall-man +.PHONY: install-bash-completion uninstall-bash-completion .PHONY: install-desktop-file uninstall-desktop-file +.PHONY: check check-tests check-opts installcheck tags clean .DELETE_ON_ERROR: + +NON_PARALLEL_TARGETS += clean install% uninstall% + +ifeq "" "$(filter $(NON_PARALLEL_TARGETS), $(or $(MAKECMDGOALS),all))" + ifeq "" "$(filter -j%, $(MAKEFLAGS))" + MAKEFLAGS += -j$(NPROC) + endif +endif diff -Nru dte-1.9.1/mk/build.mk dte-1.10/mk/build.mk --- dte-1.9.1/mk/build.mk 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/build.mk 2021-04-03 21:08:53.000000000 +0000 @@ -2,78 +2,84 @@ CFLAGS ?= -O2 LDFLAGS ?= AWK = awk -VERSION = 1.9.1 +VERSION = 1.10 +# These options are used unconditionally, since they've been supported +# by GCC since at least the minimum required version (GCC 4.6). +# WARNINGS = \ - -Wall -Wextra -Wformat -Wformat-security \ - -Wmissing-prototypes -Wstrict-prototypes \ - -Wold-style-definition -Wwrite-strings -Wundef -Wshadow \ + -Wall -Wextra -Wformat -Wformat-security -Wformat-nonliteral \ + -Wmissing-prototypes -Wstrict-prototypes -Wwrite-strings \ + -Wundef -Wshadow -Wcast-align -Wredundant-decls -Wswitch-enum \ + -Wvla -Wold-style-definition -Wframe-larger-than=32768 \ -Werror=div-by-zero -Werror=implicit-function-declaration \ -Wno-sign-compare -Wno-pointer-sign +# These options are only used if $(CC) appears to support them WARNINGS_EXTRA = \ - -Wformat-signedness -Wformat-truncation -Wformat-overflow \ + -Walloca -Walloc-zero -Wnull-dereference -Wformat-signedness \ -Wstringop-truncation -Wstringop-overflow -Wshift-overflow=2 \ - -Wframe-larger-than=32768 -Wvla + -Wcast-align=strict -Wduplicated-branches -Wduplicated-cond \ + -Wlogical-op -BUILTIN_SYNTAX_FILES := \ +BUILTIN_SYNTAX_FILES ?= \ awk c config css d diff docker dte gitcommit gitrebase go html \ - ini java javascript lua mail make markdown meson nginx ninja php \ - python robotstxt roff ruby sed sh sql tex texmfcnf vala xml \ - xresources zig - -BUILTIN_CONFIGS := $(addprefix config/, \ - rc compiler/gcc compiler/go \ - binding/default binding/shift-select \ + ini java javascript lisp lua mail make markdown meson nginx ninja \ + php python robotstxt roff ruby scheme sed sh sql tex texmfcnf tmux \ + vala xml xresources zig inc/c-comment inc/c-uchar + +BUILTIN_CONFIGS = $(addprefix config/, \ + rc binding/default compiler/gcc compiler/go \ $(addprefix color/, reset reset-basic default darkgray) \ $(addprefix syntax/, $(BUILTIN_SYNTAX_FILES)) ) TEST_CONFIGS := $(addprefix test/data/, $(addsuffix .dterc, \ - env thai crlf fuzz1 )) - -build_subdirs := $(addprefix build/, $(addsuffix /, \ - editorconfig encoding syntax terminal util test )) + env thai crlf pipe redo fuzz1 fuzz2 )) util_objects := $(call prefix-obj, build/util/, \ - ascii exec hashset path ptr-array readfile string strtonum \ - unicode utf8 wbuf xmalloc xreadwrite xsnprintf ) + ascii base64 debug exec hashmap hashset numtostr path ptr-array \ + readfile string strtonum unicode utf8 xmalloc xreadwrite xsnprintf ) + +command_objects := $(call prefix-obj, build/command/, \ + args env macro parse run serialize ) editorconfig_objects := $(call prefix-obj, build/editorconfig/, \ editorconfig ini match ) -encoding_objects := $(call prefix-obj, build/encoding/, \ - bom convert decoder encoder encoding ) - syntax_objects := $(call prefix-obj, build/syntax/, \ - bitset color highlight state syntax ) + color highlight state syntax ) terminal_objects := $(call prefix-obj, build/terminal/, \ - color ecma48 input key no-op output terminal terminfo xterm xterm-keys ) + color ecma48 input key kitty mode output rxvt terminal \ + winsize xterm ) editor_objects := $(call prefix-obj, build/, \ - alias bind block block-iter buffer change cmdline command \ - command-parse command-run compiler completion config ctags debug \ - edit editor env error file-history file-option filetype frame \ - history indent load-save lock main mode-command mode-git-open \ - mode-normal mode-search move msg options parse-args regexp \ + alias bind block block-iter buffer change cmdline commands \ + convert compiler completion config ctags edit editor \ + encoding error file-history file-option filetype frame history \ + indent load-save lock main mode move msg options regexp \ screen screen-cmdline screen-status screen-tabbar screen-view \ - search selection spawn tag view window ) \ + search selection show spawn tag view window ) \ + $(command_objects) \ $(editorconfig_objects) \ - $(encoding_objects) \ $(syntax_objects) \ $(terminal_objects) \ $(util_objects) test_objects := $(call prefix-obj, build/test/, \ - cmdline command config editorconfig encoding filetype main \ - syntax terminal test util ) + bind cmdline command config dump editorconfig encoding filetype \ + history main options syntax terminal test util ) + +feature_tests := $(addprefix build/feature/, $(addsuffix .h, \ + dup3 pipe2 fsync TIOCGWINSZ tcgetwinsize posix_madvise )) all_objects := $(editor_objects) $(test_objects) +build_subdirs := $(filter-out build/, $(sort $(dir $(all_objects)))) build/feature/ editor_sources := $(patsubst build/%.o, src/%.c, $(editor_objects)) test_sources := $(patsubst build/test/%.o, test/%.c, $(test_objects)) -ifdef WERROR +ifeq "$(WERROR)" "1" WARNINGS += -Werror endif @@ -83,51 +89,49 @@ $(call make-lazy,CSTD) ifeq "$(KERNEL)" "Darwin" - LDLIBS_ICONV += -liconv + LDLIBS_ICONV = -liconv else ifeq "$(OS)" "Cygwin" - LDLIBS_ICONV += -liconv + LDLIBS_ICONV = -liconv EXEC_SUFFIX = .exe else ifeq "$(KERNEL)" "OpenBSD" - LDLIBS_ICONV += -liconv + LDLIBS_ICONV = -liconv BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib else ifeq "$(KERNEL)" "NetBSD" ifeq ($(shell expr "`uname -r`" : '[01]\.'),2) - LDLIBS_ICONV += -liconv + LDLIBS_ICONV = -liconv endif BASIC_CFLAGS += -I/usr/pkg/include BASIC_LDFLAGS += -L/usr/pkg/lib endif -ifdef ICONV_DISABLE - build/encoding/convert.o: BASIC_CFLAGS += -DICONV_DISABLE=1 +ifeq "$(ICONV_DISABLE)" "1" + build/convert.o: BASIC_CFLAGS += -DICONV_DISABLE=1 else LDLIBS += $(LDLIBS_ICONV) endif -ifdef TERMINFO_DISABLE - build/terminal/terminfo.o: BASIC_CFLAGS += -DTERMINFO_DISABLE=1 -else - LDLIBS += $(or $(call pkg-libs, tinfo), $(call pkg-libs, ncurses), -lcurses) +ifeq "$(SANE_WCTYPE)" "1" + BASIC_CFLAGS += -DSANE_WCTYPE=1 endif dte = dte$(EXEC_SUFFIX) test = build/test/test$(EXEC_SUFFIX) -ifdef USE_SANITIZER +ifeq "$(USE_SANITIZER)" "1" SANITIZER_FLAGS := \ -fsanitize=address,undefined -fsanitize-address-use-after-scope \ -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common CC_SANITIZER_FLAGS := $(or \ - $(call cc-option, $(SANITIZER_FLAGS)), \ + $(call cc-option,$(SANITIZER_FLAGS)), \ $(warning USE_SANITIZER set but compiler doesn't support ASan/UBSan) ) $(all_objects): BASIC_CFLAGS += $(CC_SANITIZER_FLAGS) $(dte) $(test): BASIC_LDFLAGS += $(CC_SANITIZER_FLAGS) - export ASAN_OPTIONS=detect_leaks=1:detect_stack_use_after_return=1 DEBUG = 3 else # 0: Disable debugging # 1: Enable BUG_ON() and light-weight sanity checks + # 2: Enable logging to $DTE_LOG # 3: Enable expensive sanity checks DEBUG ?= 0 endif @@ -137,10 +141,15 @@ $(call make-lazy,UNWIND) endif -$(all_objects): BASIC_CFLAGS += $(CSTD) -DDEBUG=$(DEBUG) $(CWARNS) $(UNWIND) +BASIC_CFLAGS += \ + $(CSTD) $(CWARNS) $(UNWIND) \ + -DDEBUG=$(DEBUG) \ + -D_FILE_OFFSET_BITS=64 + +$(all_objects): BASIC_CFLAGS += -Isrc -# If "make install" with no other named targets -ifeq "" "$(filter-out install,$(or $(MAKECMDGOALS),all))" +# If "make install*" with no other named targets +ifeq "" "$(filter-out install%,$(or $(MAKECMDGOALS),all))" OPTCHECK = : else OPTCHECK = SILENT_BUILD='$(MAKE_S)' mk/optcheck.sh @@ -158,18 +167,21 @@ $(dte): $(editor_objects) $(test): $(filter-out build/main.o, $(all_objects)) $(util_objects): | build/util/ +$(command_objects): | build/command/ $(editorconfig_objects): | build/editorconfig/ -$(encoding_objects): | build/encoding/ $(syntax_objects): | build/syntax/ $(terminal_objects): | build/terminal/ +$(build_subdirs): | build/ +$(feature_tests): mk/feature-test/defs.h build/all.cflags | build/feature/ build/builtin-config.h: build/builtin-config.mk +build/test/data.h: build/test/data.mk build/config.o: build/builtin-config.h build/test/config.o: build/test/data.h build/editor.o: build/version.h -build/terminal/terminfo.o: build/terminal/terminfo.cflags -build/encoding/convert.o: build/encoding/convert.cflags -build/terminal/terminfo.cflags: | build/terminal/ -build/encoding/convert.cflags: | build/encoding/ +build/load-save.o: build/feature.h +build/util/exec.o: build/feature.h +build/terminal/winsize.o: build/feature.h +build/convert.o: build/convert.cflags CFLAGS_ALL = $(CPPFLAGS) $(CFLAGS) $(BASIC_CFLAGS) LDFLAGS_ALL = $(CFLAGS) $(LDFLAGS) $(BASIC_LDFLAGS) @@ -198,6 +210,9 @@ build/builtin-config.mk: FORCE | build/ @$(OPTCHECK) '$(@:.mk=.h): $(BUILTIN_CONFIGS)' $@ +build/test/data.mk: FORCE | build/test/ + @$(OPTCHECK) '$(@:.mk=.h): $(TEST_CONFIGS)' $@ + build/builtin-config.h: $(BUILTIN_CONFIGS) mk/config2c.awk | build/ $(E) GEN $@ $(Q) $(AWK) -f mk/config2c.awk $(BUILTIN_CONFIGS) > $@ @@ -206,10 +221,19 @@ $(E) GEN $@ $(Q) $(AWK) -f mk/config2c.awk $(TEST_CONFIGS) > $@ -$(build_subdirs): | build/ - $(Q) mkdir -p $@ +build/feature.h: mk/feature-test/defs.h $(feature_tests) + $(E) GEN $@ + $(Q) cat $^ > $@ + +$(feature_tests): build/feature/%.h: mk/feature-test/%.c mk/feature-test/%.h + $(E) DETECT $* + $(Q) if $(CC) $(CFLAGS_ALL) -o $(@:.h=.o) $< 2>$(@:.h=.log); then \ + cp $(<:.c=.h) $@ ; \ + else \ + echo '// $* not detected' > $@ ; \ + fi -build/: +build/ $(build_subdirs): $(Q) mkdir -p $@ diff -Nru dte-1.9.1/mk/docs.mk dte-1.10/mk/docs.mk --- dte-1.9.1/mk/docs.mk 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/docs.mk 2021-04-03 21:08:53.000000000 +0000 @@ -2,51 +2,52 @@ PANDOC_FLAGS = -f markdown_github+definition_lists+auto_identifiers+yaml_metadata_block-hard_line_breaks PDMAN = $(PANDOC) $(PANDOC_FLAGS) -t docs/pdman.lua PDHTML = $(PANDOC) $(PANDOC_FLAGS) -t html5 --toc --template=docs/template.html -Voutput_basename=$(@F) -FINDLINKS = sed -n 's|^.*\(https\?://[A-Za-z0-9_/.-]*\).*|\1|gp' -CHECKURL = curl -sSI -w '%{http_code} @1 %{redirect_url}\n' -o /dev/null @1 -XARGS_P_FLAG = $(call try-run, printf "1\n2" | xargs -P2 -I@ echo '@', -P$(NPROC)) -html-man = public/dterc.html public/dte-syntax.html +public/dte.html: PANDOC_FLAGS += --indented-code-classes=sh + +man = docs/dte.1 docs/dterc.5 docs/dte-syntax.5 +html-man = public/dte.html public/dterc.html public/dte-syntax.html html = public/index.html public/releases.html $(html-man) +css = public/style.css +img = public/screenshot.png public/favicon.ico -docs: man html gz -man: docs/dterc.5 docs/dte-syntax.5 -html: $(html) +docs: man html htmlgz +man: $(man) +html: $(html) $(css) $(img) +htmlgz: $(patsubst %, %.gz, $(html) $(css) public/favicon.ico) pdf: public/dte.pdf -gz: $(patsubst %, %.gz, $(html) public/style.css) $(html): docs/template.html | public/style.css -docs/%.5: docs/%.md docs/pdman.lua +docs/%.1 docs/%.5: docs/%.md docs/pdman.lua $(E) PANDOC $@ $(Q) $(PDMAN) -o $@ $< -public/dte.pdf: docs/dte.1 docs/dterc.5 docs/dte-syntax.5 | public/ +public/dte.pdf: $(man) | public/ $(E) GROFF $@ $(Q) groff -mandoc -Tpdf $^ > $@ -public/index.html: build/docs/index.md | public/screenshot.png +public/index.html: docs/index.yml build/docs/index.md docs/gitlab.md | public/ $(E) PANDOC $@ - $(Q) $(PDHTML) -Mtitle=_ -o $@ $< + $(Q) $(PDHTML) -o $@ $(filter-out docs/template.html, $^) -build/docs/index.md: README.md docs/keys.md | build/docs/ +build/docs/index.md: docs/index.sed README.md | build/docs/ $(E) GEN $@ - $(Q) sed '/^Online documentation is/,/^Public License/d' README.md > $@ - $(Q) sed '/^`/s|`\([^`]\+\)`|\1|g' docs/keys.md >> $@ + $(Q) sed -f $^ > $@ -public/releases.html: CHANGELOG.md | public/ +public/releases.html: docs/releases.yml CHANGELOG.md | public/ $(E) PANDOC $@ - $(Q) $(PDHTML) -Mtitle=_ -o $@ $< + $(Q) $(PDHTML) -o $@ $(filter-out docs/template.html, $^) -$(html-man): public/%.html: docs/%.md +$(html-man): public/%.html: docs/%.md docs/fix-anchors.lua $(E) PANDOC $@ - $(Q) $(PDHTML) -o $@ $< + $(Q) $(PDHTML) --lua-filter=docs/fix-anchors.lua -o $@ $< public/style.css: docs/layout.css docs/style.css | public/ $(E) CSSCAT $@ $(Q) cat $^ > $@ -public/screenshot.png: docs/screenshot.png | public/ +$(img): public/%: docs/% | public/ $(E) CP $@ $(Q) cp $< $@ @@ -60,9 +61,6 @@ build/docs/: build/ $(Q) mkdir -p $@ -check-docs: README.md CHANGELOG.md docs/contributing.md docs/dterc.md docs/dte-syntax.md - @$(FINDLINKS) $^ | xargs -I@1 $(XARGS_P_FLAG) $(CHECKURL) - CLEANDIRS += public/ -.PHONY: docs man html pdf gz check-docs +.PHONY: docs man html htmlgz pdf diff -Nru dte-1.9.1/mk/feature-test/basic.c dte-1.10/mk/feature-test/basic.c --- dte-1.9.1/mk/feature-test/basic.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/basic.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,16 @@ +/* + This file serves as a minimal, valid input that can be passed to + a compiler in order to make it run in C mode (like "gcc -xc"). + + The behaviour for a file argument matching "*.c" is specified by + the POSIX c99(1) spec, whereas "-xc" is a GCC-specific flag. + + See also: + - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html + - https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html +*/ + +int main(void) +{ + return 0; +} diff -Nru dte-1.9.1/mk/feature-test/defs.h dte-1.10/mk/feature-test/defs.h --- dte-1.9.1/mk/feature-test/defs.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/defs.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define _GNU_SOURCE diff -Nru dte-1.9.1/mk/feature-test/dup3.c dte-1.10/mk/feature-test/dup3.c --- dte-1.9.1/mk/feature-test/dup3.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/dup3.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,8 @@ +#include "defs.h" +#include +#include + +int main(void) +{ + return (dup3)(2, 1, O_CLOEXEC); +} diff -Nru dte-1.9.1/mk/feature-test/dup3.h dte-1.10/mk/feature-test/dup3.h --- dte-1.9.1/mk/feature-test/dup3.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/dup3.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_DUP3 diff -Nru dte-1.9.1/mk/feature-test/fsync.c dte-1.10/mk/feature-test/fsync.c --- dte-1.9.1/mk/feature-test/fsync.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/fsync.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,7 @@ +#include "defs.h" +#include + +int main(void) +{ + return (fsync)(1); +} diff -Nru dte-1.9.1/mk/feature-test/fsync.h dte-1.10/mk/feature-test/fsync.h --- dte-1.9.1/mk/feature-test/fsync.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/fsync.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_FSYNC diff -Nru dte-1.9.1/mk/feature-test/pipe2.c dte-1.10/mk/feature-test/pipe2.c --- dte-1.9.1/mk/feature-test/pipe2.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/pipe2.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +#include "defs.h" +#include +#include + +int main(void) +{ + int fd[2] = {-1, -1}; + return (pipe2)(fd, O_CLOEXEC); +} diff -Nru dte-1.9.1/mk/feature-test/pipe2.h dte-1.10/mk/feature-test/pipe2.h --- dte-1.9.1/mk/feature-test/pipe2.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/pipe2.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_PIPE2 diff -Nru dte-1.9.1/mk/feature-test/posix_madvise.c dte-1.10/mk/feature-test/posix_madvise.c --- dte-1.9.1/mk/feature-test/posix_madvise.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/posix_madvise.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + static char buf[256]; + return (posix_madvise)(buf, sizeof buf, POSIX_MADV_SEQUENTIAL); +} diff -Nru dte-1.9.1/mk/feature-test/posix_madvise.h dte-1.10/mk/feature-test/posix_madvise.h --- dte-1.9.1/mk/feature-test/posix_madvise.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/posix_madvise.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_POSIX_MADVISE diff -Nru dte-1.9.1/mk/feature-test/tcgetwinsize.c dte-1.10/mk/feature-test/tcgetwinsize.c --- dte-1.9.1/mk/feature-test/tcgetwinsize.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/tcgetwinsize.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,8 @@ +#include + +int main(void) +{ + struct winsize ws; + int r = (tcgetwinsize)(0, &ws); + return !(r != -1 && ws.ws_col > 0 && ws.ws_row > 0); +} diff -Nru dte-1.9.1/mk/feature-test/tcgetwinsize.h dte-1.10/mk/feature-test/tcgetwinsize.h --- dte-1.9.1/mk/feature-test/tcgetwinsize.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/tcgetwinsize.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_TCGETWINSIZE diff -Nru dte-1.9.1/mk/feature-test/TIOCGWINSZ.c dte-1.10/mk/feature-test/TIOCGWINSZ.c --- dte-1.9.1/mk/feature-test/TIOCGWINSZ.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/TIOCGWINSZ.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,8 @@ +#include + +int main(void) +{ + struct winsize ws; + int r = (ioctl)(0, TIOCGWINSZ, &ws); + return !(r != -1 && ws.ws_col > 0 && ws.ws_row > 0); +} diff -Nru dte-1.9.1/mk/feature-test/TIOCGWINSZ.h dte-1.10/mk/feature-test/TIOCGWINSZ.h --- dte-1.9.1/mk/feature-test/TIOCGWINSZ.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/mk/feature-test/TIOCGWINSZ.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1 @@ +#define HAVE_TIOCGWINSZ diff -Nru dte-1.9.1/mk/gen.mk dte-1.10/mk/gen.mk --- dte-1.9.1/mk/gen.mk 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/gen.mk 2021-04-03 21:08:53.000000000 +0000 @@ -4,16 +4,16 @@ UCD_FILES = $(addprefix .cache/, \ UnicodeData.txt EastAsianWidth.txt DerivedCoreProperties.txt ) -gen-wcwidth: $(UCD_FILES) - $(E) GEN src/util/wcwidth.c - $(Q) $(LUA) src/util/wcwidth.lua $(UCD_FILES) > src/util/wcwidth.c +gen-unidata: $(UCD_FILES) + $(E) GEN src/util/unidata.h + $(Q) $(LUA) src/util/unidata.lua $(UCD_FILES) > src/util/unidata.h $(UCD_FILES): | .cache/ $(E) FETCH $@ - $(Q) $(FETCH) https://unicode.org/Public/12.1.0/ucd/$(@F) + $(Q) $(FETCH) https://unicode.org/Public/13.0.0/ucd/$(@F) .cache/: @mkdir -p $@ -.PHONY: gen-wcwidth +.PHONY: gen-unidata diff -Nru dte-1.9.1/mk/nproc.sh dte-1.10/mk/nproc.sh --- dte-1.9.1/mk/nproc.sh 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/nproc.sh 2021-04-03 21:08:53.000000000 +0000 @@ -3,12 +3,18 @@ case "$(uname)" in Linux) exec getconf _NPROCESSORS_ONLN;; -FreeBSD|NetBSD|OpenBSD) +FreeBSD|NetBSD|OpenBSD|DragonFly) exec /sbin/sysctl -n hw.ncpu;; Darwin) exec /usr/sbin/sysctl -n hw.activecpu;; SunOS) exec /usr/sbin/psrinfo -p;; *) - echo 1;; + ( + getconf NPROCESSORS_ONLN || + getconf _NPROCESSORS_ONLN || + sysctl -n hw.ncpu || + nproc || + echo 1 + ) 2>/dev/null;; esac diff -Nru dte-1.9.1/mk/util.mk dte-1.10/mk/util.mk --- dte-1.9.1/mk/util.mk 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/util.mk 2021-04-03 21:08:53.000000000 +0000 @@ -1,23 +1,26 @@ PKGCONFIG = $(shell command -v pkg-config || command -v pkgconf || echo ':') $(call make-lazy,PKGCONFIG) +CFILE := mk/feature-test/basic.c streq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) try-run = $(if $(shell $(1) >/dev/null 2>&1 && echo 1),$(2),$(3)) -cc-option = $(call try-run,$(CC) $(1) -Werror -c -x c -o /dev/null /dev/null,$(1),$(2)) +cc-option = $(call try-run,$(CC) $(1) -Werror -c -o /dev/null $(CFILE),$(1),$(2)) prefix-obj = $(addprefix $(1), $(addsuffix .o, $(2))) -pkg-libs = $(shell $(PKGCONFIG) --libs $(1) 2>/dev/null) +pkg-var = $(shell $(PKGCONFIG) --variable='$(strip $(2))' $(1)) +echo-if-set = $(foreach var, $(1), $(if $($(var)), $(var))) MAKEFLAGS += -r KERNEL := $(shell sh -c 'uname -s 2>/dev/null || echo not') OS := $(shell sh -c 'uname -o 2>/dev/null || echo not') DISTRO = $(shell . /etc/os-release && echo "$$NAME $$VERSION_ID") ARCH = $(shell uname -m 2>/dev/null) -NPROC = $(shell sh mk/nproc.sh) +NPROC = $(or $(shell sh mk/nproc.sh), 1) _POSIX_VERSION = $(shell getconf _POSIX_VERSION 2>/dev/null) _XOPEN_VERSION = $(shell getconf _XOPEN_VERSION 2>/dev/null) -TPUT = $(shell sh -c 'command -v tput') -TPUT-V = $(if $(TPUT), $(shell $(TPUT) -V 2>/dev/null)) -CC_VERSION = $(shell $(CC) --version 2>/dev/null | head -n1) +CC_VERSION = $(or \ + $(shell $(CC) --version 2>/dev/null | head -n1), \ + $(shell $(CC) -v 2>&1 | grep version) ) +CC_TARGET = $(shell $(CC) -dumpmachine 2>/dev/null) MAKE_S = $(findstring s,$(firstword -$(MAKEFLAGS)))$(filter -s,$(MAKEFLAGS)) PRINTVAR = printf '\033[1m%15s\033[0m = %s$(2)\n' '$(1)' '$(strip $($(1)))' $(3) PRINTVARX = $(call PRINTVAR,$(1), \033[32m(%s)\033[0m, '$(origin $(1))') @@ -27,7 +30,8 @@ VERSION KERNEL \ $(if $(call streq,$(KERNEL),Linux), DISTRO) \ ARCH NPROC _POSIX_VERSION _XOPEN_VERSION \ - TERM SHELL PKGCONFIG TPUT TPUT-V MAKE_VERSION CC_VERSION + TERM SHELL LANG $(call echo-if-set, LC_CTYPE LC_ALL) \ + PKGCONFIG MAKE_VERSION MAKEFLAGS CC_VERSION CC_TARGET vars: @echo diff -Nru dte-1.9.1/mk/version.sh dte-1.10/mk/version.sh --- dte-1.9.1/mk/version.sh 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/mk/version.sh 2021-04-03 21:08:53.000000000 +0000 @@ -5,11 +5,11 @@ set -eu export VPREFIX="$1" -# These values are filled automatically for git-archive(1) tarballs. -# See: "export-subst" in gitattributes(5). -distinfo_commit_full='cae7c45ea5a563c022001a54d1eee71c268f62b4' -distinfo_commit_short='cae7c45e' -distinfo_author_date='2019-09-29 16:51:55 +0100' +# This value is filled automatically for git-archive(1) tarballs. +# See also: "export-subst" in gitattributes(5). +# shellcheck disable=SC2016 +distinfo_commit_short='6e37409d' + if expr "$distinfo_commit_short" : '[0-9a-f]\{7,40\}$' >/dev/null; then echo "${VPREFIX}-g${distinfo_commit_short}-dist" exit 0 diff -Nru dte-1.9.1/README.md dte-1.10/README.md --- dte-1.9.1/README.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/README.md 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,7 @@ dte === -dte is a small and easy to use console text editor. +A small and easy to use console text editor. Features -------- @@ -14,7 +14,7 @@ * Customizable key bindings * Support for all xterm Ctrl/Alt/Shift key codes * Command language with auto-completion -* Unicode 12.1 compatible text rendering +* Unicode 13 compatible text rendering * Support for multiple encodings (using [iconv]) * Jump to definition (using [ctags]) * Jump to compiler error @@ -25,25 +25,44 @@ ![dte screenshot](https://craigbarnes.gitlab.io/dte/screenshot.png) -Requirements ------------- +Installing +---------- + +`dte` can be installed via package manager on the following platforms: + +| OS | Install command | +|---------------------------|--------------------------------------------| +| [Debian Testing] | `apt-get install dte` | +| [Ubuntu] | `apt-get install dte` | +| Arch Linux ([AUR]) | `$AUR_HELPER -S dte` | +| [Void Linux] | `xbps-install -S dte` | +| Slackware ([SlackBuilds]) | See: [SlackBuild Usage HOWTO] | +| [FreeBSD] | `pkg install dte` | +| DragonFly BSD ([DPorts]) | `pkg install dte` | +| [OpenBSD] | `pkg_add dte` | +| NetBSD ([pkgsrc]) | `pkg_add dte` | +| OS X ([Homebrew]) | `brew tap yumitsu/dte && brew install dte` | +| Android ([Termux]) | `pkg install dte` | + +Building +-------- + +To build from source, first ensure the following dependencies are +installed: * [GCC] 4.6+ or [Clang] * [GNU Make] 3.81+ -* [terminfo] library (may be provided by [ncurses], depending on OS) -* [iconv] library (may be included in libc, depending on OS) -* [POSIX]-compatible [`sh`] and [`awk`] - -Installation ------------- - -To build `dte` from source, first install the requirements listed above, -then use the following commands: - - curl -LO https://craigbarnes.gitlab.io/dist/dte/dte-1.9.1.tar.gz - tar -xzf dte-1.9.1.tar.gz - cd dte-1.9.1 - make -j8 && sudo make install +* [iconv] library (usually provided by libc on Linux/FreeBSD) + +...then download and unpack the latest release tarball: + + curl -LO https://craigbarnes.gitlab.io/dist/dte/dte-1.10.tar.gz + tar -xzf dte-1.10.tar.gz + cd dte-1.10 + +...and compile and install: + + make && sudo make install Documentation ------------- @@ -53,99 +72,15 @@ Online documentation is also available at . -Testing -------- - -`dte` is tested on the following platforms: - -| Platform | Testing Method | -|-------------------|-----------------------| -| Debian | [GitLab CI] | -| CentOS | GitLab CI | -| Alpine Linux | GitLab CI | -| Void Linux (musl) | GitLab CI | -| Mac OS X | [Travis CI] | -| Ubuntu | GitLab CI + Travis CI | -| FreeBSD | Manual testing | -| NetBSD | Manual testing | -| OpenBSD | Manual testing | -| Cygwin | Manual testing | - -Other [POSIX] 2008 compatible platforms should also work, but may -require build system changes. - Packaging --------- -**Stable releases**: - -The [releases] page contains a short summary of changes for each -stable version and links to the corresponding source tarballs. - -Note: auto-generated tarballs from GitHub/GitLab can (and -[do][libgit issue #4343]) change over time and cannot be guaranteed to -have long-term stable checksums. Use the tarballs from the [releases] -page, unless you're prepared to deal with future checksum failures. - -**Build variables**: - -The following build variables may be useful when packaging `dte`: - -* `prefix`: Top-level installation prefix (defaults to `/usr/local`). -* `bindir`: Installation prefix for program binary (defaults to - `$prefix/bin`). -* `mandir`: Installation prefix for manual pages (defaults to - `$prefix/share/man`). -* `DESTDIR`: Standard variable used for [staged installs]. -* `V=1`: Enable verbose build output. -* `TERMINFO_DISABLE=1`: Use built-in terminal support, instead of - linking to the system [terminfo]/curses library. This makes it much - easier to build a portable, statically linked binary. The built-in - terminal support currently works with `tmux`, `screen`, `st`, `xterm` - (and many other `xterm`-compatible terminals) and falls back to - [ECMA-48] mode for other terminals. -* `ICONV_DISABLE=1`: Disable support for all file encodings except - UTF-8, to avoid the need to link with the system [iconv] library. - This can significantly reduce the size of statically linked builds, - but is generally not recommended. - -Example usage: - - make V=1 - make install V=1 prefix=/usr DESTDIR=PKG - -**Persistent configuration**: - -Build variables can also be configured persistently by adding them to -a `Config.mk` file, for example: - - prefix = /usr - mandir = $(prefix)/man - DESTDIR = ~/buildroot - V = 1 - -The `Config.mk` file should be in the project base directory alongside -`GNUmakefile` and *must* be valid GNU make syntax. - -**Desktop menu entry**: - -A desktop menu entry for `dte` can be added by running: - - make install-desktop-file - -Any variable overrides specified for `make install` must also be specified -for `make install-desktop-file`. The easiest way to do this is simply to -run both at the same time, e.g.: - - make install install-desktop-file V=1 prefix=/usr DESTDIR=PKG - -**Note**: the `install-desktop-file` target requires [desktop-file-utils] -to be installed. +See [`docs/packaging.md`](https://gitlab.com/craigbarnes/dte/blob/master/docs/packaging.md). License ------- -Copyright (C) 2017-2019 Craig Barnes. +Copyright (C) 2017-2021 Craig Barnes. Copyright (C) 2010-2015 Timo Hirvonen. This program is free software; you can redistribute it and/or modify it @@ -158,24 +93,23 @@ Public License version 2 for more details. -[ctags]: https://en.wikipedia.org/wiki/Ctags +[ctags]: https://ctags.io/ [EditorConfig]: https://editorconfig.org/ [GCC]: https://gcc.gnu.org/ [Clang]: https://clang.llvm.org/ [GNU Make]: https://www.gnu.org/software/make/ -[ncurses]: https://www.gnu.org/software/ncurses/ -[terminfo]: https://en.wikipedia.org/wiki/Terminfo -[ECMA-48]: https://www.ecma-international.org/publications/standards/Ecma-048.htm "ANSI X3.64 / ECMA-48 / ISO/IEC 6429" -[desktop-file-utils]: https://www.freedesktop.org/wiki/Software/desktop-file-utils [`GNUmakefile`]: https://gitlab.com/craigbarnes/dte/blob/master/GNUmakefile -[syntax files]: https://gitlab.com/craigbarnes/dte/tree/master/config/syntax -[staged installs]: https://www.gnu.org/prep/standards/html_node/DESTDIR.html -[POSIX]: https://pubs.opengroup.org/onlinepubs/9699919799/ [iconv]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/iconv.h.html -[`sh`]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html -[`awk`]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html -[GitLab CI]: https://gitlab.com/craigbarnes/dte/pipelines -[Travis CI]: https://travis-ci.org/craigbarnes/dte -[General Public License version 2]: https://www.gnu.org/licenses/gpl-2.0.html -[releases]: https://craigbarnes.gitlab.io/dte/releases.html -[libgit issue #4343]: https://github.com/libgit2/libgit2/issues/4343 +[General Public License version 2]: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html +[Debian Testing]: https://packages.debian.org/testing/dte +[Ubuntu]: https://launchpad.net/ubuntu/+source/dte +[AUR]: https://aur.archlinux.org/packages/dte/ +[Void Linux]: https://github.com/void-linux/void-packages/tree/master/srcpkgs/dte +[SlackBuilds]: https://slackbuilds.org/repository/14.2/development/dte/ +[SlackBuild Usage HOWTO]: https://slackbuilds.org/howto/ +[FreeBSD]: https://svnweb.freebsd.org/ports/head/editors/dte/ +[DPorts]: https://gitweb.dragonflybsd.org/dports.git/tree/HEAD:/editors/dte +[OpenBSD]: https://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/editors/dte/ +[pkgsrc]: https://pkgsrc.se/editors/dte +[Homebrew]: https://github.com/yumitsu/homebrew-dte +[Termux]: https://github.com/termux/termux-packages/tree/master/packages/dte diff -Nru dte-1.9.1/src/alias.c dte-1.10/src/alias.c --- dte-1.9.1/src/alias.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/alias.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,116 +1,86 @@ +#include #include #include #include #include "alias.h" -#include "command.h" +#include "command/serialize.h" #include "completion.h" #include "editor.h" -#include "error.h" -#include "util/ascii.h" +#include "util/hashmap.h" #include "util/macros.h" -#include "util/ptr-array.h" #include "util/str-util.h" #include "util/xmalloc.h" -typedef struct { - char *name; - char *value; -} CommandAlias; - -static PointerArray aliases = PTR_ARRAY_INIT; +static HashMap aliases = HASHMAP_INIT; -static void CONSTRUCTOR prealloc(void) +void init_aliases(void) { - ptr_array_init(&aliases, 32); -} - -static bool is_valid_alias_name(const char *name) -{ - if (unlikely(name[0] == '\0')) { - error_msg("Empty alias name not allowed"); - return false; - } - - for (unsigned char c; (c = *name); name++) { - if (is_word_byte(c) || c == '-' || c == '?' || c == '!') { - continue; - } - error_msg("Invalid byte in alias name: 0x%02x", (unsigned int)c); - return false; - } - - return true; + BUG_ON(aliases.entries); + hashmap_init(&aliases, 32); } void add_alias(const char *name, const char *value) { - if (!is_valid_alias_name(name)) { - return; - } - if (find_command(commands, name)) { - error_msg("Can't replace existing command %s with an alias", name); - return; - } - - // Replace existing alias - for (size_t i = 0; i < aliases.count; i++) { - CommandAlias *alias = aliases.ptrs[i]; - if (streq(alias->name, name)) { - free(alias->value); - alias->value = xstrdup(value); - return; - } - } - - CommandAlias *alias = xnew(CommandAlias, 1); - alias->name = xstrdup(name); - alias->value = xstrdup(value); - ptr_array_add(&aliases, alias); - - if (editor.status != EDITOR_INITIALIZING) { - sort_aliases(); - } + free(hashmap_insert_or_replace(&aliases, xstrdup(name), xstrdup(value))); } -static int alias_cmp(const void *ap, const void *bp) +const char *find_alias(const char *const name) { - const CommandAlias *const *a = ap; - const CommandAlias *const *b = bp; - return strcmp((*a)->name, (*b)->name); + return hashmap_get(&aliases, name); } -void sort_aliases(void) +void collect_aliases(const char *const prefix) { - ptr_array_sort(&aliases, alias_cmp); + collect_hashmap_keys(&aliases, prefix); } -const char *find_alias(const char *const name) -{ - const CommandAlias key = {.name = (char*) name}; - const void *ptr = ptr_array_bsearch(aliases, &key, alias_cmp); - if (ptr) { - const CommandAlias *alias = *(const CommandAlias **) ptr; - return alias->value; - } - return NULL; -} +typedef struct { + const char *name; + const char *value; +} CommandAlias; -void collect_aliases(const char *const prefix) +static int alias_cmp(const void *ap, const void *bp) { - for (size_t i = 0; i < aliases.count; i++) { - const CommandAlias *const alias = aliases.ptrs[i]; - if (str_has_prefix(alias->name, prefix)) { - add_completion(xstrdup(alias->name)); - } - } + const CommandAlias *a = ap; + const CommandAlias *b = bp; + return strcmp(a->name, b->name); } String dump_aliases(void) { + const size_t count = aliases.count; + if (unlikely(count == 0)) { + return string_new(0); + } + + // Clone the contents of the HashMap as an array of name/value pairs + CommandAlias *array = xnew(CommandAlias, count); + size_t n = 0; + for (HashMapIter it = hashmap_iter(&aliases); hashmap_next(&it); ) { + array[n++] = (CommandAlias) { + .name = it.entry->key, + .value = it.entry->value, + }; + } + + // Sort the array + BUG_ON(n != count); + qsort(array, count, sizeof(array[0]), alias_cmp); + + // Serialize the aliases in sorted order String buf = string_new(4096); - for (size_t i = 0; i < aliases.count; i++) { - const CommandAlias *alias = aliases.ptrs[i]; - string_sprintf(&buf, " %s -> %s\n", alias->name, alias->value); + for (size_t i = 0; i < count; i++) { + const char *name = array[i].name; + string_append_literal(&buf, "alias "); + if (unlikely(name[0] == '-')) { + string_append_literal(&buf, "-- "); + } + string_append_escaped_arg(&buf, name, true); + string_append_byte(&buf, ' '); + string_append_escaped_arg(&buf, array[i].value, true); + string_append_byte(&buf, '\n'); } + + free(array); return buf; } diff -Nru dte-1.9.1/src/alias.h dte-1.10/src/alias.h --- dte-1.9.1/src/alias.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/alias.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,8 +3,8 @@ #include "util/string.h" +void init_aliases(void); void add_alias(const char *name, const char *value); -void sort_aliases(void); const char *find_alias(const char *name); void collect_aliases(const char *prefix); String dump_aliases(void); diff -Nru dte-1.9.1/src/bind.c dte-1.10/src/bind.c --- dte-1.9.1/src/bind.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/bind.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,11 +1,17 @@ +#include +#include #include #include "bind.h" #include "change.h" -#include "command.h" -#include "debug.h" +#include "command/args.h" +#include "command/macro.h" +#include "command/parse.h" +#include "command/serialize.h" +#include "commands.h" +#include "completion.h" #include "error.h" -#include "parse-args.h" -#include "util/ascii.h" +#include "util/debug.h" +#include "util/macros.h" #include "util/ptr-array.h" #include "util/str-util.h" #include "util/xmalloc.h" @@ -22,26 +28,22 @@ // Fallback for all other keys (Unicode combos etc.) static PointerArray bindings_ptr_array = PTR_ARRAY_INIT; -static ssize_t key_lookup_index(KeyCode k) +static ssize_t get_lookup_table_index(KeyCode k) { const KeyCode modifiers = keycode_get_modifiers(k); const KeyCode key = keycode_get_key(k); - static_assert(MOD_MASK >> 24 == (1 | 2 | 4)); + static_assert(MOD_MASK >> MOD_OFFSET == (1 | 2 | 4)); if (key >= KEY_SPECIAL_MIN && key <= KEY_SPECIAL_MAX) { - const size_t mod_offset = (modifiers >> 24) * NR_SPECIAL_KEYS; + const size_t mod_offset = (modifiers >> MOD_OFFSET) * NR_SPECIAL_KEYS; return (2 * 128) + mod_offset + (key - KEY_SPECIAL_MIN); } if (key >= 0x20 && key <= 0x7E) { switch (modifiers) { - case MOD_CTRL: - return key; - case MOD_META: - return key + 128; - default: - break; + case MOD_CTRL: return key; + case MOD_META: return key + 128; } } @@ -53,19 +55,20 @@ const KeyCode min = KEY_SPECIAL_MIN; const KeyCode max = KEY_SPECIAL_MAX; const KeyCode nsk = NR_SPECIAL_KEYS; - BUG_ON(key_lookup_index(MOD_MASK | max) != size - 1); - BUG_ON(key_lookup_index(min) != 256); - BUG_ON(key_lookup_index(max) != 256 + nsk - 1); - BUG_ON(key_lookup_index(MOD_CTRL | min) != 256 + nsk); - BUG_ON(key_lookup_index(MOD_SHIFT | max) != 256 + (5 * nsk) - 1); - - BUG_ON(key_lookup_index(MOD_CTRL | ' ') != 32); - BUG_ON(key_lookup_index(MOD_META | ' ') != 32 + 128); - BUG_ON(key_lookup_index(MOD_CTRL | '~') != 126); - BUG_ON(key_lookup_index(MOD_META | '~') != 126 + 128); + BUG_ON(size != 256 + (8 * NR_SPECIAL_KEYS)); + BUG_ON(get_lookup_table_index(MOD_MASK | max) != size - 1); + BUG_ON(get_lookup_table_index(min) != 256); + BUG_ON(get_lookup_table_index(max) != 256 + nsk - 1); + BUG_ON(get_lookup_table_index(MOD_SHIFT | min) != 256 + nsk); + BUG_ON(get_lookup_table_index(MOD_CTRL | max) != 256 + (5 * nsk) - 1); + + BUG_ON(get_lookup_table_index(MOD_CTRL | KEY_SPACE) != 32); + BUG_ON(get_lookup_table_index(MOD_META | KEY_SPACE) != 32 + 128); + BUG_ON(get_lookup_table_index(MOD_CTRL | '~') != 126); + BUG_ON(get_lookup_table_index(MOD_META | '~') != 126 + 128); - BUG_ON(key_lookup_index(MOD_CTRL | MOD_META | 'a') != -1); - BUG_ON(key_lookup_index(MOD_META | 0x0E01) != -1); + BUG_ON(get_lookup_table_index(MOD_CTRL | MOD_META | 'a') != -1); + BUG_ON(get_lookup_table_index(MOD_META | 0x0E01) != -1); } static KeyBinding *key_binding_new(const char *cmd_str) @@ -76,8 +79,7 @@ memcpy(b->cmd_str, cmd_str, cmd_str_len + 1); PointerArray array = PTR_ARRAY_INIT; - CommandParseError err = 0; - if (!parse_commands(&array, cmd_str, &err)) { + if (parse_commands(&array, cmd_str) != CMDERR_NONE) { goto out; } @@ -87,7 +89,7 @@ goto out; } - const Command *cmd = find_command(commands, array.ptrs[0]); + const Command *cmd = find_normal_command(array.ptrs[0]); if (!cmd) { // Aliases or non-existent commands can't be cached goto out; @@ -100,10 +102,7 @@ free(ptr_array_remove_idx(&array, 0)); CommandArgs a = {.args = (char**)array.ptrs}; - suppress_error_msg(); - bool ok = parse_args(cmd, &a); - unsuppress_error_msg(); - if (!ok) { + if (do_parse_args(cmd, &a) != 0) { goto out; } @@ -130,12 +129,12 @@ void add_binding(const char *keystr, const char *command) { KeyCode key; - if (!parse_key(&key, keystr)) { + if (!parse_key_string(&key, keystr)) { error_msg("invalid key string: %s", keystr); return; } - const ssize_t idx = key_lookup_index(key); + const ssize_t idx = get_lookup_table_index(key); if (idx >= 0) { key_binding_free(bindings_lookup_table[idx]); bindings_lookup_table[idx] = key_binding_new(command); @@ -145,17 +144,17 @@ KeyBindingEntry *b = xnew(KeyBindingEntry, 1); b->key = key; b->bind = key_binding_new(command); - ptr_array_add(&bindings_ptr_array, b); + ptr_array_append(&bindings_ptr_array, b); } void remove_binding(const char *keystr) { KeyCode key; - if (!parse_key(&key, keystr)) { + if (!parse_key_string(&key, keystr)) { return; } - const ssize_t idx = key_lookup_index(key); + const ssize_t idx = get_lookup_table_index(key); if (idx >= 0) { key_binding_free(bindings_lookup_table[idx]); bindings_lookup_table[idx] = NULL; @@ -176,7 +175,7 @@ const KeyBinding *lookup_binding(KeyCode key) { - const ssize_t idx = key_lookup_index(key); + const ssize_t idx = get_lookup_table_index(key); if (idx >= 0) { const KeyBinding *b = bindings_lookup_table[idx]; if (b) { @@ -201,26 +200,77 @@ return; } - if (!b->cmd) { - // Command isn't cached; parse and run command string - handle_command(commands, b->cmd_str); + // If the command isn't cached or a macro is being recorded + if (!b->cmd || macro_is_recording()) { + // Parse and run command string + handle_command(&commands, b->cmd_str, true); return; } // Command is cached; call it directly begin_change(CHANGE_MERGE_NONE); + current_command = b->cmd; b->cmd->cmd(&b->a); + current_command = NULL; end_change(); } +static void maybe_add_key_completion(const char *prefix, KeyCode k) +{ + const char *str = keycode_to_string(k); + if (str_has_prefix(str, prefix)) { + add_completion(xstrdup(str)); + } +} + +static void maybe_add_lt_key_completion(const char *prefix, KeyCode k) +{ + const ssize_t idx = get_lookup_table_index(k); + BUG_ON(idx < 0); + if (bindings_lookup_table[idx]) { + maybe_add_key_completion(prefix, k); + } +} + +void collect_bound_keys(const char *prefix) +{ + for (KeyCode k = 0x20; k < 0x7E; k++) { + maybe_add_lt_key_completion(prefix, MOD_CTRL | k); + } + + for (KeyCode k = 0x20; k < 0x7E; k++) { + maybe_add_lt_key_completion(prefix, MOD_META | k); + } + + static_assert(MOD_MASK >> MOD_OFFSET == 7); + for (KeyCode m = 0, mods = 0; m <= 7; mods = ++m << MOD_OFFSET) { + for (KeyCode k = KEY_SPECIAL_MIN; k <= KEY_SPECIAL_MAX; k++) { + maybe_add_lt_key_completion(prefix, mods | k); + } + } + + for (size_t i = 0, n = bindings_ptr_array.count; i < n; i++) { + const KeyBindingEntry *b = bindings_ptr_array.ptrs[i]; + maybe_add_key_completion(prefix, b->key); + } +} + +static void append_binding(String *s, KeyCode key, const char *cmd) +{ + string_append_literal(s, "bind "); + string_append_escaped_arg(s, keycode_to_string(key), true); + string_append_byte(s, ' '); + string_append_escaped_arg(s, cmd, true); + string_append_byte(s, '\n'); +} + static void append_lookup_table_binding(String *buf, KeyCode key) { - const ssize_t i = key_lookup_index(key); + const ssize_t i = get_lookup_table_index(key); BUG_ON(i < 0); const KeyBinding *b = bindings_lookup_table[i]; if (b) { - const char *keystr = key_to_string(key); - string_sprintf(buf, " %-10s %s\n", keystr, b->cmd_str); + append_binding(buf, key, b->cmd_str); } } @@ -236,17 +286,22 @@ append_lookup_table_binding(&buf, MOD_META | k); } - static_assert(MOD_CTRL == (1 << 24)); - for (KeyCode m = 0, modifiers = 0; m <= 7; modifiers = ++m << 24) { + static_assert(MOD_MASK >> MOD_OFFSET == 7); + for (KeyCode m = 0, modifiers = 0; m <= 7; modifiers = ++m << MOD_OFFSET) { for (KeyCode k = KEY_SPECIAL_MIN; k <= KEY_SPECIAL_MAX; k++) { append_lookup_table_binding(&buf, modifiers | k); } } - for (size_t i = 0, nbinds = bindings_ptr_array.count; i < nbinds; i++) { + size_t n = bindings_ptr_array.count; + if (DEBUG && n) { + // Show a blank line divider in debug mode, to make it easier to + // see which bindings are in the fallback PointerArray + string_append_byte(&buf, '\n'); + } + for (size_t i = 0; i < n; i++) { const KeyBindingEntry *b = bindings_ptr_array.ptrs[i]; - const char *keystr = key_to_string(b->key); - string_sprintf(&buf, " %-10s %s\n", keystr, b->bind->cmd_str); + append_binding(&buf, b->key, b->bind->cmd_str); } return buf; diff -Nru dte-1.9.1/src/bind.h dte-1.10/src/bind.h --- dte-1.9.1/src/bind.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/bind.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,7 @@ #ifndef BIND_H #define BIND_H -#include "command.h" +#include "command/run.h" #include "terminal/key.h" #include "util/string.h" @@ -17,6 +17,7 @@ void remove_binding(const char *keystr); const KeyBinding *lookup_binding(KeyCode key); void handle_binding(KeyCode key); +void collect_bound_keys(const char *keystr_prefix); String dump_bindings(void); #endif diff -Nru dte-1.9.1/src/block.c dte-1.10/src/block.c --- dte-1.9.1/src/block.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/block.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,7 @@ #include "block.h" #include "buffer.h" -#include "debug.h" #include "syntax/highlight.h" +#include "util/debug.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "view.h" @@ -32,7 +32,7 @@ static size_t ALLOC_ROUND(size_t size) { - return ROUND_UP(size, 64); + return round_size_to_next_multiple(size, 64); } Block *block_new(size_t alloc) @@ -51,7 +51,7 @@ free(blk); } -static size_t copy_count_nl(char *dst, const char *const src, size_t len) +static size_t copy_count_nl(char *dst, const char *src, size_t len) { size_t nl = 0; for (size_t i = 0; i < len; i++) { @@ -236,12 +236,11 @@ void do_insert(const char *buf, size_t len) { size_t nl = insert_bytes(buf, len); - buffer->nl += nl; sanity_check(); view_update_cursor_y(view); - buffer_mark_lines_changed(view->buffer, view->cy, nl ? INT_MAX : view->cy); + buffer_mark_lines_changed(buffer, view->cy, nl ? LONG_MAX : view->cy); if (buffer->syn) { hl_insert(buffer, view->cy, nl); } @@ -336,11 +335,7 @@ sanity_check(); view_update_cursor_y(view); - buffer_mark_lines_changed ( - view->buffer, - view->cy, - deleted_nl ? INT_MAX : view->cy - ); + buffer_mark_lines_changed(buffer, view->cy, deleted_nl ? LONG_MAX : view->cy); if (buffer->syn) { hl_delete(buffer, view->cy, deleted_nl); } @@ -393,9 +388,9 @@ view_update_cursor_y(view); if (del_nl == ins_nl) { // Some line(s) changed but lines after them did not move up or down - buffer_mark_lines_changed(view->buffer, view->cy, view->cy + del_nl); + buffer_mark_lines_changed(buffer, view->cy, view->cy + del_nl); } else { - buffer_mark_lines_changed(view->buffer, view->cy, INT_MAX); + buffer_mark_lines_changed(buffer, view->cy, LONG_MAX); } if (buffer->syn) { hl_delete(buffer, view->cy, del_nl); diff -Nru dte-1.9.1/src/block-iter.c dte-1.10/src/block-iter.c --- dte-1.9.1/src/block-iter.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/block-iter.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,6 +1,6 @@ +#include #include "block-iter.h" -#include "debug.h" -#include "util/str-util.h" +#include "util/debug.h" #include "util/utf8.h" #include "util/xmalloc.h" @@ -20,9 +20,8 @@ size_t block_iter_eat_line(BlockIter *bi) { block_iter_normalize(bi); - const size_t offset = bi->offset; - if (offset == bi->blk->size) { + if (unlikely(offset == bi->blk->size)) { return 0; } @@ -32,7 +31,8 @@ } else { const unsigned char *end; end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); - bi->offset = end + 1 - bi->blk->data; + BUG_ON(!end); + bi->offset = (size_t)(end + 1 - bi->blk->data); } return bi->offset - offset; @@ -46,9 +46,8 @@ size_t block_iter_next_line(BlockIter *bi) { block_iter_normalize(bi); - const size_t offset = bi->offset; - if (offset == bi->blk->size) { + if (unlikely(offset == bi->blk->size)) { return 0; } @@ -59,7 +58,8 @@ } else { const unsigned char *end; end = memchr(bi->blk->data + offset, '\n', bi->blk->size - offset); - new_offset = end + 1 - bi->blk->data; + BUG_ON(!end); + new_offset = (size_t)(end + 1 - bi->blk->data); } if (new_offset == bi->blk->size && bi->blk->node.next == bi->head) { return 0; @@ -100,7 +100,7 @@ return start - offset; } -size_t block_iter_get_char(BlockIter *bi, CodePoint *up) +size_t block_iter_get_char(const BlockIter *bi, CodePoint *up) { BlockIter tmp = *bi; return block_iter_next_char(&tmp, up); @@ -109,9 +109,8 @@ size_t block_iter_next_char(BlockIter *bi, CodePoint *up) { size_t offset = bi->offset; - - if (offset == bi->blk->size) { - if (bi->blk->node.next == bi->head) { + if (unlikely(offset == bi->blk->size)) { + if (unlikely(bi->blk->node.next == bi->head)) { return 0; } bi->blk = BLOCK(bi->blk->node.next); @@ -120,7 +119,7 @@ // Note: this block can't be empty *up = bi->blk->data[offset]; - if (*up < 0x80) { + if (likely(*up < 0x80)) { bi->offset++; return 1; } @@ -132,9 +131,8 @@ size_t block_iter_prev_char(BlockIter *bi, CodePoint *up) { size_t offset = bi->offset; - - if (!offset) { - if (bi->blk->node.prev == bi->head) { + if (unlikely(offset == 0)) { + if (unlikely(bi->blk->node.prev == bi->head)) { return 0; } bi->blk = BLOCK(bi->blk->node.prev); @@ -143,7 +141,7 @@ // Note: this block can't be empty *up = bi->blk->data[offset - 1]; - if (*up < 0x80) { + if (likely(*up < 0x80)) { bi->offset--; return 1; } @@ -176,9 +174,8 @@ size_t block_iter_bol(BlockIter *bi) { block_iter_normalize(bi); - size_t offset = bi->offset; - if (!offset || offset == bi->blk->size) { + if (offset == 0 || offset == bi->blk->size) { return 0; } @@ -200,7 +197,7 @@ block_iter_normalize(bi); const Block *blk = bi->blk; const size_t offset = bi->offset; - if (offset == blk->size) { + if (unlikely(offset == blk->size)) { // Cursor at end of last block return 0; } @@ -208,9 +205,9 @@ bi->offset = blk->size - 1; return bi->offset - offset; } - const unsigned char *end; - end = memchr(blk->data + offset, '\n', blk->size - offset); - bi->offset = end - blk->data; + const unsigned char *end = memchr(blk->data + offset, '\n', blk->size - offset); + BUG_ON(!end); + bi->offset = (size_t)(end - blk->data); return bi->offset - offset; } @@ -281,15 +278,6 @@ return offset + bi->offset; } -bool block_iter_is_bol(const BlockIter *bi) -{ - const size_t offset = bi->offset; - if (offset == 0) { - return true; - } - return bi->blk->data[offset - 1] == '\n'; -} - char *block_iter_get_bytes(const BlockIter *bi, size_t len) { if (len == 0) { @@ -304,13 +292,11 @@ while (pos < len) { const size_t avail = blk->size - offset; size_t count = len - pos; - if (count > avail) { count = avail; } memcpy(buf + pos, blk->data + offset, count); pos += count; - BUG_ON(pos < len && blk->node.next == bi->head); blk = BLOCK(blk->node.next); offset = 0; @@ -320,46 +306,48 @@ } // bi should be at bol -void fill_line_ref(BlockIter *bi, LineRef *lr) +void fill_line_ref(BlockIter *bi, StringView *line) { block_iter_normalize(bi); - lr->line = bi->blk->data + bi->offset; + line->data = bi->blk->data + bi->offset; const size_t max = bi->blk->size - bi->offset; - if (max == 0) { + if (unlikely(max == 0)) { // Cursor at end of last block - lr->size = 0; + line->length = 0; return; } if (bi->blk->nl == 1) { - lr->size = max - 1; + line->length = max - 1; return; } - const unsigned char *nl = memchr(lr->line, '\n', max); - lr->size = nl - lr->line; + const unsigned char *nl = memchr(line->data, '\n', max); + BUG_ON(!nl); + line->length = (size_t)(nl - line->data); } -void fill_line_nl_ref(BlockIter *bi, LineRef *lr) +void fill_line_nl_ref(BlockIter *bi, StringView *line) { block_iter_normalize(bi); - lr->line = bi->blk->data + bi->offset; + line->data = bi->blk->data + bi->offset; const size_t max = bi->blk->size - bi->offset; - if (max == 0) { + if (unlikely(max == 0)) { // Cursor at end of last block - lr->size = 0; + line->length = 0; return; } if (bi->blk->nl == 1) { - lr->size = max; + line->length = max; return; } - const unsigned char *nl = memchr(lr->line, '\n', max); - lr->size = nl - lr->line + 1; + const unsigned char *nl = memchr(line->data, '\n', max); + BUG_ON(!nl); + line->length = (size_t)(nl - line->data + 1); } -size_t fetch_this_line(const BlockIter *bi, LineRef *lr) +size_t fetch_this_line(const BlockIter *bi, StringView *line) { BlockIter tmp = *bi; const size_t count = block_iter_bol(&tmp); - fill_line_ref(&tmp, lr); + fill_line_ref(&tmp, line); return count; } diff -Nru dte-1.9.1/src/block-iter.h dte-1.10/src/block-iter.h --- dte-1.9.1/src/block-iter.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/block-iter.h 2021-04-03 21:08:53.000000000 +0000 @@ -4,20 +4,17 @@ #include #include #include "block.h" +#include "util/list.h" #include "util/macros.h" +#include "util/string-view.h" #include "util/unicode.h" typedef struct { Block *blk; - ListHead *head; + const ListHead *head; size_t offset; } BlockIter; -typedef struct { - const unsigned char NONSTRING *line; - size_t size; -} LineRef; - #define BLOCK_ITER_INIT(head_) { \ .blk = BLOCK((head_)->next), \ .head = (head_), \ @@ -36,16 +33,20 @@ bi->offset = bi->blk->size; } -static inline bool block_iter_is_eof(const BlockIter *const bi) +static inline bool block_iter_is_eof(const BlockIter *bi) { return bi->offset == bi->blk->size && bi->blk->node.next == bi->head; } +static inline bool block_iter_is_bol(const BlockIter *bi) +{ + return bi->offset == 0 || bi->blk->data[bi->offset - 1] == '\n'; +} + void block_iter_normalize(BlockIter *bi); size_t block_iter_eat_line(BlockIter *bi); size_t block_iter_next_line(BlockIter *bi); size_t block_iter_prev_line(BlockIter *bi); -size_t block_iter_get_char(BlockIter *bi, CodePoint *up); size_t block_iter_next_char(BlockIter *bi, CodePoint *up); size_t block_iter_prev_char(BlockIter *bi, CodePoint *up); size_t block_iter_next_column(BlockIter *bi); @@ -56,12 +57,12 @@ void block_iter_skip_bytes(BlockIter *bi, size_t count); void block_iter_goto_offset(BlockIter *bi, size_t offset); void block_iter_goto_line(BlockIter *bi, size_t line); -size_t block_iter_get_offset(const BlockIter *bi); -bool block_iter_is_bol(const BlockIter *bi); -char *block_iter_get_bytes(const BlockIter *bi, size_t len); - -void fill_line_ref(BlockIter *bi, LineRef *lr); -void fill_line_nl_ref(BlockIter *bi, LineRef *lr); -size_t fetch_this_line(const BlockIter *bi, LineRef *lr); +size_t block_iter_get_offset(const BlockIter *bi) WARN_UNUSED_RESULT; +size_t block_iter_get_char(const BlockIter *bi, CodePoint *up) WARN_UNUSED_RESULT; +char *block_iter_get_bytes(const BlockIter *bi, size_t len) WARN_UNUSED_RESULT; + +void fill_line_ref(BlockIter *bi, StringView *line); +void fill_line_nl_ref(BlockIter *bi, StringView *line); +size_t fetch_this_line(const BlockIter *bi, StringView *line); #endif diff -Nru dte-1.9.1/src/buffer.c dte-1.10/src/buffer.c --- dte-1.9.1/src/buffer.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/buffer.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,9 @@ #include #include +#include #include #include "block.h" +#include "block-iter.h" #include "buffer.h" #include "editor.h" #include "file-option.h" @@ -15,9 +17,8 @@ #include "util/xmalloc.h" Buffer *buffer; -PointerArray buffers = PTR_ARRAY_INIT; -static void set_display_filename(Buffer *b, char *name) +void set_display_filename(Buffer *b, char *name) { free(b->display_filename); b->display_filename = name; @@ -49,22 +50,38 @@ const char *buffer_filename(const Buffer *b) { - return b->display_filename; + return b->display_filename ? b->display_filename : "(No name)"; +} + +void buffer_set_encoding(Buffer *b, Encoding encoding) +{ + if ( + b->encoding.type != encoding.type + || b->encoding.name != encoding.name + ) { + const EncodingType type = encoding.type; + if (type == UTF8) { + b->bom = editor.options.utf8_bom; + } else { + b->bom = type < NR_ENCODING_TYPES && get_bom_for_encoding(type); + } + b->encoding = encoding; + } } Buffer *buffer_new(const Encoding *encoding) { - static unsigned int id; + static unsigned long id; Buffer *b = xnew0(Buffer, 1); list_init(&b->blocks); b->cur_change = &b->change_head; b->saved_change = &b->change_head; b->id = ++id; - b->newline = editor.options.newline; + b->crlf_newlines = editor.options.crlf_newlines; if (encoding) { - b->encoding = *encoding; + buffer_set_encoding(b, *encoding); } else { b->encoding.type = ENCODING_AUTODETECT; } @@ -74,7 +91,7 @@ b->options.filetype = str_intern("none"); b->options.indent_regex = NULL; - ptr_array_add(&buffers, b); + ptr_array_append(&editor.buffers, b); return b; } @@ -86,53 +103,56 @@ Block *blk = block_new(1); list_add_before(&blk->node, &b->blocks); - set_display_filename(b, xmemdup_literal("(No name)")); return b; } -void free_buffer(Buffer *b) +void free_blocks(Buffer *b) { - ptr_array_remove(&buffers, b); - - if (b->locked) { - unlock_file(b->abs_filename); - } - ListHead *item = b->blocks.next; while (item != &b->blocks) { ListHead *next = item->next; Block *blk = BLOCK(item); - free(blk->data); free(blk); item = next; } +} + +void free_buffer(Buffer *b) +{ + ptr_array_remove(&editor.buffers, b); + + if (b->locked) { + unlock_file(b->abs_filename); + } + free_changes(&b->change_head); free(b->line_start_states.ptrs); free(b->views.ptrs); free(b->display_filename); free(b->abs_filename); + + if (b->stdout_buffer) { + return; + } + + free_blocks(b); free(b); } -static bool same_file(const struct stat *const a, const struct stat *const b) +static bool same_file(const Buffer *b, const struct stat *st) { - return a->st_dev == b->st_dev && a->st_ino == b->st_ino; + return (st->st_dev == b->file.dev) && (st->st_ino == b->file.ino); } Buffer *find_buffer(const char *abs_filename) { struct stat st; bool st_ok = stat(abs_filename, &st) == 0; - - for (size_t i = 0; i < buffers.count; i++) { - Buffer *b = buffers.ptrs[i]; + for (size_t i = 0, n = editor.buffers.count; i < n; i++) { + Buffer *b = editor.buffers.ptrs[i]; const char *f = b->abs_filename; - - if ( - (f != NULL && streq(f, abs_filename)) - || (st_ok && same_file(&st, &b->st)) - ) { + if ((f && streq(f, abs_filename)) || (st_ok && same_file(b, &st))) { return b; } } @@ -141,8 +161,8 @@ Buffer *find_buffer_by_id(unsigned long id) { - for (size_t i = 0; i < buffers.count; i++) { - Buffer *b = buffers.ptrs[i]; + for (size_t i = 0, n = editor.buffers.count; i < n; i++) { + Buffer *b = editor.buffers.ptrs[i]; if (b->id == id) { return b; } @@ -152,54 +172,53 @@ bool buffer_detect_filetype(Buffer *b) { - const char *ft = NULL; + StringView line = STRING_VIEW_INIT; if (BLOCK(b->blocks.next)->size) { BlockIter bi = BLOCK_ITER_INIT(&b->blocks); - LineRef lr; - fill_line_ref(&bi, &lr); - const StringView line = string_view(lr.line, lr.size); - ft = find_ft(b->abs_filename, line); - } else if (b->abs_filename) { - const StringView line = STRING_VIEW_INIT; - ft = find_ft(b->abs_filename, line); + fill_line_ref(&bi, &line); + } else if (!b->abs_filename) { + return false; } + const char *ft = find_ft(b->abs_filename, line); if (ft && !streq(ft, b->options.filetype)) { b->options.filetype = str_intern(ft); return true; } + return false; } static char *short_filename_cwd(const char *absolute, const char *cwd) { - char *f = relative_filename(absolute, cwd); - size_t home_len = strlen(editor.home_dir); + char *relative = relative_filename(absolute, cwd); + size_t home_len = editor.home_dir.length; size_t abs_len = strlen(absolute); - size_t f_len = strlen(f); + size_t rel_len = strlen(relative); - if (f_len >= abs_len) { + if (rel_len >= abs_len) { // Prefer absolute if relative isn't shorter - free(f); - f = xstrdup(absolute); - f_len = abs_len; + free(relative); + relative = xstrdup(absolute); + rel_len = abs_len; } if ( abs_len > home_len - && !memcmp(absolute, editor.home_dir, home_len) + && mem_equal(absolute, editor.home_dir.data, home_len) && absolute[home_len] == '/' ) { size_t len = abs_len - home_len + 1; - if (len < f_len) { + if (len < rel_len) { char *filename = xmalloc(len + 1); filename[0] = '~'; memcpy(filename + 1, absolute + home_len, len); - free(f); + free(relative); return filename; } } - return f; + + return relative; } char *short_filename(const char *absolute) @@ -248,10 +267,9 @@ // Start state of first line is constant PointerArray *s = &b->line_start_states; if (!s->alloc) { - s->alloc = 64; - s->ptrs = xnew(void *, s->alloc); + ptr_array_init(s, 64); } - s->ptrs[0] = syn->states.ptrs[0]; + s->ptrs[0] = syn->start_state; s->count = 1; } @@ -321,12 +339,10 @@ int space_count = 0; for (unsigned int i = 0; i < 200; i++) { - LineRef lr; - int indent; + StringView line; + fill_line_ref(&bi, &line); bool tab; - - fill_line_ref(&bi, &lr); - indent = indent_len(b, lr.line, lr.size, &tab); + int indent = indent_len(b, line.data, line.length, &tab); if (indent == -2) { // Ignore mixed indent because tab width might not be 8 } else if (indent == -1) { @@ -360,40 +376,45 @@ break; } } + if (tab_count == 0 && space_count == 0) { return false; } + if (tab_count > space_count) { b->options.emulate_tab = false; b->options.expand_tab = false; b->options.indent_width = b->options.tab_width; - } else { - size_t m = 0; - for (size_t i = 1; i < ARRAY_COUNT(counts); i++) { - if (b->options.detect_indent & 1 << (i - 1)) { - if (counts[i] > counts[m]) { - m = i; - } - } - } - if (m == 0) { - return false; + return true; + } + + size_t m = 0; + for (size_t i = 1; i < ARRAY_COUNT(counts); i++) { + unsigned int bit = 1u << (i - 1); + if ((b->options.detect_indent & bit) && counts[i] > counts[m]) { + m = i; } - b->options.emulate_tab = true; - b->options.expand_tab = true; - b->options.indent_width = m; } + + if (m == 0) { + return false; + } + + b->options.emulate_tab = true; + b->options.expand_tab = true; + b->options.indent_width = m; return true; } void buffer_setup(Buffer *b) { + const char *filename = b->abs_filename; b->setup = true; buffer_detect_filetype(b); set_file_options(b); set_editorconfig_options(b); buffer_update_syntax(b); - if (b->options.detect_indent && b->abs_filename != NULL) { + if (b->options.detect_indent && filename) { detect_indent(b); } } diff -Nru dte-1.9.1/src/buffer.h dte-1.10/src/buffer.h --- dte-1.9.1/src/buffer.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/buffer.h 2021-04-03 21:08:53.000000000 +0000 @@ -4,16 +4,25 @@ #include #include #include -#include -#include "block-iter.h" +#include #include "change.h" -#include "encoding/encoding.h" +#include "encoding.h" #include "options.h" #include "syntax/syntax.h" #include "util/list.h" #include "util/macros.h" #include "util/ptr-array.h" -#include "util/unicode.h" + +// Subset of stat(3) struct +typedef struct { + off_t size; + mode_t mode; + gid_t gid; + uid_t uid; + dev_t dev; + ino_t ino; + time_t mtime; +} FileInfo; typedef struct Buffer { ListHead blocks; @@ -23,7 +32,7 @@ // Used to determine if buffer is modified Change *saved_change; - struct stat st; + FileInfo file; // Needed for identifying buffers whose filename is NULL unsigned long id; @@ -37,12 +46,14 @@ char *abs_filename; bool readonly; + bool temporary; + bool stdout_buffer; bool locked; bool setup; + bool crlf_newlines; + bool bom; - LineEndingType newline; - - // Encoding of the file. Buffer always contains UTF-8. + // Encoding of the file (buffer always contains UTF-8) Encoding encoding; LocalOptions options; @@ -59,32 +70,33 @@ // buffer = view->buffer = window->view->buffer extern struct View *view; extern Buffer *buffer; -extern PointerArray buffers; static inline void mark_all_lines_changed(Buffer *b) { b->changed_line_min = 0; - b->changed_line_max = INT_MAX; + b->changed_line_max = LONG_MAX; } static inline bool buffer_modified(const Buffer *b) { - return b->saved_change != b->cur_change; + return b->saved_change != b->cur_change && !b->temporary; } -void buffer_mark_lines_changed(Buffer *b, long min, long max); -const char *buffer_filename(const Buffer *b); - +void buffer_mark_lines_changed(Buffer *b, long min, long max) NONNULL_ARGS; +void buffer_set_encoding(Buffer *b, Encoding encoding) NONNULL_ARGS; +const char *buffer_filename(const Buffer *b) NONNULL_ARGS_AND_RETURN; +void set_display_filename(Buffer *b, char *name) NONNULL_ARG(1); char *short_filename(const char *absolute) XSTRDUP; -void update_short_filename_cwd(Buffer *b, const char *cwd); -void update_short_filename(Buffer *b); -Buffer *find_buffer(const char *abs_filename); +void update_short_filename_cwd(Buffer *b, const char *cwd) NONNULL_ARG(1); +void update_short_filename(Buffer *b) NONNULL_ARGS; +Buffer *find_buffer(const char *abs_filename) NONNULL_ARGS; Buffer *find_buffer_by_id(unsigned long id); -Buffer *buffer_new(const Encoding *encoding); -Buffer *open_empty_buffer(void); -void free_buffer(Buffer *b); -bool buffer_detect_filetype(Buffer *b); -void buffer_update_syntax(Buffer *b); -void buffer_setup(Buffer *b); +Buffer *buffer_new(const Encoding *encoding) RETURNS_NONNULL; +Buffer *open_empty_buffer(void) RETURNS_NONNULL; +void free_buffer(Buffer *b) NONNULL_ARGS; +void free_blocks(Buffer *b) NONNULL_ARGS; +bool buffer_detect_filetype(Buffer *b) NONNULL_ARGS; +void buffer_update_syntax(Buffer *b) NONNULL_ARGS; +void buffer_setup(Buffer *b) NONNULL_ARGS; #endif diff -Nru dte-1.9.1/src/change.c dte-1.10/src/change.c --- dte-1.9.1/src/change.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/change.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,7 @@ #include "change.h" #include "buffer.h" -#include "debug.h" #include "error.h" +#include "util/debug.h" #include "util/xmalloc.h" #include "view.h" @@ -16,11 +16,9 @@ static void add_change(Change *change) { Change *head = buffer->cur_change; - change->next = head; xrenew(head->prev, head->nr_prev + 1); head->prev[head->nr_prev++] = change; - buffer->cur_change = change; } @@ -61,7 +59,6 @@ static void record_insert(size_t len) { Change *change = buffer->cur_change; - BUG_ON(!len); if ( change_merge == prev_change_merge @@ -191,7 +188,6 @@ size_t del_count = change->ins_count; size_t ins_count = change->del_count; char *buf = do_replace(del_count, change->buf, ins_count); - free(change->buf); change->buf = buf; change->ins_count = ins_count; @@ -207,7 +203,6 @@ bool undo(void) { Change *change = buffer->cur_change; - view_reset_preferred_x(view); if (!change->next) { return false; @@ -215,7 +210,6 @@ if (is_change_chain_barrier(change)) { unsigned long count = 0; - while (1) { change = change->next; if (is_change_chain_barrier(change)) { @@ -230,6 +224,7 @@ } else { reverse_change(change); } + buffer->cur_change = change->next; return true; } @@ -237,7 +232,6 @@ bool redo(unsigned long change_id) { Change *change = buffer->cur_change; - view_reset_preferred_x(view); if (!change->prev) { // Don't complain if change_id is 0 @@ -247,30 +241,25 @@ return false; } - if (change_id) { - if (--change_id >= change->nr_prev) { - error_msg ( - "There are only %lu possible changes to redo.", - change->nr_prev - ); - return false; + const unsigned long nr_prev = change->nr_prev; + BUG_ON(nr_prev == 0); + if (change_id == 0) { + // Default to newest change + change_id = nr_prev - 1; + if (nr_prev > 1) { + unsigned long i = change_id + 1; + info_msg("Redoing newest (%lu) of %lu possible changes.", i, nr_prev); } } else { - // Default to newest change - change_id = change->nr_prev - 1; - if (change->nr_prev > 1) { - info_msg ( - "Redoing newest (%lu) of %lu possible changes.", - change_id + 1, - change->nr_prev - ); + if (--change_id >= nr_prev) { + error_msg("There are only %lu possible changes to redo.", nr_prev); + return false; } } change = change->prev[change_id]; if (is_change_chain_barrier(change)) { unsigned long count = 0; - while (1) { change = change->prev[change->nr_prev - 1]; if (is_change_chain_barrier(change)) { @@ -285,6 +274,7 @@ } else { reverse_change(change); } + buffer->cur_change = change; return true; } @@ -299,7 +289,6 @@ // ch is leaf now while (ch->next) { Change *next = ch->next; - free(ch->buf); free(ch); @@ -315,13 +304,12 @@ void buffer_insert_bytes(const char *buf, const size_t len) { - size_t rec_len = len; - view_reset_preferred_x(view); if (len == 0) { return; } + size_t rec_len = len; if (buf[len - 1] != '\n' && block_iter_is_eof(&view->cursor)) { // Force newline at EOF do_insert("\n", 1); @@ -343,7 +331,6 @@ while (1) { size_t avail = blk->size - offset; - if (avail > count) { return false; } @@ -394,14 +381,11 @@ buffer_delete_bytes_internal(len, true); } -void buffer_replace_bytes ( - size_t del_count, - const char *const inserted, - size_t ins_count -) { +void buffer_replace_bytes(size_t del_count, const char *ins, size_t ins_count) +{ view_reset_preferred_x(view); if (del_count == 0) { - buffer_insert_bytes(inserted, ins_count); + buffer_insert_bytes(ins, ins_count); return; } if (ins_count == 0) { @@ -411,16 +395,16 @@ // Check if all newlines from EOF would be deleted if (would_delete_last_bytes(del_count)) { - if (inserted[ins_count - 1] != '\n') { + if (ins[ins_count - 1] != '\n') { // Don't replace last newline if (--del_count == 0) { - buffer_insert_bytes(inserted, ins_count); + buffer_insert_bytes(ins, ins_count); return; } } } - char *deleted = do_replace(del_count, inserted, ins_count); + char *deleted = do_replace(del_count, ins, ins_count); record_replace(deleted, del_count, ins_count); if (buffer->views.count > 1) { diff -Nru dte-1.9.1/src/cmdline.c dte-1.10/src/cmdline.c --- dte-1.9.1/src/cmdline.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/cmdline.c 2021-04-03 21:08:53.000000000 +0000 @@ -145,14 +145,6 @@ c->pos = i; } -static void cmdline_insert_bytes(CommandLine *c, const char *buf, size_t size) -{ - string_make_space(&c->buf, c->pos, size); - for (size_t i = 0; i < size; i++) { - c->buf.buffer[c->pos++] = buf[i]; - } -} - static void cmdline_insert_paste(CommandLine *c) { size_t size; @@ -162,7 +154,8 @@ text[i] = ' '; } } - cmdline_insert_bytes(c, text, size); + string_insert_buf(&c->buf, c->pos, text, size); + c->pos += size; free(text); } @@ -170,7 +163,7 @@ { string_clear(&c->buf); const size_t text_len = strlen(text); - string_add_buf(&c->buf, text, text_len); + string_append_buf(&c->buf, text, text_len); c->pos = text_len; } @@ -178,82 +171,76 @@ { string_clear(&c->buf); c->pos = 0; - c->search_pos = -1; + c->search_pos = NULL; } void cmdline_set_text(CommandLine *c, const char *text) { set_text(c, text); - c->search_pos = -1; + c->search_pos = NULL; } -CommandLineResult cmdline_handle_key ( - CommandLine *c, - PointerArray *history, - KeyCode key -) { +CommandLineResult cmdline_handle_key(CommandLine *c, History *hist, KeyCode key) +{ if (key <= KEY_UNICODE_MAX) { c->pos += string_insert_ch(&c->buf, c->pos, key); return CMDLINE_KEY_HANDLED; } + switch (key) { case CTRL('['): // ESC case CTRL('C'): case CTRL('G'): cmdline_clear(c); return CMDLINE_CANCEL; + + case KEY_DELETE: case CTRL('D'): cmdline_delete(c); goto reset_search_pos; + case CTRL('K'): cmdline_delete_eol(c); goto reset_search_pos; + case CTRL('H'): case CTRL('?'): if (c->buf.len > 0) { cmdline_backspace(c); } goto reset_search_pos; + case CTRL('U'): cmdline_delete_bol(c); goto reset_search_pos; + case CTRL('W'): case MOD_META | MOD_CTRL | 'H': case MOD_META | MOD_CTRL | '?': cmdline_erase_word(c); goto reset_search_pos; + case MOD_CTRL | KEY_DELETE: case MOD_META | KEY_DELETE: case MOD_META | 'd': cmdline_delete_word(c); goto reset_search_pos; - case CTRL('A'): - c->pos = 0; - goto handled; + case KEY_LEFT: case CTRL('B'): cmdline_prev_char(c); goto handled; - case CTRL('E'): - c->pos = c->buf.len; - goto handled; - case CTRL('F'): - cmdline_next_char(c); - goto handled; - case KEY_DELETE: - cmdline_delete(c); - goto reset_search_pos; - case KEY_LEFT: - cmdline_prev_char(c); - goto handled; case KEY_RIGHT: + case CTRL('F'): cmdline_next_char(c); goto handled; + case CTRL(KEY_LEFT): case MOD_META | 'b': cmdline_prev_word(c); goto handled; + case CTRL(KEY_RIGHT): case MOD_META | 'f': cmdline_next_word(c); @@ -261,48 +248,54 @@ case KEY_HOME: case MOD_META | KEY_LEFT: + case CTRL('A'): c->pos = 0; goto handled; + case KEY_END: case MOD_META | KEY_RIGHT: + case CTRL('E'): c->pos = c->buf.len; goto handled; + case KEY_UP: - if (history == NULL) { + if (!hist) { return CMDLINE_UNKNOWN_KEY; } - if (c->search_pos < 0) { + if (!c->search_pos) { free(c->search_text); c->search_text = string_clone_cstring(&c->buf); - c->search_pos = history->count; } - if (history_search_forward(history, &c->search_pos, c->search_text)) { - set_text(c, history->ptrs[c->search_pos]); + if (history_search_forward(hist, &c->search_pos, c->search_text)) { + set_text(c, c->search_pos->text); } goto handled; + case KEY_DOWN: - if (history == NULL) { + if (!hist) { return CMDLINE_UNKNOWN_KEY; } - if (c->search_pos < 0) { + if (!c->search_pos) { goto handled; } - if (history_search_backward(history, &c->search_pos, c->search_text)) { - set_text(c, history->ptrs[c->search_pos]); + if (history_search_backward(hist, &c->search_pos, c->search_text)) { + set_text(c, c->search_pos->text); } else { set_text(c, c->search_text); - c->search_pos = -1; + c->search_pos = NULL; } goto handled; + case KEY_PASTE: cmdline_insert_paste(c); goto reset_search_pos; + default: return CMDLINE_UNKNOWN_KEY; } reset_search_pos: - c->search_pos = -1; + c->search_pos = NULL; handled: return CMDLINE_KEY_HANDLED; } diff -Nru dte-1.9.1/src/cmdline.h dte-1.10/src/cmdline.h --- dte-1.9.1/src/cmdline.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/cmdline.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,14 +2,14 @@ #define CMDLINE_H #include +#include "history.h" #include "terminal/key.h" -#include "util/ptr-array.h" #include "util/string.h" typedef struct { String buf; size_t pos; - ssize_t search_pos; + const HistoryEntry *search_pos; char *search_text; } CommandLine; @@ -21,11 +21,6 @@ void cmdline_clear(CommandLine *c); void cmdline_set_text(CommandLine *c, const char *text); - -CommandLineResult cmdline_handle_key ( - CommandLine *c, - PointerArray *history, - KeyCode key -); +CommandLineResult cmdline_handle_key(CommandLine *c, History *hist, KeyCode key); #endif diff -Nru dte-1.9.1/src/command/args.c dte-1.10/src/command/args.c --- dte-1.9.1/src/command/args.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/args.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,152 @@ +#include +#include "args.h" +#include "error.h" +#include "util/str-util.h" + +/* + * Flags and first "--" are removed. + * Flag arguments are moved to beginning. + * Other arguments come right after flag arguments. + * + * a->args field should be set before calling. + * If parsing succeeds, the other fields are set and 0 is returned. + */ +ArgParseError do_parse_args(const Command *cmd, CommandArgs *a) +{ + char **args = a->args; + BUG_ON(!args); + + size_t argc = string_array_length(args); + size_t nr_flags = 0; + size_t nr_flag_args = 0; + + const char *flag_desc = cmd->flags; + bool flags_after_arg = true; + if (flag_desc[0] == '-') { + flag_desc++; + flags_after_arg = false; + } + + for (size_t i = 0; args[i]; ) { + char *arg = args[i]; + if (unlikely(streq(arg, "--"))) { + // Move the NULL too + memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); + free(arg); + argc--; + break; + } + + if (arg[0] != '-' || arg[1] == '\0') { + if (!flags_after_arg) { + break; + } + i++; + continue; + } + + for (size_t j = 1; arg[j]; j++) { + unsigned char flag = arg[j]; + const char *flagp = strchr(flag_desc, flag); + if (unlikely(!flagp || flag == '=')) { + return ARGERR_INVALID_OPTION | (flag << 8); + } + + a->flag_set |= UINT64_C(1) << cmdargs_flagset_idx(flag); + a->flags[nr_flags++] = flag; + if (unlikely(nr_flags == ARRAY_COUNT(a->flags))) { + return ARGERR_TOO_MANY_OPTIONS; + } + + if (likely(flagp[1] != '=')) { + continue; + } + + if (unlikely(j > 1 || arg[j + 1])) { + return ARGERR_OPTION_ARGUMENT_NOT_SEPARATE | (flag << 8); + } + + char *flag_arg = args[i + 1]; + if (unlikely(!flag_arg)) { + return ARGERR_OPTION_ARGUMENT_MISSING | (flag << 8); + } + + // Move flag argument before any other arguments + if (i != nr_flag_args) { + // farg1 arg1 arg2 -f farg2 arg3 + // farg1 farg2 arg1 arg2 arg3 + size_t count = i - nr_flag_args; + char **ptr = args + nr_flag_args; + memmove(ptr + 1, ptr, count * sizeof(*args)); + } + args[nr_flag_args++] = flag_arg; + i++; + } + + memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); + free(arg); + argc--; + } + + a->flags[nr_flags] = '\0'; + a->nr_flags = nr_flags; + a->nr_args = argc - nr_flag_args; + a->nr_flag_args = nr_flag_args; + + if (unlikely(a->nr_args < cmd->min_args)) { + return ARGERR_TOO_FEW_ARGUMENTS; + } + + static_assert_compatible_types(cmd->max_args, uint8_t); + if (unlikely(a->nr_args > cmd->max_args && cmd->max_args != 0xFF)) { + return ARGERR_TOO_MANY_ARGUMENTS; + } + + return 0; +} + +bool parse_args(const Command *cmd, CommandArgs *a) +{ + ArgParseError err = do_parse_args(cmd, a); + if (likely(err == 0)) { + return true; + } + + ArgParseErrorType err_type = err & 0xFF; + unsigned char flag = (err >> 8) & 0xFF; + switch (err_type) { + case ARGERR_INVALID_OPTION: + error_msg("Invalid option -%c", flag); + break; + case ARGERR_TOO_MANY_OPTIONS: + error_msg("Too many options given"); + break; + case ARGERR_OPTION_ARGUMENT_NOT_SEPARATE: + error_msg ( + "Flag -%c must be given separately because it" + " requires an argument", + flag + ); + break; + case ARGERR_OPTION_ARGUMENT_MISSING: + error_msg("Option -%c requires an argument", flag); + break; + case ARGERR_TOO_FEW_ARGUMENTS: + error_msg ( + "Too few arguments (got: %zu, minimum: %u)", + a->nr_args, + (unsigned int)cmd->min_args + ); + break; + case ARGERR_TOO_MANY_ARGUMENTS: + error_msg ( + "Too many arguments (got: %zu, maximum: %u)", + a->nr_args, + (unsigned int)cmd->max_args + ); + break; + default: + BUG("unhandled error type"); + } + return false; +} diff -Nru dte-1.9.1/src/command/args.h dte-1.10/src/command/args.h --- dte-1.9.1/src/command/args.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/args.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,45 @@ +#ifndef COMMAND_ARGS_H +#define COMMAND_ARGS_H + +#include +#include "run.h" +#include "util/base64.h" +#include "util/debug.h" +#include "util/macros.h" + +typedef enum { + ARGERR_INVALID_OPTION = 1, + ARGERR_TOO_MANY_OPTIONS, + ARGERR_OPTION_ARGUMENT_NOT_SEPARATE, + ARGERR_OPTION_ARGUMENT_MISSING, + ARGERR_TOO_FEW_ARGUMENTS, + ARGERR_TOO_MANY_ARGUMENTS, +} ArgParseErrorType; + +// Success: 0 +// Failure: (ARGERR_* | (flag << 8)) +typedef unsigned int ArgParseError; + +// Map ASCII alphanumeric characters to values between 1 and 62, +// for use as bitset indices in CommandArgs::flag_set +static inline unsigned int cmdargs_flagset_idx(unsigned char flag) +{ + // We use base64_decode() here simply because it does a similar byte + // mapping to the one we want and allows us to share the lookup table. + // There's no "real" base64 encoding involved. + unsigned int idx = base64_decode(flag) + 1; + BUG_ON(idx > 62); + return idx; +} + +static inline bool cmdargs_has_flag(const CommandArgs *a, unsigned char flag) +{ + uint64_t bitmask = UINT64_C(1) << cmdargs_flagset_idx(flag); + static_assert_compatible_types(bitmask, a->flag_set); + return (a->flag_set & bitmask) != 0; +} + +bool parse_args(const Command *cmd, CommandArgs *a) NONNULL_ARGS; +ArgParseError do_parse_args(const Command *cmd, CommandArgs *a) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/command/env.c dte-1.10/src/command/env.c --- dte-1.9.1/src/command/env.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/env.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,108 @@ +#include +#include "env.h" +#include "buffer.h" +#include "completion.h" +#include "editor.h" +#include "error.h" +#include "selection.h" +#include "util/numtostr.h" +#include "util/str-util.h" +#include "util/xmalloc.h" +#include "view.h" + +typedef struct { + const char *name; + char *(*expand)(void); +} BuiltinEnv; + +static char *expand_dte_home(void) +{ + return xstrdup(editor.user_config_dir); +} + +static char *expand_file(void) +{ + if (!buffer || !buffer->abs_filename) { + return NULL; + } + return xstrdup(buffer->abs_filename); +} + +static char *expand_filetype(void) +{ + return buffer ? xstrdup(buffer->options.filetype) : NULL; +} + +static char *expand_lineno(void) +{ + return view ? xstrdup(umax_to_str(view->cy + 1)) : NULL; +} + +static char *expand_word(void) +{ + if (!view) { + return NULL; + } + + size_t size; + char *selection = view_get_selection(view, &size); + if (selection) { + xrenew(selection, size + 1); + selection[size] = '\0'; + return selection; + } + + StringView word = view_get_word_under_cursor(view); + return word.length ? xstrcut(word.data, word.length) : NULL; +} + +static char *expand_pkgdatadir(void) +{ + error_msg("The $PKGDATADIR variable was removed in dte v1.4"); + return NULL; +} + +static const BuiltinEnv builtin[] = { + {"PKGDATADIR", expand_pkgdatadir}, + {"DTE_HOME", expand_dte_home}, + {"FILE", expand_file}, + {"FILETYPE", expand_filetype}, + {"LINENO", expand_lineno}, + {"WORD", expand_word}, +}; + +void collect_builtin_env(const char *prefix) +{ + for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { + const char *name = builtin[i].name; + if (str_has_prefix(name, prefix)) { + add_completion(xstrdup(name)); + } + } +} + +void collect_env(const char *prefix) +{ + extern char **environ; + for (size_t i = 0; environ[i]; i++) { + const char *e = environ[i]; + if (str_has_prefix(e, prefix)) { + const char *end = strchr(e, '='); + if (end) { + add_completion(xstrcut(e, end - e)); + } + } + } +} + +bool expand_builtin_env(const char *name, char **value) +{ + for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { + const BuiltinEnv *be = &builtin[i]; + if (streq(be->name, name)) { + *value = be->expand(); + return true; + } + } + return false; +} diff -Nru dte-1.9.1/src/command/env.h dte-1.10/src/command/env.h --- dte-1.9.1/src/command/env.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/env.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,11 @@ +#ifndef COMMAND_ENV_H +#define COMMAND_ENV_H + +#include +#include "util/macros.h" + +void collect_builtin_env(const char *prefix) NONNULL_ARGS; +void collect_env(const char *prefix) NONNULL_ARGS; +bool expand_builtin_env(const char *name, char **value) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/command/macro.c dte-1.10/src/command/macro.c --- dte-1.9.1/src/command/macro.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/macro.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,144 @@ +#include "macro.h" +#include "commands.h" +#include "error.h" +#include "run.h" +#include "serialize.h" +#include "util/ptr-array.h" +#include "util/string-view.h" + +static PointerArray macro = PTR_ARRAY_INIT; +static PointerArray prev_macro = PTR_ARRAY_INIT; +static String insert_buffer = STRING_INIT; +static bool recording_macro = false; + +static void merge_insert_buffer(void) +{ + size_t len = insert_buffer.len; + if (len == 0) { + return; + } + String s = string_new(32 + len); + StringView ibuf = strview_from_string(&insert_buffer); + string_append_literal(&s, "insert -km "); + if (unlikely(strview_has_prefix(&ibuf, "-"))) { + string_append_literal(&s, "-- "); + } + string_append_escaped_arg_sv(&s, ibuf, true); + string_clear(&insert_buffer); + ptr_array_append(¯o, string_steal_cstring(&s)); +} + +bool macro_is_recording(void) +{ + return recording_macro; +} + +void macro_record(void) +{ + if (recording_macro) { + info_msg("Already recording"); + return; + } + ptr_array_free(&prev_macro); + prev_macro = macro; + macro = (PointerArray) PTR_ARRAY_INIT; + info_msg("Recording macro"); + recording_macro = true; +} + +void macro_stop(void) +{ + if (!recording_macro) { + info_msg("Not recording"); + return; + } + merge_insert_buffer(); + const size_t count = macro.count; + const char *plural = (count > 1) ? "s" : ""; + info_msg("Macro recording stopped; %zu command%s saved", count, plural); + recording_macro = false; +} + +void macro_toggle(void) +{ + if (recording_macro) { + macro_stop(); + } else { + macro_record(); + } +} + +void macro_cancel(void) +{ + if (!recording_macro) { + info_msg("Not recording"); + return; + } + ptr_array_free(¯o); + macro = prev_macro; + prev_macro = (PointerArray) PTR_ARRAY_INIT; + info_msg("Macro recording cancelled"); + recording_macro = false; +} + +void macro_command_hook(const char *cmd_name, char **args) +{ + if (!recording_macro) { + return; + } + String buf = string_new(512); + string_append_cstring(&buf, cmd_name); + for (size_t i = 0; args[i]; i++) { + string_append_byte(&buf, ' '); + string_append_escaped_arg(&buf, args[i], true); + } + merge_insert_buffer(); + ptr_array_append(¯o, string_steal_cstring(&buf)); +} + +void macro_insert_char_hook(CodePoint c) +{ + if (!recording_macro) { + return; + } + string_append_codepoint(&insert_buffer, c); +} + +void macro_insert_text_hook(const char *text, size_t size) +{ + if (!recording_macro) { + return; + } + String buf = string_new(512); + StringView sv = string_view(text, size); + string_append_literal(&buf, "insert -m "); + if (unlikely(strview_has_prefix(&sv, "-"))) { + string_append_literal(&buf, "-- "); + } + string_append_escaped_arg_sv(&buf, sv, true); + merge_insert_buffer(); + ptr_array_append(¯o, string_steal_cstring(&buf)); +} + +void macro_play(void) +{ + unsigned int saved_nr_errors = get_nr_errors(); + for (size_t i = 0, n = macro.count; i < n; i++) { + const char *cmd_str = macro.ptrs[i]; + handle_command(&commands, cmd_str, false); + if (get_nr_errors() != saved_nr_errors) { + break; + } + } +} + +String dump_macro(void) +{ + String buf = string_new(4096); + for (size_t i = 0, n = macro.count; i < n; i++) { + const char *cmd_str = macro.ptrs[i]; + string_append_cstring(&buf, cmd_str); + string_append_byte(&buf, '\n'); + } + return buf; +} diff -Nru dte-1.9.1/src/command/macro.h dte-1.10/src/command/macro.h --- dte-1.9.1/src/command/macro.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/macro.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,20 @@ +#ifndef COMMAND_MACRO_H +#define COMMAND_MACRO_H + +#include +#include +#include "util/string.h" +#include "util/unicode.h" + +bool macro_is_recording(void); +void macro_record(void); +void macro_stop(void); +void macro_toggle(void); +void macro_cancel(void); +void macro_play(void); +void macro_command_hook(const char *cmd_name, char **args); +void macro_insert_char_hook(CodePoint c); +void macro_insert_text_hook(const char *text, size_t size); +String dump_macro(void); + +#endif diff -Nru dte-1.9.1/src/command/parse.c dte-1.10/src/command/parse.c --- dte-1.9.1/src/command/parse.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/parse.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,283 @@ +#include +#include "parse.h" +#include "env.h" +#include "editor.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/string.h" +#include "util/strtonum.h" +#include "util/unicode.h" +#include "util/xmalloc.h" + +static size_t parse_sq(const char *cmd, size_t len, String *buf) +{ + size_t pos = 0; + char ch = '\0'; + while (pos < len) { + ch = cmd[pos]; + if (ch == '\'') { + break; + } + pos++; + } + string_append_buf(buf, cmd, pos); + if (ch == '\'') { + pos++; + } + return pos; +} + +static size_t unicode_escape(const char *str, size_t count, String *buf) +{ + if (unlikely(count == 0)) { + return 0; + } + + CodePoint u = 0; + size_t i; + for (i = 0; i < count; i++) { + unsigned int x = hex_decode(str[i]); + if (unlikely(x > 0xF)) { + break; + } + u = u << 4 | x; + } + if (likely(u_is_unicode(u))) { + string_append_codepoint(buf, u); + } + return i; +} + +static size_t parse_dq(const char *cmd, size_t len, String *buf) +{ + size_t pos = 0; + while (pos < len) { + unsigned char ch = cmd[pos++]; + + if (ch == '"') { + break; + } + + if (ch == '\\' && pos < len) { + ch = cmd[pos++]; + switch (ch) { + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'e': ch = '\033'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + case '\\': + case '"': + break; + case 'x': + if (pos < len) { + unsigned int x1 = hex_decode(cmd[pos]); + if (x1 <= 0xF && ++pos < len) { + unsigned int x2 = hex_decode(cmd[pos]); + if (x2 <= 0xF) { + pos++; + ch = x1 << 4 | x2; + break; + } + } + } + continue; + case 'u': + pos += unicode_escape(cmd + pos, MIN(4, len - pos), buf); + continue; + case 'U': + pos += unicode_escape(cmd + pos, MIN(8, len - pos), buf); + continue; + default: + string_append_byte(buf, '\\'); + break; + } + } + + string_append_byte(buf, ch); + } + + return pos; +} + +static size_t parse_var(const char *cmd, size_t len, String *buf) +{ + if (len == 0 || !is_alpha_or_underscore(cmd[0])) { + return 0; + } + + size_t n = 1; + while (n < len && is_alnum_or_underscore(cmd[n])) { + n++; + } + + char *name = xstrcut(cmd, n); + char *value; + if (expand_builtin_env(name, &value)) { + if (value) { + string_append_cstring(buf, value); + free(value); + } + } else { + const char *val = getenv(name); + if (val) { + string_append_cstring(buf, val); + } + } + + free(name); + return n; +} + +char *parse_command_arg(const char *cmd, size_t len, bool tilde) +{ + String buf; + size_t pos = 0; + + if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') { + buf = string_new(len + editor.home_dir.length); + string_append_strview(&buf, &editor.home_dir); + string_append_byte(&buf, '/'); + pos += 2; + } else { + buf = string_new(len); + } + + while (pos < len) { + char ch = cmd[pos++]; + switch (ch) { + case '\t': + case '\n': + case '\r': + case ' ': + case ';': + goto end; + case '\'': + pos += parse_sq(cmd + pos, len - pos, &buf); + break; + case '"': + pos += parse_dq(cmd + pos, len - pos, &buf); + break; + case '$': + pos += parse_var(cmd + pos, len - pos, &buf); + break; + case '\\': + if (pos == len) { + goto end; + } + ch = cmd[pos++]; + // Fallthrough + default: + string_append_byte(&buf, ch); + break; + } + } + +end: + return string_steal_cstring(&buf); +} + +size_t find_end(const char *cmd, const size_t startpos, CommandParseError *err) +{ + size_t pos = startpos; + while (1) { + switch (cmd[pos++]) { + case '\'': + while (1) { + if (cmd[pos] == '\'') { + pos++; + break; + } + if (cmd[pos] == '\0') { + *err = CMDERR_UNCLOSED_SQUOTE; + return 0; + } + pos++; + } + break; + case '"': + while (1) { + if (cmd[pos] == '"') { + pos++; + break; + } + if (cmd[pos] == '\0') { + *err = CMDERR_UNCLOSED_DQUOTE; + return 0; + } + if (cmd[pos++] == '\\') { + if (cmd[pos] == '\0') { + goto unexpected_eof; + } + pos++; + } + } + break; + case '\\': + if (cmd[pos] == '\0') { + goto unexpected_eof; + } + pos++; + break; + case '\0': + case '\t': + case '\n': + case '\r': + case ' ': + case ';': + *err = CMDERR_NONE; + return pos - 1; + } + } + BUG("Unexpected break of outer loop"); +unexpected_eof: + *err = CMDERR_UNEXPECTED_EOF; + return 0; +} + +CommandParseError parse_commands(PointerArray *array, const char *cmd) +{ + size_t pos = 0; + while (1) { + while (ascii_isspace(cmd[pos])) { + pos++; + } + + if (cmd[pos] == '\0') { + break; + } + + if (cmd[pos] == ';') { + ptr_array_append(array, NULL); + pos++; + continue; + } + + CommandParseError err; + size_t end = find_end(cmd, pos, &err); + if (err != CMDERR_NONE) { + return err; + } + + ptr_array_append(array, parse_command_arg(cmd + pos, end - pos, true)); + pos = end; + } + ptr_array_append(array, NULL); + return CMDERR_NONE; +} + +const char *command_parse_error_to_string(CommandParseError err) +{ + static const char error_strings[][16] = { + [CMDERR_UNCLOSED_SQUOTE] = "unclosed '", + [CMDERR_UNCLOSED_DQUOTE] = "unclosed \"", + [CMDERR_UNEXPECTED_EOF] = "unexpected EOF", + }; + BUG_ON(err <= CMDERR_NONE); + BUG_ON(err >= ARRAY_COUNT(error_strings)); + return error_strings[err]; +} diff -Nru dte-1.9.1/src/command/parse.h dte-1.10/src/command/parse.h --- dte-1.9.1/src/command/parse.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/parse.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,20 @@ +#ifndef COMMAND_PARSE_H +#define COMMAND_PARSE_H + +#include +#include +#include "util/ptr-array.h" + +typedef enum { + CMDERR_NONE, + CMDERR_UNCLOSED_SQUOTE, + CMDERR_UNCLOSED_DQUOTE, + CMDERR_UNEXPECTED_EOF, +} CommandParseError; + +char *parse_command_arg(const char *cmd, size_t len, bool tilde); +size_t find_end(const char *cmd, size_t pos, CommandParseError *err); +CommandParseError parse_commands(PointerArray *array, const char *cmd); +const char *command_parse_error_to_string(CommandParseError err); + +#endif diff -Nru dte-1.9.1/src/command/run.c dte-1.10/src/command/run.c --- dte-1.9.1/src/command/run.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/run.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,112 @@ +#include "run.h" +#include "args.h" +#include "parse.h" +#include "alias.h" +#include "change.h" +#include "config.h" +#include "error.h" +#include "macro.h" +#include "util/macros.h" +#include "util/ptr-array.h" +#include "util/xmalloc.h" + +const Command *current_command; + +static void run_commands(const CommandSet *cmds, const PointerArray *array, bool allow_recording); + +static void run_command(const CommandSet *cmds, char **av, bool allow_recording) +{ + const Command *cmd = cmds->lookup(av[0]); + if (!cmd) { + const char *alias_name = av[0]; + const char *alias_value = find_alias(alias_name); + if (unlikely(!alias_value)) { + error_msg("No such command or alias: %s", alias_name); + return; + } + + PointerArray array = PTR_ARRAY_INIT; + CommandParseError err = parse_commands(&array, alias_value); + if (unlikely(err != CMDERR_NONE)) { + const char *err_msg = command_parse_error_to_string(err); + error_msg("Parsing alias %s: %s", alias_name, err_msg); + ptr_array_free(&array); + return; + } + + // Remove NULL + array.count--; + + for (size_t i = 1; av[i]; i++) { + ptr_array_append(&array, xstrdup(av[i])); + } + ptr_array_append(&array, NULL); + + run_commands(cmds, &array, allow_recording); + ptr_array_free(&array); + return; + } + + if (unlikely(current_config.file && !cmd->allow_in_rc)) { + error_msg("Command %s not allowed in config file.", cmd->name); + return; + } + + // Record command in macro buffer, if recording (this needs to be done + // before parse_args() mutates the array) + if (allow_recording && cmds->allow_recording(cmd, av + 1)) { + macro_command_hook(cmd->name, av + 1); + } + + // By default change can't be merged with previous one. + // Any command can override this by calling begin_change() again. + begin_change(CHANGE_MERGE_NONE); + + CommandArgs a = {.args = av + 1}; + current_command = cmd; + if (likely(parse_args(cmd, &a))) { + cmd->cmd(&a); + } + current_command = NULL; + + end_change(); +} + +static void run_commands(const CommandSet *cmds, const PointerArray *array, bool allow_recording) +{ + static unsigned int recursion_count; + if (unlikely(recursion_count++ > 16)) { + error_msg("alias recursion overflow"); + goto out; + } + + size_t s = 0; + while (s < array->count) { + size_t e = s; + while (e < array->count && array->ptrs[e]) { + e++; + } + + if (e > s) { + run_command(cmds, (char **)array->ptrs + s, allow_recording); + } + + s = e + 1; + } + +out: + recursion_count--; +} + +void handle_command(const CommandSet *cmds, const char *cmd, bool allow_recording) +{ + PointerArray array = PTR_ARRAY_INIT; + CommandParseError err = parse_commands(&array, cmd); + if (likely(err == CMDERR_NONE)) { + run_commands(cmds, &array, allow_recording); + } else { + const char *str = command_parse_error_to_string(err); + error_msg("Command syntax error: %s", str); + } + ptr_array_free(&array); +} diff -Nru dte-1.9.1/src/command/run.h dte-1.10/src/command/run.h --- dte-1.9.1/src/command/run.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/run.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,43 @@ +#ifndef COMMAND_RUN_H +#define COMMAND_RUN_H + +#include +#include +#include +#include + +typedef struct { + char **args; // Positional args, with flag args moved to the front + size_t nr_args; // Number of args (not including flag args) + uint64_t flag_set; // Bitset of used flags + char flags[10]; // Flags in parsed order + uint8_t nr_flags; // Number of parsed flags + uint8_t nr_flag_args; // Number of flag args +} CommandArgs; + +typedef struct { + const char name[16]; + const char flags[12]; + bool allow_in_rc; + uint8_t min_args; + uint8_t max_args; // 0xFF here means "no limit" (effectively SIZE_MAX) + void (*cmd)(const CommandArgs *args); +} Command; + +typedef struct { + const Command* (*lookup)(const char *name); + bool (*allow_recording)(const Command *cmd, char **args); +} CommandSet; + +extern const Command *current_command; + +static inline int command_cmp(const void *key, const void *elem) +{ + const char *name = key; + const Command *cmd = elem; + return strcmp(name, cmd->name); +} + +void handle_command(const CommandSet *cmds, const char *cmd, bool allow_recording); + +#endif diff -Nru dte-1.9.1/src/command/serialize.c dte-1.10/src/command/serialize.c --- dte-1.9.1/src/command/serialize.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/serialize.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,86 @@ +#include +#include "serialize.h" +#include "util/ascii.h" + +void string_append_escaped_arg_sv(String *s, StringView sv, bool escape_tilde) +{ + static const char hexmap[16] = "0123456789ABCDEF"; + static const char escmap[] = { + [0x07] = 'a', [0x08] = 'b', + [0x09] = 't', [0x0A] = 'n', + [0x0B] = 'v', [0x0C] = 'f', + [0x0D] = 'r', [0x1B] = 'e', + ['"'] = '"', ['\\'] = '\\', + }; + + const unsigned char *arg = sv.data; + size_t len = sv.length; + if (len == 0) { + string_append_literal(s, "''"); + return; + } + + bool has_tilde_slash_prefix = strview_has_prefix(&sv, "~/"); + if (has_tilde_slash_prefix && !escape_tilde) { + // Print "~/" and skip past it, so it doesn't get quoted + string_append_literal(s, "~/"); + arg += 2; + len -= 2; + } + + bool squote = false; + for (size_t i = 0; i < len; i++) { + const unsigned char c = arg[i]; + switch (c) { + case ' ': + case '"': + case '$': + case ';': + case '\\': + squote = true; + continue; + case '\'': + goto dquote; + } + if (ascii_iscntrl(c)) { + goto dquote; + } + } + + if (squote) { + string_append_byte(s, '\''); + string_append_buf(s, arg, len); + string_append_byte(s, '\''); + } else { + if (has_tilde_slash_prefix && escape_tilde) { + string_append_byte(s, '\\'); + } + string_append_buf(s, arg, len); + } + return; + +dquote: + string_append_byte(s, '"'); + for (size_t i = 0; i < len; i++) { + const unsigned char ch = arg[i]; + if (unlikely(ch < sizeof(escmap) && escmap[ch])) { + string_append_byte(s, '\\'); + string_append_byte(s, escmap[ch]); + } else if (unlikely(ascii_iscntrl(ch))) { + string_append_literal(s, "\\x"); + string_append_byte(s, hexmap[(ch >> 4) & 15]); + string_append_byte(s, hexmap[ch & 15]); + } else { + string_append_byte(s, ch); + } + } + string_append_byte(s, '"'); +} + +char *escape_command_arg(const char *arg, bool escape_tilde) +{ + size_t n = strlen(arg); + String buf = string_new(n + 16); + string_append_escaped_arg_sv(&buf, string_view(arg, n), escape_tilde); + return string_steal_cstring(&buf); +} diff -Nru dte-1.9.1/src/command/serialize.h dte-1.10/src/command/serialize.h --- dte-1.9.1/src/command/serialize.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/command/serialize.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,17 @@ +#ifndef COMMAND_SERIALIZE_H +#define COMMAND_SERIALIZE_H + +#include +#include "util/macros.h" +#include "util/string.h" +#include "util/string-view.h" + +char *escape_command_arg(const char *arg, bool escape_tilde) NONNULL_ARGS_AND_RETURN; +void string_append_escaped_arg_sv(String *s, StringView arg, bool escape_tilde) NONNULL_ARGS; + +static inline void string_append_escaped_arg(String *s, const char *arg, bool escape_tilde) +{ + string_append_escaped_arg_sv(s, strview_from_cstring(arg), escape_tilde); +} + +#endif diff -Nru dte-1.9.1/src/command.c dte-1.10/src/command.c --- dte-1.9.1/src/command.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/command.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1984 +0,0 @@ -#include -#include -#include -#include "alias.h" -#include "bind.h" -#include "change.h" -#include "cmdline.h" -#include "command.h" -#include "config.h" -#include "debug.h" -#include "edit.h" -#include "editor.h" -#include "encoding/convert.h" -#include "encoding/encoding.h" -#include "error.h" -#include "file-option.h" -#include "filetype.h" -#include "frame.h" -#include "history.h" -#include "load-save.h" -#include "lock.h" -#include "move.h" -#include "msg.h" -#include "parse-args.h" -#include "search.h" -#include "selection.h" -#include "spawn.h" -#include "syntax/state.h" -#include "syntax/syntax.h" -#include "tag.h" -#include "terminal/color.h" -#include "terminal/input.h" -#include "terminal/terminal.h" -#include "util/path.h" -#include "util/str-util.h" -#include "util/strtonum.h" -#include "util/xmalloc.h" -#include "util/xreadwrite.h" -#include "util/xsnprintf.h" -#include "view.h" -#include "window.h" - -static void do_selection(SelectionType sel) -{ - if (sel == SELECT_NONE) { - if (view->next_movement_cancels_selection) { - view->next_movement_cancels_selection = false; - unselect(); - } - return; - } - - view->next_movement_cancels_selection = true; - - if (view->selection) { - view->selection = sel; - mark_all_lines_changed(buffer); - return; - } - - view->sel_so = block_iter_get_offset(&view->cursor); - view->sel_eo = UINT_MAX; - view->selection = sel; - - // Need to mark current line changed because cursor might - // move up or down before screen is updated - view_update_cursor_y(view); - buffer_mark_lines_changed(view->buffer, view->cy, view->cy); -} - -static void handle_select_chars_flag(const char *pf) -{ - do_selection(strchr(pf, 'c') ? SELECT_CHARS : SELECT_NONE); -} - -static void handle_select_chars_or_lines_flags(const char *pf) -{ - SelectionType sel = SELECT_NONE; - if (strchr(pf, 'l')) { - sel = SELECT_LINES; - } else if (strchr(pf, 'c')) { - sel = SELECT_CHARS; - } - do_selection(sel); -} - -// Go to compiler error saving position if file changed or cursor moved -static void activate_current_message_save(void) -{ - FileLocation *loc = file_location_create ( - view->buffer->abs_filename, - view->buffer->id, - view->cy + 1, - view->cx_char + 1 - ); - - BlockIter save = view->cursor; - - activate_current_message(); - if (view->cursor.blk != save.blk || view->cursor.offset != save.offset) { - push_file_location(loc); - } else { - file_location_free(loc); - } -} - -static void cmd_alias(const CommandArgs *a) -{ - add_alias(a->args[0], a->args[1]); -} - -static void cmd_bind(const CommandArgs *a) -{ - const char *key = a->args[0]; - const char *cmd = a->args[1]; - if (cmd) { - add_binding(key, cmd); - } else { - remove_binding(key); - } -} - -static void cmd_bof(const CommandArgs* UNUSED_ARG(a)) -{ - move_bof(); -} - -static void cmd_bol(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - if (strchr(a->flags, 's')) { - move_bol_smart(); - } else { - move_bol(); - } -} - -static void cmd_bolsf(const CommandArgs* UNUSED_ARG(a)) -{ - do_selection(SELECT_NONE); - if (!block_iter_bol(&view->cursor)) { - long top = view->vy + window_get_scroll_margin(window); - if (view->cy > top) { - move_up(view->cy - top); - } else { - block_iter_bof(&view->cursor); - } - } - view_reset_preferred_x(view); -} - -static void cmd_case(const CommandArgs *a) -{ - const char *pf = a->flags; - int mode = 't'; - while (*pf) { - switch (*pf) { - case 'l': - case 'u': - mode = *pf; - break; - } - pf++; - } - change_case(mode); -} - -static void cmd_cd(const CommandArgs *a) -{ - const char *dir = a->args[0]; - char cwd[8192]; - const char *cwdp = NULL; - bool got_cwd = !!getcwd(cwd, sizeof(cwd)); - - if (streq(dir, "-")) { - dir = getenv("OLDPWD"); - if (dir == NULL || dir[0] == '\0') { - error_msg("cd: OLDPWD not set"); - return; - } - } - if (chdir(dir)) { - error_msg("cd: %s", strerror(errno)); - return; - } - - if (got_cwd) { - setenv("OLDPWD", cwd, 1); - } - got_cwd = !!getcwd(cwd, sizeof(cwd)); - if (got_cwd) { - setenv("PWD", cwd, 1); - cwdp = cwd; - } - - for (size_t i = 0; i < buffers.count; i++) { - Buffer *b = buffers.ptrs[i]; - update_short_filename_cwd(b, cwdp); - } - - // Need to update all tabbars - mark_everything_changed(); -} - -static void cmd_center_view(const CommandArgs* UNUSED_ARG(a)) -{ - view->force_center = true; -} - -static void cmd_clear(const CommandArgs* UNUSED_ARG(a)) -{ - clear_lines(); -} - -static void cmd_close(const CommandArgs *a) -{ - const char *pf = a->flags; - bool force = false; - bool allow_quit = false; - bool allow_wclose = false; - while (*pf) { - switch (*pf) { - case 'f': - force = true; - break; - case 'q': - allow_quit = true; - break; - case 'w': - allow_wclose = true; - break; - } - pf++; - } - - if (!view_can_close(view) && !force) { - error_msg ( - "The buffer is modified. " - "Save or run 'close -f' to close without saving." - ); - return; - } - - if (allow_quit && buffers.count == 1 && root_frame->frames.count <= 1) { - editor.status = EDITOR_EXITING; - return; - } - - if (allow_wclose && window->views.count <= 1) { - window_close_current(); - return; - } - - window_close_current_view(window); - set_view(window->view); -} - -static void cmd_command(const CommandArgs *a) -{ - const char *text = a->args[0]; - set_input_mode(INPUT_COMMAND); - if (text) { - cmdline_set_text(&editor.cmdline, text); - } -} - -static void cmd_compile(const CommandArgs *a) -{ - const char *pf = a->flags; - SpawnFlags flags = SPAWN_DEFAULT; - while (*pf) { - switch (*pf) { - case '1': - flags |= SPAWN_READ_STDOUT; - break; - case 'p': - flags |= SPAWN_PROMPT; - break; - case 's': - flags |= SPAWN_QUIET; - break; - } - pf++; - } - - const char *name = a->args[0]; - Compiler *c = find_compiler(name); - if (!c) { - error_msg("No such error parser %s", name); - return; - } - clear_messages(); - spawn_compiler(a->args + 1, flags, c); - if (message_count()) { - activate_current_message_save(); - } -} - -static void cmd_copy(const CommandArgs *a) -{ - BlockIter save = view->cursor; - if (view->selection) { - copy(prepare_selection(view), view->selection == SELECT_LINES); - bool keep_selection = a->flags[0] == 'k'; - if (!keep_selection) { - unselect(); - } - } else { - block_iter_bol(&view->cursor); - BlockIter tmp = view->cursor; - copy(block_iter_eat_line(&tmp), true); - } - view->cursor = save; -} - -static void cmd_cut(const CommandArgs* UNUSED_ARG(a)) -{ - const long x = view_get_preferred_x(view); - if (view->selection) { - cut(prepare_selection(view), view->selection == SELECT_LINES); - if (view->selection == SELECT_LINES) { - move_to_preferred_x(x); - } - unselect(); - } else { - BlockIter tmp; - block_iter_bol(&view->cursor); - tmp = view->cursor; - cut(block_iter_eat_line(&tmp), true); - move_to_preferred_x(x); - } -} - -static void cmd_delete(const CommandArgs* UNUSED_ARG(a)) -{ - delete_ch(); -} - -static void cmd_delete_eol(const CommandArgs *a) -{ - if (view->selection) { - return; - } - - bool delete_newline_if_at_eol = a->flags[0] == 'n'; - BlockIter bi = view->cursor; - if (delete_newline_if_at_eol) { - CodePoint ch; - block_iter_get_char(&view->cursor, &ch); - if (ch == '\n') { - delete_ch(); - return; - } - } - - buffer_delete_bytes(block_iter_eol(&bi)); -} - -static void cmd_delete_word(const CommandArgs *a) -{ - bool skip_non_word = a->flags[0] == 's'; - BlockIter bi = view->cursor; - buffer_delete_bytes(word_fwd(&bi, skip_non_word)); -} - -static void cmd_down(const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(a->flags); - move_down(1); -} - -static void cmd_eof(const CommandArgs* UNUSED_ARG(a)) -{ - move_eof(); -} - -static void cmd_eol(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - move_eol(); -} - -static void cmd_eolsf(const CommandArgs* UNUSED_ARG(a)) -{ - do_selection(SELECT_NONE); - if (!block_iter_eol(&view->cursor)) { - long bottom = view->vy + window->edit_h - 1 - window_get_scroll_margin(window); - if (view->cy < bottom) { - move_down(bottom - view->cy); - } else { - block_iter_eof(&view->cursor); - } - } - view_reset_preferred_x(view); -} - -static void cmd_erase(const CommandArgs* UNUSED_ARG(a)) -{ - erase(); -} - -static void cmd_erase_bol(const CommandArgs* UNUSED_ARG(a)) -{ - buffer_erase_bytes(block_iter_bol(&view->cursor)); -} - -static void cmd_erase_word(const CommandArgs *a) -{ - bool skip_non_word = a->flags[0] == 's'; - buffer_erase_bytes(word_bwd(&view->cursor, skip_non_word)); -} - -static void cmd_errorfmt(const CommandArgs *a) -{ - bool ignore = a->flags[0] == 'i'; - add_error_fmt(a->args[0], ignore, a->args[1], a->args + 2); -} - -static void cmd_eval(const CommandArgs *a) -{ - FilterData data = FILTER_DATA_INIT; - if (spawn_filter(a->args, &data)) { - return; - } - exec_config(commands, data.out, data.out_len); - free(data.out); -} - -static void cmd_filter(const CommandArgs *a) -{ - FilterData data; - BlockIter save = view->cursor; - - if (view->selection) { - data.in_len = prepare_selection(view); - } else { - Block *blk; - data.in_len = 0; - block_for_each(blk, &buffer->blocks) { - data.in_len += blk->size; - } - move_bof(); - } - - data.in = block_iter_get_bytes(&view->cursor, data.in_len); - if (spawn_filter(a->args, &data)) { - free(data.in); - view->cursor = save; - return; - } - - free(data.in); - buffer_replace_bytes(data.in_len, data.out, data.out_len); - free(data.out); - - unselect(); -} - -static void cmd_ft(const CommandArgs *a) -{ - const char *pf = a->flags; - FileDetectionType dt = FT_EXTENSION; - while (*pf) { - switch (*pf) { - case 'b': - dt = FT_BASENAME; - break; - case 'c': - dt = FT_CONTENT; - break; - case 'f': - dt = FT_FILENAME; - break; - case 'i': - dt = FT_INTERPRETER; - break; - } - pf++; - } - - char **args = a->args; - if (args[0][0] == '\0') { - error_msg("Filetype can't be blank"); - return; - } - - for (size_t i = 1; args[i]; i++) { - add_filetype(args[0], args[i], dt); - } -} - -static void cmd_git_open(const CommandArgs* UNUSED_ARG(a)) -{ - set_input_mode(INPUT_GIT_OPEN); - git_open_reload(); -} - -static void cmd_hi(const CommandArgs *a) -{ - char **args = a->args; - TermColor color; - if (args[0] == NULL) { - exec_reset_colors_rc(); - remove_extra_colors(); - } else if (parse_term_color(&color, args + 1)) { - color.fg = color_to_nearest(color.fg, terminal.color_type); - color.bg = color_to_nearest(color.bg, terminal.color_type); - set_highlight_color(args[0], &color); - } - - // Don't call update_all_syntax_colors() needlessly. - // It is called right after config has been loaded. - if (editor.status != EDITOR_INITIALIZING) { - update_all_syntax_colors(); - mark_everything_changed(); - } -} - -static void cmd_include(const CommandArgs *a) -{ - ConfigFlags flags = CFG_MUST_EXIST; - if (a->flags[0] == 'b') { - flags |= CFG_BUILTIN; - } - read_config(commands, a->args[0], flags); -} - -static void cmd_insert(const CommandArgs *a) -{ - const char *str = a->args[0]; - if (strchr(a->flags, 'k')) { - for (size_t i = 0; str[i]; i++) { - insert_ch(str[i]); - } - return; - } - - size_t del_len = 0; - size_t ins_len = strlen(str); - if (view->selection) { - del_len = prepare_selection(view); - unselect(); - } - buffer_replace_bytes(del_len, str, ins_len); - - if (strchr(a->flags, 'm')) { - block_iter_skip_bytes(&view->cursor, ins_len); - } -} - -static void cmd_insert_builtin(const CommandArgs *a) -{ - const char *name = a->args[0]; - const BuiltinConfig *cfg = get_builtin_config(name); - if (cfg) { - buffer_insert_bytes(cfg->text.data, cfg->text.length); - } else { - error_msg("No built-in config with name '%s'", name); - } -} - -static void cmd_join(const CommandArgs* UNUSED_ARG(a)) -{ - join_lines(); -} - -static void cmd_left(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - move_cursor_left(); -} - -static void cmd_line(const CommandArgs *a) -{ - const char *arg = a->args[0]; - const long x = view_get_preferred_x(view); - size_t line; - if (!str_to_size(arg, &line) || line == 0) { - error_msg("Invalid line number: %s", arg); - return; - } - move_to_line(view, line); - move_to_preferred_x(x); -} - -static void cmd_load_syntax(const CommandArgs *a) -{ - const char *filename = a->args[0]; - const char *filetype = path_basename(filename); - if (filename != filetype) { - if (find_syntax(filetype)) { - error_msg("Syntax for filetype %s already loaded", filetype); - } else { - int err; - load_syntax_file(filename, true, &err); - } - } else { - if (!find_syntax(filetype)) { - load_syntax_by_filetype(filetype); - } - } -} - -static void cmd_move_tab(const CommandArgs *a) -{ - const char *str = a->args[0]; - size_t j, i = ptr_array_idx(&window->views, view); - if (streq(str, "left")) { - j = i - 1; - } else if (streq(str, "right")) { - j = i + 1; - } else { - size_t num; - if (!str_to_size(str, &num) || num == 0) { - error_msg("Invalid tab position %s", str); - return; - } - j = num - 1; - if (j >= window->views.count) { - j = window->views.count - 1; - } - } - j = (window->views.count + j) % window->views.count; - ptr_array_insert ( - &window->views, - ptr_array_remove_idx(&window->views, i), - j - ); - window->update_tabbar = true; -} - -static void cmd_msg(const CommandArgs *a) -{ - const char *pf = a->flags; - char dir = 0; - while (*pf) { - switch (*pf) { - case 'n': - case 'p': - dir = *pf; - break; - } - pf++; - } - - if (dir == 'n') { - activate_next_message(); - } else if (dir == 'p') { - activate_prev_message(); - } else { - activate_current_message(); - } -} - -static void cmd_new_line(const CommandArgs* UNUSED_ARG(a)) -{ - new_line(); -} - -static void cmd_next(const CommandArgs* UNUSED_ARG(a)) -{ - set_view(ptr_array_next(&window->views, view)); -} - -static void cmd_open(const CommandArgs *a) -{ - char **args = a->args; - const char *pf = a->flags; - const char *requested_encoding = NULL; - bool use_glob = false; - while (*pf) { - switch (*pf) { - case 'e': - requested_encoding = *args++; - break; - case 'g': - use_glob = args[0] ? true : false; - break; - } - pf++; - } - - Encoding encoding = { - .type = ENCODING_AUTODETECT, - .name = NULL - }; - - if (requested_encoding) { - if ( - lookup_encoding(requested_encoding) != UTF8 - && !encoding_supported_by_iconv(requested_encoding) - ) { - error_msg("Unsupported encoding %s", requested_encoding); - return; - } - encoding = encoding_from_name(requested_encoding); - } - - char **paths = args; - glob_t globbuf; - if (use_glob) { - int err = glob(args[0], GLOB_NOCHECK, NULL, &globbuf); - while (err == 0 && *++args) { - err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, &globbuf); - } - if (globbuf.gl_pathc > 0) { - paths = globbuf.gl_pathv; - } - } - - if (!paths[0]) { - window_open_new_file(window); - if (requested_encoding) { - buffer->encoding = encoding; - } - } else if (!paths[1]) { - // Previous view is remembered when opening single file - window_open_file(window, paths[0], &encoding); - } else { - // It makes no sense to remember previous view when opening - // multiple files - window_open_files(window, paths, &encoding); - } - - if (use_glob) { - globfree(&globbuf); - } -} - -static void cmd_option(const CommandArgs *a) -{ - char **args = a->args; - size_t argc = a->nr_args; - char **strs = args + 1; - size_t count = argc - 1; - - if (argc % 2 == 0) { - error_msg("Missing option value"); - return; - } - if (!validate_local_options(strs)) { - return; - } - - if (a->flags[0] == 'r') { - add_file_options ( - FILE_OPTIONS_FILENAME, - xstrdup(args[0]), - copy_string_array(strs, count) - ); - return; - } - - char *comma, *list = args[0]; - do { - comma = strchr(list, ','); - size_t len = comma ? comma - list : strlen(list); - add_file_options ( - FILE_OPTIONS_FILETYPE, - xstrcut(list, len), - copy_string_array(strs, count) - ); - list = comma + 1; - } while (comma); -} - -static void cmd_paste(const CommandArgs *a) -{ - bool at_cursor = a->flags[0] == 'c'; - paste(at_cursor); -} - -static void cmd_pgdown(const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(a->flags); - - long margin = window_get_scroll_margin(window); - long bottom = view->vy + window->edit_h - 1 - margin; - long count; - - if (view->cy < bottom) { - count = bottom - view->cy; - } else { - count = window->edit_h - 1 - margin * 2; - } - move_down(count); -} - -static void cmd_pgup(const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(a->flags); - - long margin = window_get_scroll_margin(window); - long top = view->vy + margin; - long count; - - if (view->cy > top) { - count = view->cy - top; - } else { - count = window->edit_h - 1 - margin * 2; - } - move_up(count); -} - -static void cmd_pipe_from(const CommandArgs *a) -{ - const char *pf = a->flags; - bool strip_nl = false; - bool move = false; - while (*pf) { - switch (*pf++) { - case 'm': - move = true; - break; - case 's': - strip_nl = true; - break; - } - } - - FilterData data = FILTER_DATA_INIT; - if (spawn_filter(a->args, &data)) { - return; - } - - size_t del_len = 0; - if (view->selection) { - del_len = prepare_selection(view); - unselect(); - } - - if (strip_nl && data.out_len > 0 && data.out[data.out_len - 1] == '\n') { - if (--data.out_len > 0 && data.out[data.out_len - 1] == '\r') { - data.out_len--; - } - } - - buffer_replace_bytes(del_len, data.out, data.out_len); - free(data.out); - - if (move) { - block_iter_skip_bytes(&view->cursor, data.out_len); - } -} - -static void cmd_pipe_to(const CommandArgs *a) -{ - const BlockIter saved_cursor = view->cursor; - const ssize_t saved_sel_so = view->sel_so; - const ssize_t saved_sel_eo = view->sel_eo; - - size_t input_len = 0; - if (view->selection) { - input_len = prepare_selection(view); - } else { - Block *blk; - block_for_each(blk, &buffer->blocks) { - input_len += blk->size; - } - move_bof(); - } - - char *input = block_iter_get_bytes(&view->cursor, input_len); - spawn_writer(a->args, input, input_len); - free(input); - - // Restore cursor and selection offsets, instead of calling unselect() - view->cursor = saved_cursor; - view->sel_so = saved_sel_so; - view->sel_eo = saved_sel_eo; -} - -static void cmd_prev(const CommandArgs* UNUSED_ARG(a)) -{ - set_view(ptr_array_prev(&window->views, view)); -} - -static void cmd_quit(const CommandArgs *a) -{ - const char *pf = a->flags; - bool prompt = false; - while (*pf) { - switch (*pf++) { - case 'f': - editor.status = EDITOR_EXITING; - return; - case 'p': - prompt = true; - break; - } - } - - for (size_t i = 0; i < buffers.count; i++) { - Buffer *b = buffers.ptrs[i]; - if (buffer_modified(b)) { - // Activate modified buffer - View *v = window_find_view(window, b); - - if (v == NULL) { - // Buffer isn't open in current window. - // Activate first window of the buffer. - v = b->views.ptrs[0]; - window = v->window; - mark_everything_changed(); - } - set_view(v); - if (prompt) { - if (get_confirmation("yN", "Quit without saving changes?") == 'y') { - editor.status = EDITOR_EXITING; - } - return; - } else { - error_msg ( - "Save modified files or run 'quit -f' to quit" - " without saving." - ); - return; - } - } - } - - editor.status = EDITOR_EXITING; -} - -static void cmd_redo(const CommandArgs *a) -{ - char *arg = a->args[0]; - unsigned long change_id = 0; - if (arg) { - if (!str_to_ulong(arg, &change_id) || change_id == 0) { - error_msg("Invalid change id: %s", arg); - return; - } - } - if (redo(change_id)) { - unselect(); - } -} - -static void cmd_refresh(const CommandArgs* UNUSED_ARG(a)) -{ - mark_everything_changed(); -} - -static void cmd_repeat(const CommandArgs *a) -{ - char **args = a->args; - unsigned int count = 0; - if (!str_to_uint(args[0], &count)) { - error_msg("Not a valid repeat count: %s", args[0]); - return; - } else if (count == 0) { - return; - } - - const Command *cmd = find_command(commands, args[1]); - if (!cmd) { - error_msg("No such command: %s", args[1]); - return; - } - - CommandArgs a2 = {.args = args + 2}; - if (parse_args(cmd, &a2)) { - while (count-- > 0) { - cmd->cmd(&a2); - } - } -} - -static void cmd_replace(const CommandArgs *a) -{ - const char *pf = a->flags; - unsigned int flags = 0; - for (size_t i = 0; pf[i]; i++) { - switch (pf[i]) { - case 'b': - flags |= REPLACE_BASIC; - break; - case 'c': - flags |= REPLACE_CONFIRM; - break; - case 'g': - flags |= REPLACE_GLOBAL; - break; - case 'i': - flags |= REPLACE_IGNORE_CASE; - break; - } - } - reg_replace(a->args[0], a->args[1], flags); -} - -static void cmd_right(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - move_cursor_right(); -} - -static void cmd_run(const CommandArgs *a) -{ - const char *pf = a->flags; - int fd[3] = {0, 1, 2}; - bool prompt = false; - while (*pf) { - switch (*pf) { - case 'p': - prompt = true; - break; - case 's': - fd[0] = -1; - fd[1] = -1; - fd[2] = -1; - break; - } - pf++; - } - spawn(a->args, fd, prompt); -} - -static bool stat_changed(const struct stat *const a, const struct stat *const b) -{ - // Don't compare st_mode because we allow chmod 755 etc. - return a->st_mtime != b->st_mtime || - a->st_dev != b->st_dev || - a->st_ino != b->st_ino; -} - -static void cmd_save(const CommandArgs *a) -{ - const char *pf = a->flags; - char **args = a->args; - char *absolute = buffer->abs_filename; - Encoding encoding = buffer->encoding; - const char *requested_encoding = NULL; - bool force = false; - bool prompt = false; - LineEndingType newline = buffer->newline; - mode_t old_mode = buffer->st.st_mode; - struct stat st; - bool new_locked = false; - - while (*pf) { - switch (*pf) { - case 'd': - newline = NEWLINE_DOS; - break; - case 'e': - requested_encoding = *args++; - break; - case 'f': - force = true; - break; - case 'p': - prompt = true; - break; - case 'u': - newline = NEWLINE_UNIX; - break; - } - pf++; - } - - if (requested_encoding) { - if ( - lookup_encoding(requested_encoding) != UTF8 - && !encoding_supported_by_iconv(requested_encoding) - ) { - error_msg("Unsupported encoding %s", requested_encoding); - return; - } - encoding = encoding_from_name(requested_encoding); - } - - // The encoding_from_name() call above may have allocated memory, - // so use "goto error" instead of early return beyond this point, to - // ensure correct de-allocation. - - if (args[0]) { - if (args[0][0] == '\0') { - error_msg("Empty filename not allowed"); - goto error; - } - char *tmp = path_absolute(args[0]); - if (!tmp) { - error_msg("Failed to make absolute path: %s", strerror(errno)); - goto error; - } - if (absolute && streq(tmp, absolute)) { - free(tmp); - } else { - absolute = tmp; - } - } else { - if (!absolute) { - if (prompt) { - set_input_mode(INPUT_COMMAND); - cmdline_set_text(&editor.cmdline, "save "); - // This branch is not really an error, but we still return via - // the "error" label because we need to clean up memory and - // that's all it's used for currently. - goto error; - } else { - error_msg("No filename."); - goto error; - } - } - if (buffer->readonly && !force) { - error_msg("Use -f to force saving read-only file."); - goto error; - } - } - - if (stat(absolute, &st)) { - if (errno != ENOENT) { - error_msg("stat failed for %s: %s", absolute, strerror(errno)); - goto error; - } - if (editor.options.lock_files) { - if (absolute == buffer->abs_filename) { - if (!buffer->locked) { - if (lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - buffer->locked = true; - } - } - } else { - if (lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - new_locked = true; - } - } - } - } else { - if ( - absolute == buffer->abs_filename - && !force - && stat_changed(&buffer->st, &st) - ) { - error_msg ( - "File has been modified by someone else." - " Use -f to force overwrite." - ); - goto error; - } - if (S_ISDIR(st.st_mode)) { - error_msg("Will not overwrite directory %s", absolute); - goto error; - } - if (editor.options.lock_files) { - if (absolute == buffer->abs_filename) { - if (!buffer->locked) { - if (lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - buffer->locked = true; - } - } - } else { - if (lock_file(absolute)) { - if (!force) { - error_msg("Can't lock file %s", absolute); - goto error; - } - } else { - new_locked = true; - } - } - } - if (absolute != buffer->abs_filename && !force) { - error_msg("Use -f to overwrite %s.", absolute); - goto error; - } - - // Allow chmod 755 etc. - buffer->st.st_mode = st.st_mode; - } - if (save_buffer(buffer, absolute, &encoding, newline)) { - goto error; - } - - buffer->saved_change = buffer->cur_change; - buffer->readonly = false; - buffer->newline = newline; - if (requested_encoding) { - buffer->encoding = encoding; - } - - if (absolute != buffer->abs_filename) { - if (buffer->locked) { - // Filename changes, release old file lock - unlock_file(buffer->abs_filename); - } - buffer->locked = new_locked; - - free(buffer->abs_filename); - buffer->abs_filename = absolute; - update_short_filename(buffer); - - // Filename change is not detected (only buffer_modified() change) - mark_buffer_tabbars_changed(buffer); - } - if (!old_mode && streq(buffer->options.filetype, "none")) { - // New file and most likely user has not changed the filetype - if (buffer_detect_filetype(buffer)) { - set_file_options(buffer); - set_editorconfig_options(buffer); - buffer_update_syntax(buffer); - } - } - return; -error: - if (new_locked) { - unlock_file(absolute); - } - if (absolute != buffer->abs_filename) { - free(absolute); - } -} - -static void cmd_scroll_down(const CommandArgs* UNUSED_ARG(a)) -{ - view->vy++; - if (view->cy < view->vy) { - move_down(1); - } -} - -static void cmd_scroll_pgdown(const CommandArgs* UNUSED_ARG(a)) -{ - long max = buffer->nl - window->edit_h + 1; - if (view->vy < max && max > 0) { - long count = window->edit_h - 1; - if (view->vy + count > max) { - count = max - view->vy; - } - view->vy += count; - move_down(count); - } else if (view->cy < buffer->nl) { - move_down(buffer->nl - view->cy); - } -} - -static void cmd_scroll_pgup(const CommandArgs* UNUSED_ARG(a)) -{ - if (view->vy > 0) { - long count = window->edit_h - 1; - if (count > view->vy) { - count = view->vy; - } - view->vy -= count; - move_up(count); - } else if (view->cy > 0) { - move_up(view->cy); - } -} - -static void cmd_scroll_up(const CommandArgs* UNUSED_ARG(a)) -{ - if (view->vy) { - view->vy--; - } - if (view->vy + window->edit_h <= view->cy) { - move_up(1); - } -} - -static void cmd_search(const CommandArgs *a) -{ - const char *pf = a->flags; - char *pattern = a->args[0]; - bool history = true; - char cmd = 0; - bool w = false; - SearchDirection dir = SEARCH_FWD; - - while (*pf) { - switch (*pf) { - case 'H': - history = false; - break; - case 'n': - case 'p': - cmd = *pf; - break; - case 'r': - dir = SEARCH_BWD; - break; - case 'w': - w = true; - if (pattern) { - error_msg("Flag -w can't be used with search pattern."); - return; - } - break; - } - pf++; - } - - if (w) { - char *word = view_get_word_under_cursor(view); - if (word == NULL) { - // Error message would not be very useful here - return; - } - size_t len = strlen(word) + 5; - pattern = xmalloc(len); - xsnprintf(pattern, len, "\\<%s\\>", word); - free(word); - } - - if (pattern) { - search_set_direction(dir); - search_set_regexp(pattern); - if (w) { - search_next_word(); - } else { - search_next(); - } - if (history) { - history_add(&editor.search_history, pattern, search_history_size); - } - - if (pattern != a->args[0]) { - free(pattern); - } - } else if (cmd == 'n') { - search_next(); - } else if (cmd == 'p') { - search_prev(); - } else { - set_input_mode(INPUT_SEARCH); - search_set_direction(dir); - } -} - -static void cmd_select(const CommandArgs *a) -{ - const char *pf = a->flags; - SelectionType sel = SELECT_CHARS; - bool block = false; - bool keep = false; - - while (*pf) { - switch (*pf) { - case 'b': - block = true; - break; - case 'k': - keep = true; - break; - case 'l': - block = false; - sel = SELECT_LINES; - break; - } - pf++; - } - - view->next_movement_cancels_selection = false; - - if (block) { - select_block(); - return; - } - - if (view->selection) { - if (!keep && view->selection == sel) { - unselect(); - return; - } - view->selection = sel; - mark_all_lines_changed(buffer); - return; - } - - view->sel_so = block_iter_get_offset(&view->cursor); - view->sel_eo = UINT_MAX; - view->selection = sel; - - // Need to mark current line changed because cursor might - // move up or down before screen is updated - view_update_cursor_y(view); - buffer_mark_lines_changed(view->buffer, view->cy, view->cy); -} - -static void cmd_set(const CommandArgs *a) -{ - const char *pf = a->flags; - bool global = false; - bool local = false; - while (*pf) { - switch (*pf) { - case 'g': - global = true; - break; - case 'l': - local = true; - break; - } - pf++; - } - - // You can set only global values in config file - if (buffer == NULL) { - if (local) { - error_msg("Flag -l makes no sense in config file."); - return; - } - global = true; - } - - char **args = a->args; - size_t count = a->nr_args; - if (count == 1) { - set_bool_option(args[0], local, global); - return; - } else if (count % 2 != 0) { - error_msg("One or even number of arguments expected."); - return; - } - - for (size_t i = 0; i < count; i += 2) { - set_option(args[i], args[i + 1], local, global); - } -} - -static void cmd_setenv(const CommandArgs *a) -{ - char **args = a->args; - if (setenv(args[0], args[1], 1) < 0) { - switch (errno) { - case EINVAL: - error_msg("Invalid environment variable name '%s'", args[0]); - break; - default: - error_msg("%s", strerror(errno)); - } - } -} - -static void cmd_shift(const CommandArgs *a) -{ - const char *arg = a->args[0]; - int count; - if (!str_to_int(arg, &count)) { - error_msg("Invalid number: %s", arg); - return; - } - if (count == 0) { - error_msg("Count must be non-zero."); - return; - } - shift_lines(count); -} - -static void show_alias(const char *alias_name, bool write_to_cmdline) -{ - const char *cmd_str = find_alias(alias_name); - if (cmd_str == NULL) { - if (find_command(commands, alias_name)) { - info_msg("%s is a built-in command, not an alias", alias_name); - } else { - info_msg("%s is not a known alias", alias_name); - } - return; - } - - if (write_to_cmdline) { - set_input_mode(INPUT_COMMAND); - cmdline_set_text(&editor.cmdline, cmd_str); - } else { - info_msg("%s is aliased to: %s", alias_name, cmd_str); - } -} - -static void show_binding(const char *keystr, bool write_to_cmdline) -{ - KeyCode key; - if (!parse_key(&key, keystr)) { - error_msg("invalid key string: %s", keystr); - return; - } - - if (u_is_unicode(key)) { - info_msg("%s is not a bindable key", keystr); - return; - } - - const KeyBinding *b = lookup_binding(key); - if (b == NULL) { - info_msg("%s is not bound to a command", keystr); - return; - } - - if (write_to_cmdline) { - set_input_mode(INPUT_COMMAND); - cmdline_set_text(&editor.cmdline, b->cmd_str); - } else { - info_msg("%s is bound to: %s", keystr, b->cmd_str); - } -} - -static void cmd_show(const CommandArgs *a) -{ - typedef enum { - CMD_ALIAS, - CMD_BIND, - } CommandType; - - CommandType cmdtype; - if (streq(a->args[0], "alias")) { - cmdtype = CMD_ALIAS; - } else if (streq(a->args[0], "bind")) { - cmdtype = CMD_BIND; - } else { - error_msg("argument #1 must be: 'alias' or 'bind'"); - return; - } - - const bool cflag = a->nr_flags != 0; - if (a->nr_args == 2) { - const char *str = a->args[1]; - switch (cmdtype) { - case CMD_ALIAS: - show_alias(str, cflag); - break; - case CMD_BIND: - show_binding(str, cflag); - break; - } - return; - } - - if (cflag) { - error_msg("\"show -c\" requires 2 arguments"); - return; - } - - char tmp[32] = "/tmp/.dte.XXXXXX"; - int fd = mkstemp(tmp); - if (fd < 0) { - error_msg("mkstemp() failed: %s", strerror(errno)); - return; - } - - String s; - switch (cmdtype) { - case CMD_ALIAS: - s = dump_aliases(); - break; - case CMD_BIND: - s = dump_bindings(); - break; - } - - ssize_t rc = xwrite(fd, s.buffer, s.len); - int err = errno; - close(fd); - string_free(&s); - if (rc < 0) { - error_msg("write() failed: %s", strerror(err)); - unlink(tmp); - return; - } - - const char *argv[] = {editor.pager, tmp, NULL}; - int child_fds[3] = {0, 1, 2}; - spawn((char**)argv, child_fds, false); - unlink(tmp); -} - -static void cmd_suspend(const CommandArgs* UNUSED_ARG(a)) -{ - suspend(); -} - -static void cmd_tag(const CommandArgs *a) -{ - if (a->flags[0] == 'r') { - pop_file_location(); - return; - } - - clear_messages(); - TagFile *tf = load_tag_file(); - if (tf == NULL) { - error_msg("No tag file."); - return; - } - - const char *name = a->args[0]; - char *word = NULL; - if (!name) { - word = view_get_word_under_cursor(view); - if (!word) { - return; - } - name = word; - } - - PointerArray tags = PTR_ARRAY_INIT; - // Filename helps to find correct tags - tag_file_find_tags(tf, buffer->abs_filename, name, &tags); - if (tags.count == 0) { - error_msg("Tag %s not found.", name); - } else { - for (size_t i = 0; i < tags.count; i++) { - Tag *t = tags.ptrs[i]; - char buf[512]; - xsnprintf(buf, sizeof(buf), "Tag %s", name); - Message *m = new_message(buf); - m->loc = xnew0(FileLocation, 1); - m->loc->filename = tag_file_get_tag_filename(tf, t); - if (t->pattern) { - m->loc->pattern = t->pattern; - t->pattern = NULL; - } else { - m->loc->line = t->line; - } - add_message(m); - } - free_tags(&tags); - activate_current_message_save(); - } - free(word); -} - -static void cmd_toggle(const CommandArgs *a) -{ - const char *pf = a->flags; - bool global = false; - bool verbose = false; - while (*pf) { - switch (*pf) { - case 'g': - global = true; - break; - case 'v': - verbose = true; - break; - } - pf++; - } - - const char *option_name = a->args[0]; - size_t nr_values = a->nr_args - 1; - if (nr_values) { - char **values = a->args + 1; - toggle_option_values(option_name, global, verbose, values, nr_values); - } else { - toggle_option(option_name, global, verbose); - } -} - -static void cmd_undo(const CommandArgs* UNUSED_ARG(a)) -{ - if (undo()) { - unselect(); - } -} - -static void cmd_unselect(const CommandArgs* UNUSED_ARG(a)) -{ - unselect(); -} - -static void cmd_up(const CommandArgs *a) -{ - handle_select_chars_or_lines_flags(a->flags); - move_up(1); -} - -static void cmd_view(const CommandArgs *a) -{ - BUG_ON(window->views.count == 0); - const char *arg = a->args[0]; - size_t idx; - if (streq(arg, "last")) { - idx = window->views.count - 1; - } else { - if (!str_to_size(arg, &idx) || idx == 0) { - error_msg("Invalid view index: %s", arg); - return; - } - idx--; - if (idx > window->views.count - 1) { - idx = window->views.count - 1; - } - } - set_view(window->views.ptrs[idx]); -} - -static void cmd_wclose(const CommandArgs *a) -{ - View *v = window_find_unclosable_view(window, view_can_close); - bool force = a->flags[0] == 'f'; - if (v != NULL && !force) { - set_view(v); - error_msg ( - "Save modified files or run 'wclose -f' to close " - "window without saving." - ); - return; - } - window_close_current(); -} - -static void cmd_wflip(const CommandArgs* UNUSED_ARG(a)) -{ - Frame *f = window->frame; - if (f->parent == NULL) { - return; - } - f->parent->vertical ^= 1; - mark_everything_changed(); -} - -static void cmd_wnext(const CommandArgs* UNUSED_ARG(a)) -{ - window = next_window(window); - set_view(window->view); - mark_everything_changed(); - debug_frames(); -} - -static void cmd_word_bwd(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - bool skip_non_word = strchr(a->flags, 's'); - word_bwd(&view->cursor, skip_non_word); - view_reset_preferred_x(view); -} - -static void cmd_word_fwd(const CommandArgs *a) -{ - handle_select_chars_flag(a->flags); - bool skip_non_word = strchr(a->flags, 's'); - word_fwd(&view->cursor, skip_non_word); - view_reset_preferred_x(view); -} - -static void cmd_wprev(const CommandArgs* UNUSED_ARG(a)) -{ - window = prev_window(window); - set_view(window->view); - mark_everything_changed(); - debug_frames(); -} - -static void cmd_wrap_paragraph(const CommandArgs *a) -{ - const char *arg = a->args[0]; - size_t width = (size_t)buffer->options.text_width; - if (arg) { - if (!str_to_size(arg, &width) || width == 0 || width > 1000) { - error_msg("Invalid paragraph width: %s", arg); - return; - } - } - format_paragraph(width); -} - -static void cmd_wresize(const CommandArgs *a) -{ - if (window->frame->parent == NULL) { - // Only window - return; - } - - const char *pf = a->flags; - ResizeDirection dir = RESIZE_DIRECTION_AUTO; - while (*pf) { - switch (*pf) { - case 'h': - dir = RESIZE_DIRECTION_HORIZONTAL; - break; - case 'v': - dir = RESIZE_DIRECTION_VERTICAL; - break; - } - pf++; - } - - const char *arg = a->args[0]; - if (arg) { - int n; - if (!str_to_int(arg, &n)) { - error_msg("Invalid resize value: %s", arg); - return; - } - if (arg[0] == '+' || arg[0] == '-') { - add_to_frame_size(window->frame, dir, n); - } else { - resize_frame(window->frame, dir, n); - } - } else { - equalize_frame_sizes(window->frame->parent); - } - mark_everything_changed(); - debug_frames(); -} - -static void cmd_wsplit(const CommandArgs *a) -{ - const char *pf = a->flags; - bool before = false; - bool vertical = false; - bool root = false; - - while (*pf) { - switch (*pf) { - case 'b': - // Add new window before current window - before = true; - break; - case 'h': - // Split horizontally to get vertical layout - vertical = true; - break; - case 'r': - // Split root frame instead of current window - root = true; - break; - } - pf++; - } - - Frame *f; - if (root) { - f = split_root(vertical, before); - } else { - f = split_frame(window, vertical, before); - } - - View *save = view; - window = f->window; - view = NULL; - buffer = NULL; - mark_everything_changed(); - - if (a->args[0]) { - window_open_files(window, a->args, NULL); - } else { - View *new = window_add_buffer(window, save->buffer); - new->cursor = save->cursor; - set_view(new); - } - - if (window->views.count == 0) { - // Open failed, remove new window - remove_frame(window->frame); - - view = save; - buffer = view->buffer; - window = view->window; - } - - debug_frames(); -} - -static void cmd_wswap(const CommandArgs* UNUSED_ARG(a)) -{ - Frame *parent = window->frame->parent; - if (parent == NULL) { - return; - } - - size_t i = ptr_array_idx(&parent->frames, window->frame); - size_t j = i + 1; - if (j == parent->frames.count) { - j = 0; - } - - Frame *tmp = parent->frames.ptrs[i]; - parent->frames.ptrs[i] = parent->frames.ptrs[j]; - parent->frames.ptrs[j] = tmp; - mark_everything_changed(); -} - -// Prevent Clang whining about .max_args = -1 -#if HAS_WARNING("-Wbitfield-constant-conversion") - IGNORE_WARNING("-Wbitfield-constant-conversion") -#endif - -const Command commands[] = { - {"alias", "", 2, 2, cmd_alias}, - {"bind", "", 1, 2, cmd_bind}, - {"bof", "", 0, 0, cmd_bof}, - {"bol", "cs", 0, 0, cmd_bol}, - {"bolsf", "", 0, 0, cmd_bolsf}, - {"case", "lu", 0, 0, cmd_case}, - {"cd", "", 1, 1, cmd_cd}, - {"center-view", "", 0, 0, cmd_center_view}, - {"clear", "", 0, 0, cmd_clear}, - {"close", "fqw", 0, 0, cmd_close}, - {"command", "", 0, 1, cmd_command}, - {"compile", "-1ps", 2, -1, cmd_compile}, - {"copy", "k", 0, 0, cmd_copy}, - {"cut", "", 0, 0, cmd_cut}, - {"delete", "", 0, 0, cmd_delete}, - {"delete-eol", "n", 0, 0, cmd_delete_eol}, - {"delete-word", "s", 0, 0, cmd_delete_word}, - {"down", "cl", 0, 0, cmd_down}, - {"eof", "", 0, 0, cmd_eof}, - {"eol", "c", 0, 0, cmd_eol}, - {"eolsf", "", 0, 0, cmd_eolsf}, - {"erase", "", 0, 0, cmd_erase}, - {"erase-bol", "", 0, 0, cmd_erase_bol}, - {"erase-word", "s", 0, 0, cmd_erase_word}, - {"errorfmt", "i", 2, 18, cmd_errorfmt}, - {"eval", "-", 1, -1, cmd_eval}, - {"filter", "-", 1, -1, cmd_filter}, - {"ft", "-bcfi", 2, -1, cmd_ft}, - {"git-open", "", 0, 0, cmd_git_open}, - {"hi", "-", 0, -1, cmd_hi}, - {"include", "b", 1, 1, cmd_include}, - {"insert", "km", 1, 1, cmd_insert}, - {"insert-builtin", "", 1, 1, cmd_insert_builtin}, - {"join", "", 0, 0, cmd_join}, - {"left", "c", 0, 0, cmd_left}, - {"line", "", 1, 1, cmd_line}, - {"load-syntax", "", 1, 1, cmd_load_syntax}, - {"move-tab", "", 1, 1, cmd_move_tab}, - {"msg", "np", 0, 0, cmd_msg}, - {"new-line", "", 0, 0, cmd_new_line}, - {"next", "", 0, 0, cmd_next}, - {"open", "e=g", 0, -1, cmd_open}, - {"option", "-r", 3, -1, cmd_option}, - {"paste", "c", 0, 0, cmd_paste}, - {"pgdown", "cl", 0, 0, cmd_pgdown}, - {"pgup", "cl", 0, 0, cmd_pgup}, - {"pipe-from", "-ms", 1, -1, cmd_pipe_from}, - {"pipe-to", "-", 1, -1, cmd_pipe_to}, - {"prev", "", 0, 0, cmd_prev}, - {"quit", "fp", 0, 0, cmd_quit}, - {"redo", "", 0, 1, cmd_redo}, - {"refresh", "", 0, 0, cmd_refresh}, - {"repeat", "", 2, -1, cmd_repeat}, - {"replace", "bcgi", 2, 2, cmd_replace}, - {"right", "c", 0, 0, cmd_right}, - {"run", "-ps", 1, -1, cmd_run}, - {"save", "de=fpu", 0, 1, cmd_save}, - {"scroll-down", "", 0, 0, cmd_scroll_down}, - {"scroll-pgdown", "", 0, 0, cmd_scroll_pgdown}, - {"scroll-pgup", "", 0, 0, cmd_scroll_pgup}, - {"scroll-up", "", 0, 0, cmd_scroll_up}, - {"search", "Hnprw", 0, 1, cmd_search}, - {"select", "bkl", 0, 0, cmd_select}, - {"set", "gl", 1, -1, cmd_set}, - {"setenv", "", 2, 2, cmd_setenv}, - {"shift", "", 1, 1, cmd_shift}, - {"show", "c", 1, 2, cmd_show}, - {"suspend", "", 0, 0, cmd_suspend}, - {"tag", "r", 0, 1, cmd_tag}, - {"toggle", "glv", 1, -1, cmd_toggle}, - {"undo", "", 0, 0, cmd_undo}, - {"unselect", "", 0, 0, cmd_unselect}, - {"up", "cl", 0, 0, cmd_up}, - {"view", "", 1, 1, cmd_view}, - {"wclose", "f", 0, 0, cmd_wclose}, - {"wflip", "", 0, 0, cmd_wflip}, - {"wnext", "", 0, 0, cmd_wnext}, - {"word-bwd", "cs", 0, 0, cmd_word_bwd}, - {"word-fwd", "cs", 0, 0, cmd_word_fwd}, - {"wprev", "", 0, 0, cmd_wprev}, - {"wrap-paragraph", "", 0, 1, cmd_wrap_paragraph}, - {"wresize", "hv", 0, 1, cmd_wresize}, - {"wsplit", "bhr", 0, -1, cmd_wsplit}, - {"wswap", "", 0, 0, cmd_wswap}, - {"", "", 0, 0, NULL} -}; - -UNIGNORE_WARNINGS diff -Nru dte-1.9.1/src/command.h dte-1.10/src/command.h --- dte-1.9.1/src/command.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/command.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,49 +0,0 @@ -#ifndef COMMAND_H -#define COMMAND_H - -#include -#include -#include -#include "util/ptr-array.h" - -typedef struct { - char flags[8]; - char **args; - size_t nr_flags; - size_t nr_args; -} CommandArgs; - -typedef struct { - const char name[15]; - const char flags[7]; - uint16_t min_args : 4; - uint16_t max_args : 12; - void (*cmd)(const CommandArgs *args); -} Command; - -typedef enum { - CMDERR_NONE, - CMDERR_UNCLOSED_SINGLE_QUOTE, - CMDERR_UNCLOSED_DOUBLE_QUOTE, - CMDERR_UNEXPECTED_EOF, -} CommandParseError; - -#define CMD_ARG_MAX 4095 // (1 << 12) - 1 - -// command-parse.c -char *parse_command_arg(const char *cmd, size_t len, bool tilde); -size_t find_end(const char *cmd, size_t pos, CommandParseError *err); -bool parse_commands(PointerArray *array, const char *cmd, CommandParseError *err); -const char *command_parse_error_to_string(CommandParseError err); - -// command-run.c -extern const Command *current_command; -const Command *find_command(const Command *cmds, const char *name); -void run_commands(const Command *cmds, const PointerArray *array); -void run_command(const Command *cmds, char **argv); -void handle_command(const Command *cmds, const char *cmd); - -// command.c -extern const Command commands[]; - -#endif diff -Nru dte-1.9.1/src/command-parse.c dte-1.10/src/command-parse.c --- dte-1.9.1/src/command-parse.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/command-parse.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,290 +0,0 @@ -#include "command.h" -#include "debug.h" -#include "editor.h" -#include "env.h" -#include "error.h" -#include "util/ascii.h" -#include "util/ptr-array.h" -#include "util/str-util.h" -#include "util/string.h" -#include "util/utf8.h" -#include "util/xmalloc.h" - -static size_t parse_sq(const char *cmd, size_t len, String *buf) -{ - size_t pos = 0; - char ch = '\0'; - while (pos < len) { - ch = cmd[pos]; - if (ch == '\'') { - break; - } - pos++; - } - string_add_buf(buf, cmd, pos); - if (ch == '\'') { - pos++; - } - return pos; -} - -static size_t unicode_escape(const char *str, size_t count, String *buf) -{ - if (count == 0) { - return 0; - } - - CodePoint u = 0; - size_t i; - for (i = 0; i < count; i++) { - int x = hex_decode(str[i]); - if (x < 0) { - break; - } - u = u << 4 | x; - } - if (u_is_unicode(u)) { - string_add_ch(buf, u); - } - return i; -} - -static size_t min(size_t a, size_t b) -{ - return (a < b) ? a : b; -} - -static size_t parse_dq(const char *cmd, size_t len, String *buf) -{ - size_t pos = 0; - while (pos < len) { - unsigned char ch = cmd[pos++]; - - if (ch == '"') { - break; - } - - if (ch == '\\' && pos < len) { - ch = cmd[pos++]; - switch (ch) { - case 'a': ch = '\a'; break; - case 'b': ch = '\b'; break; - case 'f': ch = '\f'; break; - case 'n': ch = '\n'; break; - case 'r': ch = '\r'; break; - case 't': ch = '\t'; break; - case 'v': ch = '\v'; break; - case '\\': - case '"': - break; - case 'x': - if (pos < len) { - const int x1 = hex_decode(cmd[pos]); - if (x1 >= 0 && ++pos < len) { - const int x2 = hex_decode(cmd[pos]); - if (x2 >= 0) { - pos++; - ch = x1 << 4 | x2; - break; - } - } - } - continue; - case 'u': - pos += unicode_escape(cmd + pos, min(4, len - pos), buf); - continue; - case 'U': - pos += unicode_escape(cmd + pos, min(8, len - pos), buf); - continue; - default: - string_add_byte(buf, '\\'); - break; - } - } - - string_add_byte(buf, ch); - } - - return pos; -} - -static size_t parse_var(const char *cmd, size_t len, String *buf) -{ - if (len == 0 || !is_alpha_or_underscore(cmd[0])) { - return 0; - } - - size_t n = 1; - while (n < len && is_alnum_or_underscore(cmd[n])) { - n++; - } - - char *name = xstrcut(cmd, n); - char *value; - if (expand_builtin_env(name, &value)) { - if (value != NULL) { - string_add_str(buf, value); - free(value); - } - } else { - const char *val = getenv(name); - if (val != NULL) { - string_add_str(buf, val); - } - } - - free(name); - return n; -} - -char *parse_command_arg(const char *cmd, size_t len, bool tilde) -{ - String buf; - size_t pos = 0; - - if (tilde && len >= 2 && cmd[0] == '~' && cmd[1] == '/') { - const size_t home_dir_len = strlen(editor.home_dir); - buf = string_new(len + home_dir_len); - string_add_buf(&buf, editor.home_dir, home_dir_len); - string_add_byte(&buf, '/'); - pos += 2; - } else { - buf = string_new(len); - } - - while (pos < len) { - char ch = cmd[pos++]; - switch (ch) { - case '\t': - case '\n': - case '\r': - case ' ': - case ';': - goto end; - case '\'': - pos += parse_sq(cmd + pos, len - pos, &buf); - break; - case '"': - pos += parse_dq(cmd + pos, len - pos, &buf); - break; - case '$': - pos += parse_var(cmd + pos, len - pos, &buf); - break; - case '\\': - if (pos == len) { - goto end; - } - ch = cmd[pos++]; - // Fallthrough - default: - string_add_byte(&buf, ch); - break; - } - } - -end: - return string_steal_cstring(&buf); -} - -size_t find_end(const char *cmd, const size_t startpos, CommandParseError *err) -{ - size_t pos = startpos; - while (1) { - switch (cmd[pos++]) { - case '\'': - while (1) { - if (cmd[pos] == '\'') { - pos++; - break; - } - if (cmd[pos] == '\0') { - *err = CMDERR_UNCLOSED_SINGLE_QUOTE; - return 0; - } - pos++; - } - break; - case '"': - while (1) { - if (cmd[pos] == '"') { - pos++; - break; - } - if (cmd[pos] == '\0') { - *err = CMDERR_UNCLOSED_DOUBLE_QUOTE; - return 0; - } - if (cmd[pos++] == '\\') { - if (cmd[pos] == '\0') { - goto unexpected_eof; - } - pos++; - } - } - break; - case '\\': - if (cmd[pos] == '\0') { - goto unexpected_eof; - } - pos++; - break; - case '\0': - case '\t': - case '\n': - case '\r': - case ' ': - case ';': - return pos - 1; - } - } - BUG("Unexpected break of outer loop"); -unexpected_eof: - *err = CMDERR_UNEXPECTED_EOF; - return 0; -} - -bool parse_commands(PointerArray *array, const char *cmd, CommandParseError *err) -{ - size_t pos = 0; - - while (1) { - while (ascii_isspace(cmd[pos])) { - pos++; - } - - if (cmd[pos] == '\0') { - break; - } - - if (cmd[pos] == ';') { - ptr_array_add(array, NULL); - pos++; - continue; - } - - size_t end = find_end(cmd, pos, err); - if (*err != CMDERR_NONE) { - return false; - } - - ptr_array_add(array, parse_command_arg(cmd + pos, end - pos, true)); - pos = end; - } - ptr_array_add(array, NULL); - return true; -} - -const char *command_parse_error_to_string(CommandParseError err) -{ - switch (err) { - case CMDERR_UNCLOSED_SINGLE_QUOTE: - return "Missing '"; - case CMDERR_UNCLOSED_DOUBLE_QUOTE: - return "Missing \""; - case CMDERR_UNEXPECTED_EOF: - return "Unexpected EOF"; - case CMDERR_NONE: - break; - } - return NULL; -} diff -Nru dte-1.9.1/src/command-run.c dte-1.10/src/command-run.c --- dte-1.9.1/src/command-run.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/command-run.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,143 +0,0 @@ -#include "alias.h" -#include "change.h" -#include "command.h" -#include "debug.h" -#include "config.h" -#include "error.h" -#include "parse-args.h" -#include "util/str-util.h" -#include "util/xmalloc.h" - -const Command *current_command; - -static bool allowed_command(const char *name) -{ - size_t len = strlen(name); - switch (len) { - case 3: return !memcmp(name, "set", len); - case 4: return !memcmp(name, "bind", len); - case 5: return !memcmp(name, "alias", len); - case 7: return !memcmp(name, "include", len); - case 8: return !memcmp(name, "errorfmt", len); - case 11: return !memcmp(name, "load-syntax", len); - case 2: - switch (name[0]) { - case 'c': return name[1] == 'd'; - case 'f': return name[1] == 't'; - case 'h': return name[1] == 'i'; - } - return false; - case 6: - switch (name[0]) { - case 'o': return !memcmp(name, "option", len); - case 's': return !memcmp(name, "setenv", len); - } - return false; - } - return false; -} - -UNITTEST { - BUG_ON(!allowed_command("alias")); - BUG_ON(!allowed_command("cd")); - BUG_ON(!allowed_command("include")); - BUG_ON(!allowed_command("set")); - BUG_ON(allowed_command("alias_")); - BUG_ON(allowed_command("c")); - BUG_ON(allowed_command("cD")); -} - -const Command *find_command(const Command *cmds, const char *name) -{ - for (size_t i = 0; cmds[i].cmd; i++) { - const Command *cmd = &cmds[i]; - if (streq(name, cmd->name)) { - return cmd; - } - } - return NULL; -} - -void run_command(const Command *cmds, char **av) -{ - const Command *cmd = find_command(cmds, av[0]); - if (!cmd) { - PointerArray array = PTR_ARRAY_INIT; - const char *alias_name = av[0]; - const char *alias_value = find_alias(alias_name); - CommandParseError err = 0; - - if (alias_value == NULL) { - error_msg("No such command or alias: %s", alias_name); - return; - } - if (!parse_commands(&array, alias_value, &err)) { - const char *err_msg = command_parse_error_to_string(err); - error_msg("Parsing alias %s: %s", alias_name, err_msg); - ptr_array_free(&array); - return; - } - - // Remove NULL - array.count--; - - for (size_t i = 1; av[i]; i++) { - ptr_array_add(&array, xstrdup(av[i])); - } - ptr_array_add(&array, NULL); - - run_commands(cmds, &array); - ptr_array_free(&array); - return; - } - - if (config_file && cmds == commands && !allowed_command(cmd->name)) { - error_msg("Command %s not allowed in config file.", cmd->name); - return; - } - - // By default change can't be merged with previous one. - // Any command can override this by calling begin_change() again. - begin_change(CHANGE_MERGE_NONE); - - CommandArgs a = {.args = av + 1}; - current_command = cmd; - if (parse_args(cmd, &a)) { - cmd->cmd(&a); - } - current_command = NULL; - - end_change(); -} - -void run_commands(const Command *cmds, const PointerArray *array) -{ - size_t s = 0; - while (s < array->count) { - size_t e = s; - while (e < array->count && array->ptrs[e]) { - e++; - } - - if (e > s) { - run_command(cmds, (char **)array->ptrs + s); - } - - s = e + 1; - } -} - -void handle_command(const Command *cmds, const char *cmd) -{ - CommandParseError err = 0; - PointerArray array = PTR_ARRAY_INIT; - - if (!parse_commands(&array, cmd, &err)) { - error_msg("%s", command_parse_error_to_string(err)); - ptr_array_free(&array); - return; - } - - run_commands(cmds, &array); - ptr_array_free(&array); -} diff -Nru dte-1.9.1/src/commands.c dte-1.10/src/commands.c --- dte-1.9.1/src/commands.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/commands.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,2258 @@ +#include +#include +#include +#include +#include +#include "commands.h" +#include "alias.h" +#include "bind.h" +#include "change.h" +#include "cmdline.h" +#include "command/args.h" +#include "command/macro.h" +#include "command/serialize.h" +#include "completion.h" +#include "config.h" +#include "convert.h" +#include "edit.h" +#include "editor.h" +#include "encoding.h" +#include "error.h" +#include "file-option.h" +#include "filetype.h" +#include "frame.h" +#include "history.h" +#include "load-save.h" +#include "lock.h" +#include "move.h" +#include "msg.h" +#include "regexp.h" +#include "search.h" +#include "selection.h" +#include "show.h" +#include "spawn.h" +#include "syntax/state.h" +#include "syntax/syntax.h" +#include "tag.h" +#include "terminal/color.h" +#include "terminal/terminal.h" +#include "util/bsearch.h" +#include "util/checked-arith.h" +#include "util/debug.h" +#include "util/path.h" +#include "util/str-util.h" +#include "util/strtonum.h" +#include "util/xmalloc.h" +#include "util/xsnprintf.h" +#include "view.h" +#include "window.h" + +static void do_selection(SelectionType sel) +{ + if (sel == SELECT_NONE) { + if (view->next_movement_cancels_selection) { + view->next_movement_cancels_selection = false; + unselect(); + } + return; + } + + view->next_movement_cancels_selection = true; + + if (view->selection) { + view->selection = sel; + mark_all_lines_changed(buffer); + return; + } + + view->sel_so = block_iter_get_offset(&view->cursor); + view->sel_eo = UINT_MAX; + view->selection = sel; + + // Need to mark current line changed because cursor might + // move up or down before screen is updated + view_update_cursor_y(view); + buffer_mark_lines_changed(buffer, view->cy, view->cy); +} + +static char last_flag_or_default(const CommandArgs *a, char def) +{ + size_t n = a->nr_flags; + return n ? a->flags[n - 1] : def; +} + +static char last_flag(const CommandArgs *a) +{ + return last_flag_or_default(a, 0); +} + +static bool has_flag(const CommandArgs *a, unsigned char flag) +{ + return cmdargs_has_flag(a, flag); +} + +static void handle_select_chars_flag(const CommandArgs *a) +{ + do_selection(has_flag(a, 'c') ? SELECT_CHARS : SELECT_NONE); +} + +static void handle_select_chars_or_lines_flags(const CommandArgs *a) +{ + SelectionType sel = SELECT_NONE; + if (has_flag(a, 'l')) { + sel = SELECT_LINES; + } else if (has_flag(a, 'c')) { + sel = SELECT_CHARS; + } + do_selection(sel); +} + +static void cmd_alias(const CommandArgs *a) +{ + const char *const name = a->args[0]; + if (unlikely(name[0] == '\0')) { + error_msg("Empty alias name not allowed"); + return; + } + + for (size_t i = 0; name[i]; i++) { + unsigned char c = name[i]; + if (unlikely(!(is_word_byte(c) || c == '-' || c == '?' || c == '!'))) { + error_msg("Invalid byte in alias name: %c (0x%02hhX)", c, c); + return; + } + } + + if (unlikely(find_normal_command(name))) { + error_msg("Can't replace existing command %s with an alias", name); + return; + } + + add_alias(name, a->args[1]); +} + +static void cmd_bind(const CommandArgs *a) +{ + const char *key = a->args[0]; + const char *cmd = a->args[1]; + if (cmd) { + add_binding(key, cmd); + } else { + remove_binding(key); + } +} + +static void cmd_bof(const CommandArgs* UNUSED_ARG(a)) +{ + do_selection(SELECT_NONE); + move_bof(); +} + +static void cmd_bol(const CommandArgs *a) +{ + handle_select_chars_flag(a); + if (has_flag(a, 's')) { + move_bol_smart(); + } else { + move_bol(); + } +} + +static void cmd_bolsf(const CommandArgs* UNUSED_ARG(a)) +{ + do_selection(SELECT_NONE); + if (!block_iter_bol(&view->cursor)) { + long top = view->vy + window_get_scroll_margin(window); + if (view->cy > top) { + move_up(view->cy - top); + } else { + block_iter_bof(&view->cursor); + } + } + view_reset_preferred_x(view); +} + +static void cmd_bookmark(const CommandArgs *a) +{ + if (has_flag(a, 'r')) { + pop_file_location(); + return; + } + + push_file_location(get_current_file_location()); +} + +static void cmd_case(const CommandArgs *a) +{ + change_case(last_flag_or_default(a, 't')); +} + +static void cmd_cd(const CommandArgs *a) +{ + const char *dir = a->args[0]; + char cwd[8192]; + const char *cwdp = NULL; + bool got_cwd = !!getcwd(cwd, sizeof(cwd)); + + if (streq(dir, "-")) { + dir = getenv("OLDPWD"); + if (!dir || dir[0] == '\0') { + error_msg("cd: OLDPWD not set"); + return; + } + } + if (chdir(dir)) { + perror_msg("cd"); + return; + } + + if (got_cwd) { + setenv("OLDPWD", cwd, 1); + } + got_cwd = !!getcwd(cwd, sizeof(cwd)); + if (got_cwd) { + setenv("PWD", cwd, 1); + cwdp = cwd; + } + + for (size_t i = 0, n = editor.buffers.count; i < n; i++) { + Buffer *b = editor.buffers.ptrs[i]; + update_short_filename_cwd(b, cwdp); + } + + // Need to update all tabbars + mark_everything_changed(); +} + +static void cmd_center_view(const CommandArgs* UNUSED_ARG(a)) +{ + view->force_center = true; +} + +static void cmd_clear(const CommandArgs* UNUSED_ARG(a)) +{ + clear_lines(); +} + +static void cmd_close(const CommandArgs *a) +{ + bool force = has_flag(a, 'f'); + bool prompt = has_flag(a, 'p'); + bool allow_quit = has_flag(a, 'q'); + bool allow_wclose = has_flag(a, 'w'); + + if (!view_can_close(view) && !force) { + if (prompt) { + if (dialog_prompt("Close without saving changes? [y/N]", "ny") != 'y') { + return; + } + } else { + error_msg ( + "The buffer is modified. " + "Save or run 'close -f' to close without saving." + ); + return; + } + } + + if (allow_quit && editor.buffers.count == 1 && root_frame->frames.count <= 1) { + editor.status = EDITOR_EXITING; + return; + } + + if (allow_wclose && window->views.count <= 1) { + window_close_current(); + return; + } + + window_close_current_view(window); + set_view(window->view); +} + +static void cmd_command(const CommandArgs *a) +{ + const char *text = a->args[0]; + set_input_mode(INPUT_COMMAND); + if (text) { + cmdline_set_text(&editor.cmdline, text); + } +} + +static void cmd_compile(const CommandArgs *a) +{ + SpawnFlags flags = SPAWN_DEFAULT; + for (const char *pf = a->flags; *pf; pf++) { + switch (*pf) { + case '1': + flags |= SPAWN_READ_STDOUT; + break; + case 'p': + flags |= SPAWN_PROMPT; + break; + case 's': + flags |= SPAWN_QUIET; + break; + } + } + + const char *name = a->args[0]; + Compiler *c = find_compiler(name); + if (!c) { + error_msg("No such error parser %s", name); + return; + } + clear_messages(); + spawn_compiler(a->args + 1, flags, c); + if (message_count()) { + activate_current_message_save(); + } +} + +static void cmd_copy(const CommandArgs *a) +{ + BlockIter save = view->cursor; + if (view->selection) { + copy(prepare_selection(view), view->selection == SELECT_LINES); + bool keep_selection = has_flag(a, 'k'); + if (!keep_selection) { + unselect(); + } + } else { + block_iter_bol(&view->cursor); + BlockIter tmp = view->cursor; + copy(block_iter_eat_line(&tmp), true); + } + view->cursor = save; +} + +static void cmd_cut(const CommandArgs* UNUSED_ARG(a)) +{ + const long x = view_get_preferred_x(view); + if (view->selection) { + cut(prepare_selection(view), view->selection == SELECT_LINES); + if (view->selection == SELECT_LINES) { + move_to_preferred_x(x); + } + unselect(); + } else { + BlockIter tmp; + block_iter_bol(&view->cursor); + tmp = view->cursor; + cut(block_iter_eat_line(&tmp), true); + move_to_preferred_x(x); + } +} + +static void cmd_delete(const CommandArgs* UNUSED_ARG(a)) +{ + delete_ch(); +} + +static void cmd_delete_eol(const CommandArgs *a) +{ + if (view->selection) { + return; + } + + bool delete_newline_if_at_eol = has_flag(a, 'n'); + BlockIter bi = view->cursor; + if (delete_newline_if_at_eol) { + CodePoint ch; + if (block_iter_get_char(&view->cursor, &ch) == 1 && ch == '\n') { + delete_ch(); + return; + } + } + + buffer_delete_bytes(block_iter_eol(&bi)); +} + +static void cmd_delete_line(const CommandArgs* UNUSED_ARG(a)) +{ + delete_lines(); +} + +static void cmd_delete_word(const CommandArgs *a) +{ + bool skip_non_word = has_flag(a, 's'); + BlockIter bi = view->cursor; + buffer_delete_bytes(word_fwd(&bi, skip_non_word)); +} + +static void cmd_down(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + move_down(1); +} + +static void cmd_eof(const CommandArgs* UNUSED_ARG(a)) +{ + do_selection(SELECT_NONE); + move_eof(); +} + +static void cmd_eol(const CommandArgs *a) +{ + handle_select_chars_flag(a); + move_eol(); +} + +static void cmd_eolsf(const CommandArgs* UNUSED_ARG(a)) +{ + do_selection(SELECT_NONE); + if (!block_iter_eol(&view->cursor)) { + long bottom = view->vy + window->edit_h - 1 - window_get_scroll_margin(window); + if (view->cy < bottom) { + move_down(bottom - view->cy); + } else { + block_iter_eof(&view->cursor); + } + } + view_reset_preferred_x(view); +} + +static void cmd_erase(const CommandArgs* UNUSED_ARG(a)) +{ + erase(); +} + +static void cmd_erase_bol(const CommandArgs* UNUSED_ARG(a)) +{ + buffer_erase_bytes(block_iter_bol(&view->cursor)); +} + +static void cmd_erase_word(const CommandArgs *a) +{ + bool skip_non_word = has_flag(a, 's'); + buffer_erase_bytes(word_bwd(&view->cursor, skip_non_word)); +} + +static void cmd_errorfmt(const CommandArgs *a) +{ + bool ignore = has_flag(a, 'i'); + add_error_fmt(a->args[0], ignore, a->args[1], a->args + 2); +} + +static void cmd_eval(const CommandArgs *a) +{ + SpawnContext ctx = { + .argv = a->args, + .input = STRING_VIEW_INIT, + .output = STRING_INIT, + .flags = SPAWN_DEFAULT + }; + if (!spawn_filter(&ctx)) { + return; + } + exec_config(&commands, ctx.output.buffer, ctx.output.len); + string_free(&ctx.output); +} + +static void cmd_exec_open(const CommandArgs *a) +{ + SpawnContext ctx = { + .argv = a->args, + .output = STRING_INIT, + .flags = has_flag(a, 's') ? SPAWN_QUIET : SPAWN_DEFAULT + }; + if (!spawn_source(&ctx)) { + return; + } + + PointerArray filenames = PTR_ARRAY_INIT; + for (size_t pos = 0, size = ctx.output.len; pos < size; ) { + char *filename = buf_next_line(ctx.output.buffer, &pos, size); + if (filename[0] != '\0') { + ptr_array_append(&filenames, filename); + } + } + + ptr_array_append(&filenames, NULL); + window_open_files(window, (char**)filenames.ptrs, NULL); + macro_command_hook("open", (char**)filenames.ptrs); + ptr_array_free_array(&filenames); + string_free(&ctx.output); +} + +static void cmd_exec_tag(const CommandArgs *a) +{ + SpawnContext ctx = { + .argv = a->args, + .output = STRING_INIT, + .flags = has_flag(a, 's') ? SPAWN_QUIET : SPAWN_DEFAULT + }; + if (!spawn_source(&ctx)) { + return; + } + + const char *tag = string_borrow_cstring(&ctx.output); + const char *nl = memchr(tag, '\n', ctx.output.len); + size_t tag_len = nl ? (size_t)(nl - tag) : ctx.output.len; + + String s = string_new(tag_len + 16); + string_append_literal(&s, "tag "); + if (unlikely(tag[0] == '-')) { + string_append_literal(&s, "-- "); + } + string_append_escaped_arg_sv(&s, string_view(tag, tag_len), true); + string_free(&ctx.output); + handle_command(&commands, string_borrow_cstring(&s), true); + string_free(&s); +} + +static const char **lines_and_columns_env(void) +{ + static char lines[DECIMAL_STR_MAX(window->edit_h)]; + static char columns[DECIMAL_STR_MAX(window->edit_w)]; + static const char *vars[] = { + "LINES", lines, + "COLUMNS", columns, + NULL, + }; + + xsnprintf(lines, sizeof lines, "%d", window->edit_h); + xsnprintf(columns, sizeof columns, "%d", window->edit_w); + return vars; +} + +static void cmd_filter(const CommandArgs *a) +{ + BlockIter save = view->cursor; + SpawnContext ctx = { + .argv = a->args, + .env = lines_and_columns_env(), + .input = STRING_VIEW_INIT, + .output = STRING_INIT, + .flags = SPAWN_DEFAULT + }; + + if (view->selection) { + ctx.input.length = prepare_selection(view); + } else if (has_flag(a, 'l')) { + StringView line; + move_bol(); + fill_line_ref(&view->cursor, &line); + ctx.input.length = line.length; + } else { + Block *blk; + block_for_each(blk, &buffer->blocks) { + ctx.input.length += blk->size; + } + move_bof(); + } + + char *input = block_iter_get_bytes(&view->cursor, ctx.input.length); + ctx.input.data = input; + if (!spawn_filter(&ctx)) { + free(input); + view->cursor = save; + return; + } + + free(input); + buffer_replace_bytes(ctx.input.length, ctx.output.buffer, ctx.output.len); + string_free(&ctx.output); + unselect(); +} + +static void cmd_ft(const CommandArgs *a) +{ + char **args = a->args; + const char *filetype = args[0]; + if (unlikely(filetype[0] == '\0')) { + error_msg("Filetype can't be blank"); + return; + } + + FileDetectionType dt = FT_EXTENSION; + switch (last_flag(a)) { + case 'b': + dt = FT_BASENAME; + break; + case 'c': + dt = FT_CONTENT; + break; + case 'f': + dt = FT_FILENAME; + break; + case 'i': + dt = FT_INTERPRETER; + break; + } + + for (size_t i = 1, n = a->nr_args; i < n; i++) { + add_filetype(filetype, args[i], dt); + } +} + +static void cmd_hi(const CommandArgs *a) +{ + if (unlikely(a->nr_args == 0)) { + exec_builtin_color_reset(); + goto update; + } + + TermColor color; + if (unlikely(!parse_term_color(&color, a->args + 1))) { + return; + } + + int32_t fg = color_to_nearest(color.fg, terminal.color_type); + int32_t bg = color_to_nearest(color.bg, terminal.color_type); + if ( + terminal.color_type != TERM_TRUE_COLOR + && has_flag(a, 'c') + && (fg != color.fg || bg != color.bg) + ) { + return; + } + + color.fg = fg; + color.bg = bg; + set_highlight_color(a->args[0], &color); + +update: + // Don't call update_all_syntax_colors() needlessly. + // It is called right after config has been loaded. + if (editor.status != EDITOR_INITIALIZING) { + update_all_syntax_colors(); + mark_everything_changed(); + } +} + +static void cmd_include(const CommandArgs *a) +{ + ConfigFlags flags = has_flag(a, 'q') ? CFG_NOFLAGS : CFG_MUST_EXIST; + if (has_flag(a, 'b')) { + flags |= CFG_BUILTIN; + } + read_config(&commands, a->args[0], flags); +} + +static void cmd_insert(const CommandArgs *a) +{ + const char *str = a->args[0]; + if (has_flag(a, 'k')) { + for (size_t i = 0; str[i]; i++) { + insert_ch(str[i]); + } + return; + } + + bool move_after = has_flag(a, 'm'); + insert_text(str, strlen(str), move_after); +} + +static void cmd_join(const CommandArgs* UNUSED_ARG(a)) +{ + join_lines(); +} + +static void cmd_left(const CommandArgs *a) +{ + handle_select_chars_flag(a); + move_cursor_left(); +} + +static void cmd_line(const CommandArgs *a) +{ + const char *arg = a->args[0]; + const long x = view_get_preferred_x(view); + size_t line; + if (!str_to_size(arg, &line) || line == 0) { + error_msg("Invalid line number: %s", arg); + return; + } + + do_selection(SELECT_NONE); + move_to_line(view, line); + move_to_preferred_x(x); +} + +static void cmd_load_syntax(const CommandArgs *a) +{ + const char *filename = a->args[0]; + const char *filetype = path_basename(filename); + if (filename != filetype) { + if (find_syntax(filetype)) { + error_msg("Syntax for filetype %s already loaded", filetype); + } else { + int err; + load_syntax_file(filename, CFG_MUST_EXIST, &err); + } + } else { + if (!find_syntax(filetype)) { + load_syntax_by_filetype(filetype); + } + } +} + +static void cmd_macro(const CommandArgs *a) +{ + static const struct { + const char name[8]; + void (*handler)(void); + } actions[] = { + {"record", macro_record}, + {"stop", macro_stop}, + {"toggle", macro_toggle}, + {"cancel", macro_cancel}, + {"play", macro_play}, + {"run", macro_play}, + }; + const char *action = a->args[0]; + for (size_t i = 0; i < ARRAY_COUNT(actions); i++) { + if (streq(action, actions[i].name)) { + actions[i].handler(); + return; + } + } + error_msg("Unknown action '%s'", action); +} + +static void cmd_match_bracket(const CommandArgs* UNUSED_ARG(a)) +{ + CodePoint cursor_char; + if (!block_iter_get_char(&view->cursor, &cursor_char)) { + error_msg("No character under cursor"); + return; + } + + CodePoint target = cursor_char; + BlockIter bi = view->cursor; + size_t level = 0; + CodePoint u = 0; + + switch (cursor_char) { + case '<': + case '[': + case '{': + target++; + // Fallthrough + case '(': + target++; + goto search_fwd; + case '>': + case ']': + case '}': + target--; + // Fallthrough + case ')': + target--; + goto search_bwd; + default: + error_msg("Character under cursor not matchable"); + return; + } + +search_fwd: + block_iter_next_char(&bi, &u); + BUG_ON(u != cursor_char); + while (block_iter_next_char(&bi, &u)) { + if (u == target) { + if (level == 0) { + bi.offset--; + view->cursor = bi; + return; // Found + } + level--; + } else if (u == cursor_char) { + level++; + } + } + goto not_found; + +search_bwd: + while (block_iter_prev_char(&bi, &u)) { + if (u == target) { + if (level == 0) { + view->cursor = bi; + return; // Found + } + level--; + } else if (u == cursor_char) { + level++; + } + } + +not_found: + error_msg("No matching bracket found"); +} + +static void cmd_move_tab(const CommandArgs *a) +{ + const size_t ntabs = window->views.count; + const char *str = a->args[0]; + size_t to, from = ptr_array_idx(&window->views, view); + BUG_ON(from >= ntabs); + if (streq(str, "left")) { + to = (from ? from : ntabs) - 1; + } else if (streq(str, "right")) { + to = (from + 1) % ntabs; + } else { + if (!str_to_size(str, &to) || to == 0) { + error_msg("Invalid tab position %s", str); + return; + } + to--; + if (to >= ntabs) { + to = ntabs - 1; + } + } + ptr_array_move(&window->views, from, to); + window->update_tabbar = true; +} + +static void cmd_msg(const CommandArgs *a) +{ + switch (last_flag(a)) { + case 'n': + activate_next_message(); + return; + case 'p': + activate_prev_message(); + return; + } + activate_current_message(); +} + +static void cmd_new_line(const CommandArgs* UNUSED_ARG(a)) +{ + new_line(); +} + +static void cmd_next(const CommandArgs* UNUSED_ARG(a)) +{ + size_t i = ptr_array_idx(&window->views, view); + size_t n = window->views.count; + BUG_ON(i >= n); + set_view(window->views.ptrs[(i + 1) % n]); +} + +static bool xglob(char **args, glob_t *globbuf) +{ + BUG_ON(!args); + BUG_ON(!args[0]); + int err = glob(*args, GLOB_NOCHECK, NULL, globbuf); + while (err == 0 && *++args) { + err = glob(*args, GLOB_NOCHECK | GLOB_APPEND, NULL, globbuf); + } + + if (likely(err == 0)) { + BUG_ON(globbuf->gl_pathc == 0); + BUG_ON(!globbuf->gl_pathv); + BUG_ON(!globbuf->gl_pathv[0]); + return true; + } + + BUG_ON(err == GLOB_NOMATCH); + error_msg("glob: %s", (err == GLOB_NOSPACE) ? strerror(ENOMEM) : "failed"); + globfree(globbuf); + return false; +} + +static void cmd_open(const CommandArgs *a) +{ + bool temporary = has_flag(a, 't'); + if (unlikely(temporary && a->nr_args > 0)) { + error_msg("'open -t' can't be used with filename arguments"); + return; + } + + const char *requested_encoding = NULL; + char **args = a->args; + if (a->nr_flag_args > 0) { + // The "-e" flag is the only one that takes an argument, so the + // above condition implies it was used + BUG_ON(!has_flag(a, 'e')); + requested_encoding = args[a->nr_flag_args - 1]; + args += a->nr_flag_args; + } + + Encoding encoding; + if (requested_encoding) { + EncodingType e = lookup_encoding(requested_encoding); + if (e == UTF8) { + encoding = encoding_from_type(e); + } else if (conversion_supported_by_iconv(requested_encoding, "UTF-8")) { + encoding = encoding_from_name(requested_encoding); + } else { + if (errno == EINVAL) { + error_msg("Unsupported encoding '%s'", requested_encoding); + } else { + error_msg ( + "iconv conversion from '%s' failed: %s", + requested_encoding, + strerror(errno) + ); + } + return; + } + } else { + encoding = (Encoding){.type = ENCODING_AUTODETECT}; + } + + if (a->nr_args == 0) { + View *v = window_open_new_file(window); + v->buffer->temporary = temporary; + if (requested_encoding) { + buffer_set_encoding(v->buffer, encoding); + } + return; + } + + char **paths = args; + glob_t globbuf; + bool use_glob = has_flag(a, 'g'); + if (use_glob) { + if (!xglob(args, &globbuf)) { + return; + } + paths = globbuf.gl_pathv; + } + + if (!paths[1]) { + // Previous view is remembered when opening single file + window_open_file(window, paths[0], &encoding); + } else { + // It makes no sense to remember previous view when opening + // multiple files + window_open_files(window, paths, &encoding); + } + + if (use_glob) { + globfree(&globbuf); + } +} + +static void cmd_option(const CommandArgs *a) +{ + BUG_ON(a->nr_args < 3); + size_t nstrs = a->nr_args - 1; + if (unlikely(nstrs % 2 != 0)) { + error_msg("Missing option value"); + return; + } + + char **strs = a->args + 1; + if (unlikely(!validate_local_options(strs))) { + return; + } + + if (has_flag(a, 'r')) { + char *regex = xstrdup(a->args[0]); + char **opts = copy_string_array(strs, nstrs); + add_file_options(FILE_OPTIONS_FILENAME, regex, opts); + return; + } + + const char *ft_list = a->args[0]; + for (size_t pos = 0, len = strlen(ft_list); pos < len; ) { + const StringView sv = get_delim(ft_list, &pos, len, ','); + char *filetype = xstrcut(sv.data, sv.length); + char **opts = copy_string_array(strs, nstrs); + add_file_options(FILE_OPTIONS_FILETYPE, filetype, opts); + } +} + +static void cmd_blkdown(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + + // If current line is blank, skip past consecutive blank lines + StringView line; + fetch_this_line(&view->cursor, &line); + if (strview_isblank(&line)) { + while (block_iter_next_line(&view->cursor)) { + fill_line_ref(&view->cursor, &line); + if (!strview_isblank(&line)) { + break; + } + } + } + + // Skip past non-blank lines + while (block_iter_next_line(&view->cursor)) { + fill_line_ref(&view->cursor, &line); + if (strview_isblank(&line)) { + break; + } + } + + // If we reach the last populated line in the buffer, move down one line + BlockIter tmp = view->cursor; + block_iter_eol(&tmp); + block_iter_skip_bytes(&tmp, 1); + if (block_iter_is_eof(&tmp)) { + view->cursor = tmp; + } +} + +static void cmd_blkup(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + + // If cursor is on the first line, just move to bol + if (view->cy == 0) { + block_iter_bol(&view->cursor); + return; + } + + // If current line is blank, skip past consecutive blank lines + StringView line; + fetch_this_line(&view->cursor, &line); + if (strview_isblank(&line)) { + while (block_iter_prev_line(&view->cursor)) { + fill_line_ref(&view->cursor, &line); + if (!strview_isblank(&line)) { + break; + } + } + } + + // Skip past non-blank lines + while (block_iter_prev_line(&view->cursor)) { + fill_line_ref(&view->cursor, &line); + if (strview_isblank(&line)) { + break; + } + } +} + +static void cmd_paste(const CommandArgs *a) +{ + bool at_cursor = has_flag(a, 'c'); + paste(at_cursor); +} + +static void cmd_pgdown(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + + long margin = window_get_scroll_margin(window); + long bottom = view->vy + window->edit_h - 1 - margin; + long count; + + if (view->cy < bottom) { + count = bottom - view->cy; + } else { + count = window->edit_h - 1 - margin * 2; + } + move_down(count); +} + +static void cmd_pgup(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + + long margin = window_get_scroll_margin(window); + long top = view->vy + margin; + long count; + + if (view->cy > top) { + count = view->cy - top; + } else { + count = window->edit_h - 1 - margin * 2; + } + move_up(count); +} + +static void cmd_pipe_from(const CommandArgs *a) +{ + SpawnContext ctx = { + .argv = a->args, + .env = lines_and_columns_env(), + .output = STRING_INIT, + .flags = SPAWN_QUIET + }; + if (!spawn_source(&ctx)) { + return; + } + + size_t del_len = 0; + if (view->selection) { + del_len = prepare_selection(view); + unselect(); + } + + bool strip_nl = has_flag(a, 's'); + bool move = has_flag(a, 'm'); + size_t ins_len = ctx.output.len; + if (strip_nl) { + if (ins_len && ctx.output.buffer[ins_len - 1] == '\n') { + ins_len--; + if (ins_len && ctx.output.buffer[ins_len - 1] == '\r') { + ins_len--; + } + } + } + + buffer_replace_bytes(del_len, ctx.output.buffer, ins_len); + string_free(&ctx.output); + + if (move) { + block_iter_skip_bytes(&view->cursor, ins_len); + } +} + +static void cmd_pipe_to(const CommandArgs *a) +{ + const BlockIter saved_cursor = view->cursor; + const ssize_t saved_sel_so = view->sel_so; + const ssize_t saved_sel_eo = view->sel_eo; + + size_t input_len = 0; + if (view->selection) { + input_len = prepare_selection(view); + } else if (has_flag(a, 'l')) { + StringView line; + move_bol(); + fill_line_ref(&view->cursor, &line); + input_len = line.length; + } else { + Block *blk; + block_for_each(blk, &buffer->blocks) { + input_len += blk->size; + } + move_bof(); + } + + char *input = block_iter_get_bytes(&view->cursor, input_len); + SpawnContext ctx = { + .argv = a->args, + .input = string_view(input, input_len), + .flags = SPAWN_DEFAULT + }; + spawn_sink(&ctx); + free(input); + + // Restore cursor and selection offsets, instead of calling unselect() + view->cursor = saved_cursor; + view->sel_so = saved_sel_so; + view->sel_eo = saved_sel_eo; +} + +static void cmd_prev(const CommandArgs* UNUSED_ARG(a)) +{ + size_t i = ptr_array_idx(&window->views, view); + size_t n = window->views.count; + BUG_ON(i >= n); + set_view(window->views.ptrs[(i ? i : n) - 1]); +} + +static void cmd_quit(const CommandArgs *a) +{ + int exit_code = 0; + if (a->nr_args) { + if (!str_to_int(a->args[0], &exit_code)) { + error_msg("Not a valid integer argument: '%s'", a->args[0]); + return; + } + if (exit_code < 0 || exit_code > 125) { + error_msg("Exit code should be between 0 and 125"); + return; + } + } + + if (has_flag(a, 'f')) { + goto exit; + } + + for (size_t i = 0, n = editor.buffers.count; i < n; i++) { + Buffer *b = editor.buffers.ptrs[i]; + if (buffer_modified(b)) { + // Activate modified buffer + View *v = window_find_view(window, b); + if (!v) { + // Buffer isn't open in current window. + // Activate first window of the buffer. + v = b->views.ptrs[0]; + window = v->window; + mark_everything_changed(); + } + set_view(v); + if (has_flag(a, 'p')) { + if (dialog_prompt("Quit without saving changes? [y/N]", "ny") == 'y') { + goto exit; + } + return; + } else { + error_msg ( + "Save modified files or run 'quit -f' to quit" + " without saving." + ); + return; + } + } + } + +exit: + editor.status = EDITOR_EXITING; + editor.exit_code = exit_code; +} + +static void cmd_redo(const CommandArgs *a) +{ + char *arg = a->args[0]; + unsigned long change_id = 0; + if (arg) { + if (!str_to_ulong(arg, &change_id) || change_id == 0) { + error_msg("Invalid change id: %s", arg); + return; + } + } + if (redo(change_id)) { + unselect(); + } +} + +static void cmd_refresh(const CommandArgs* UNUSED_ARG(a)) +{ + mark_everything_changed(); +} + +static void cmd_repeat(const CommandArgs *a) +{ + unsigned int count = 0; + if (!str_to_uint(a->args[0], &count)) { + error_msg("Not a valid repeat count: %s", a->args[0]); + return; + } else if (count == 0) { + return; + } + + const Command *cmd = find_normal_command(a->args[1]); + if (!cmd) { + error_msg("No such command: %s", a->args[1]); + return; + } + + CommandArgs a2 = {.args = a->args + 2}; + current_command = cmd; + bool ok = parse_args(cmd, &a2); + current_command = NULL; + if (!ok) { + return; + } + + // Optimized special case for "insert" command + if (cmd->cmd == cmd_insert && !has_flag(&a2, 'k')) { + const char *str = a2.args[0]; + size_t str_len = strlen(str); + size_t bufsize; + if (size_multiply_overflows(count, str_len, &bufsize)) { + error_msg("Repeated insert would overflow"); + return; + } + char *buf = malloc(bufsize); + if (!buf) { + perror_msg("malloc"); + return; + } + // TODO: for many repeats of small strings, fill in the first 4K + // and then use that to batch copy larger blocks (making sure to + // handle any unaligned remainder). + for (size_t i = 0; i < count; i++) { + memcpy(buf + (i * str_len), str, str_len); + } + bool move_after = has_flag(&a2, 'm'); + insert_text(buf, bufsize, move_after); + free(buf); + return; + } + + while (count--) { + cmd->cmd(&a2); + } +} + +static void cmd_replace(const CommandArgs *a) +{ + unsigned int flags = 0; + for (const char *pf = a->flags; *pf; pf++) { + switch (*pf) { + case 'b': + flags |= REPLACE_BASIC; + break; + case 'c': + flags |= REPLACE_CONFIRM; + break; + case 'g': + flags |= REPLACE_GLOBAL; + break; + case 'i': + flags |= REPLACE_IGNORE_CASE; + break; + } + } + reg_replace(a->args[0], a->args[1], flags); +} + +static void cmd_right(const CommandArgs *a) +{ + handle_select_chars_flag(a); + move_cursor_right(); +} + +static void cmd_run(const CommandArgs *a) +{ + SpawnContext ctx = { + .argv = a->args, + .flags = SPAWN_DEFAULT + }; + for (const char *pf = a->flags; *pf; pf++) { + switch (*pf) { + case 'p': + ctx.flags |= SPAWN_PROMPT; + break; + case 's': + ctx.flags |= SPAWN_QUIET; + break; + } + } + spawn(&ctx); +} + +static bool stat_changed(const Buffer *b, const struct stat *st) +{ + // Don't compare st_mode because we allow chmod 755 etc. + return st->st_mtime != b->file.mtime + || st->st_dev != b->file.dev + || st->st_ino != b->file.ino; +} + +static void cmd_save(const CommandArgs *a) +{ + if (unlikely(buffer->stdout_buffer)) { + const char *f = buffer_filename(buffer); + info_msg("%s can't be saved; it will be piped to stdout on exit", f); + return; + } + + bool dos_nl = has_flag(a, 'd'); + bool unix_nl = has_flag(a, 'u'); + bool crlf = buffer->crlf_newlines; + if (unlikely(dos_nl && unix_nl)) { + error_msg("flags -d and -u can't be used together"); + return; + } else if (dos_nl) { + crlf = true; + } else if (unix_nl) { + crlf = false; + } + + const char *requested_encoding = NULL; + char **args = a->args; + if (a->nr_flag_args > 0) { + BUG_ON(!has_flag(a, 'e')); + requested_encoding = args[a->nr_flag_args - 1]; + args += a->nr_flag_args; + } + + Encoding encoding = buffer->encoding; + bool bom = buffer->bom; + if (requested_encoding) { + EncodingType e = lookup_encoding(requested_encoding); + if (e == UTF8) { + if (encoding.type != UTF8) { + // Encoding changed + encoding = encoding_from_type(e); + bom = editor.options.utf8_bom; + } + } else if (conversion_supported_by_iconv("UTF-8", requested_encoding)) { + encoding = encoding_from_name(requested_encoding); + if (encoding.name != buffer->encoding.name) { + // Encoding changed + bom = !!get_bom_for_encoding(encoding.type); + } + } else { + if (errno == EINVAL) { + error_msg("Unsupported encoding '%s'", requested_encoding); + } else { + error_msg ( + "iconv conversion to '%s' failed: %s", + requested_encoding, + strerror(errno) + ); + } + return; + } + } + + bool b = has_flag(a, 'b'); + bool B = has_flag(a, 'B'); + if (unlikely(b && B)) { + error_msg("flags -b and -B can't be used together"); + return; + } else if (b) { + bom = true; + } else if (B) { + bom = false; + } + + char *absolute = buffer->abs_filename; + bool force = has_flag(a, 'f'); + bool new_locked = false; + if (a->nr_args > 0) { + if (args[0][0] == '\0') { + error_msg("Empty filename not allowed"); + goto error; + } + char *tmp = path_absolute(args[0]); + if (!tmp) { + error_msg("Failed to make absolute path: %s", strerror(errno)); + goto error; + } + if (absolute && streq(tmp, absolute)) { + free(tmp); + } else { + absolute = tmp; + } + } else { + if (!absolute) { + bool prompt = has_flag(a, 'p'); + if (prompt) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, "save "); + // This branch is not really an error, but we still return via + // the "error" label because we need to clean up memory and + // that's all it's used for currently. + goto error; + } else { + error_msg("No filename."); + goto error; + } + } + if (buffer->readonly && !force) { + error_msg("Use -f to force saving read-only file."); + goto error; + } + } + + mode_t old_mode = buffer->file.mode; + struct stat st; + if (stat(absolute, &st)) { + if (errno != ENOENT) { + error_msg("stat failed for %s: %s", absolute, strerror(errno)); + goto error; + } + if (editor.options.lock_files) { + if (absolute == buffer->abs_filename) { + if (!buffer->locked) { + if (!lock_file(absolute)) { + if (!force) { + error_msg("Can't lock file %s", absolute); + goto error; + } + } else { + buffer->locked = true; + } + } + } else { + if (!lock_file(absolute)) { + if (!force) { + error_msg("Can't lock file %s", absolute); + goto error; + } + } else { + new_locked = true; + } + } + } + } else { + if ( + absolute == buffer->abs_filename + && !force + && stat_changed(buffer, &st) + ) { + error_msg ( + "File has been modified by another process." + " Use 'save -f' to force overwrite." + ); + goto error; + } + if (S_ISDIR(st.st_mode)) { + error_msg("Will not overwrite directory %s", absolute); + goto error; + } + if (editor.options.lock_files) { + if (absolute == buffer->abs_filename) { + if (!buffer->locked) { + if (!lock_file(absolute)) { + if (!force) { + error_msg("Can't lock file %s", absolute); + goto error; + } + } else { + buffer->locked = true; + } + } + } else { + if (!lock_file(absolute)) { + if (!force) { + error_msg("Can't lock file %s", absolute); + goto error; + } + } else { + new_locked = true; + } + } + } + if (absolute != buffer->abs_filename && !force) { + error_msg("Use -f to overwrite %s.", absolute); + goto error; + } + + // Allow chmod 755 etc. + buffer->file.mode = st.st_mode; + } + + if (!save_buffer(buffer, absolute, &encoding, crlf, bom)) { + goto error; + } + + buffer->saved_change = buffer->cur_change; + buffer->readonly = false; + buffer->temporary = false; + buffer->crlf_newlines = crlf; + buffer->bom = bom; + if (requested_encoding) { + buffer->encoding = encoding; + } + + if (absolute != buffer->abs_filename) { + if (buffer->locked) { + // Filename changes, release old file lock + unlock_file(buffer->abs_filename); + } + buffer->locked = new_locked; + + free(buffer->abs_filename); + buffer->abs_filename = absolute; + update_short_filename(buffer); + + // Filename change is not detected (only buffer_modified() change) + mark_buffer_tabbars_changed(buffer); + } + if (!old_mode && streq(buffer->options.filetype, "none")) { + // New file and most likely user has not changed the filetype + if (buffer_detect_filetype(buffer)) { + set_file_options(buffer); + set_editorconfig_options(buffer); + buffer_update_syntax(buffer); + } + } + return; +error: + if (new_locked) { + unlock_file(absolute); + } + if (absolute != buffer->abs_filename) { + free(absolute); + } +} + +static void cmd_scroll_down(const CommandArgs* UNUSED_ARG(a)) +{ + view->vy++; + if (view->cy < view->vy) { + move_down(1); + } +} + +static void cmd_scroll_pgdown(const CommandArgs* UNUSED_ARG(a)) +{ + long max = buffer->nl - window->edit_h + 1; + if (view->vy < max && max > 0) { + long count = window->edit_h - 1; + if (view->vy + count > max) { + count = max - view->vy; + } + view->vy += count; + move_down(count); + } else if (view->cy < buffer->nl) { + move_down(buffer->nl - view->cy); + } +} + +static void cmd_scroll_pgup(const CommandArgs* UNUSED_ARG(a)) +{ + if (view->vy > 0) { + long count = window->edit_h - 1; + if (count > view->vy) { + count = view->vy; + } + view->vy -= count; + move_up(count); + } else if (view->cy > 0) { + move_up(view->cy); + } +} + +static void cmd_scroll_up(const CommandArgs* UNUSED_ARG(a)) +{ + if (view->vy) { + view->vy--; + } + if (view->vy + window->edit_h <= view->cy) { + move_up(1); + } +} + +static void cmd_search(const CommandArgs *a) +{ + char *pattern = a->args[0]; + bool history = !has_flag(a, 'H'); + bool next = has_flag(a, 'n'); + bool prev = has_flag(a, 'p'); + bool w = has_flag(a, 'w'); + SearchDirection dir = has_flag(a, 'r') ? SEARCH_BWD : SEARCH_FWD; + + if (unlikely(w && pattern)) { + error_msg("flag -w can't be used with search pattern."); + return; + } + if (unlikely(next && prev)) { + error_msg("flags -n and -p can't be used together"); + return; + } + + char pattbuf[4096]; + if (w) { + StringView word = view_get_word_under_cursor(view); + if (word.length == 0) { + // Error message would not be very useful here + return; + } + const size_t bmax = sizeof(regexp_word_boundary.start); + static_assert_compatible_types(regexp_word_boundary.start, char[8]); + if (unlikely(word.length >= sizeof(pattbuf) - (bmax * 2))) { + error_msg("word under cursor too long"); + return; + } + char *ptr = stpncpy(pattbuf, regexp_word_boundary.start, bmax); + memcpy(ptr, word.data, word.length); + memcpy(ptr + word.length, regexp_word_boundary.end, bmax); + pattern = pattbuf; + } + + do_selection(SELECT_NONE); + + if (pattern) { + set_search_direction(dir); + search_set_regexp(pattern); + if (w) { + search_next_word(); + } else { + search_next(); + } + if (history) { + history_add(&editor.search_history, pattern); + } + } else if (next) { + search_next(); + } else if (prev) { + search_prev(); + } else { + set_input_mode(INPUT_SEARCH); + set_search_direction(dir); + } +} + +static void cmd_select(const CommandArgs *a) +{ + SelectionType sel = has_flag(a, 'l') ? SELECT_LINES : SELECT_CHARS; + bool block = has_flag(a, 'b'); + bool keep = has_flag(a, 'k'); + view->next_movement_cancels_selection = false; + + if (block) { + select_block(); + return; + } + + if (view->selection) { + if (!keep && view->selection == sel) { + unselect(); + return; + } + view->selection = sel; + mark_all_lines_changed(buffer); + return; + } + + view->sel_so = block_iter_get_offset(&view->cursor); + view->sel_eo = UINT_MAX; + view->selection = sel; + + // Need to mark current line changed because cursor might + // move up or down before screen is updated + view_update_cursor_y(view); + buffer_mark_lines_changed(buffer, view->cy, view->cy); +} + +static void cmd_set(const CommandArgs *a) +{ + bool global = has_flag(a, 'g'); + bool local = has_flag(a, 'l'); + if (!buffer) { + if (local) { + error_msg("Flag -l makes no sense in config file."); + return; + } + global = true; + } + + char **args = a->args; + size_t count = a->nr_args; + if (count == 1) { + set_bool_option(args[0], local, global); + return; + } else if (count % 2 != 0) { + error_msg("One or even number of arguments expected."); + return; + } + + for (size_t i = 0; i < count; i += 2) { + set_option(args[i], args[i + 1], local, global); + } +} + +static void cmd_setenv(const CommandArgs *a) +{ + const char *name = a->args[0]; + if (unlikely(streq(name, "DTE_VERSION"))) { + error_msg("$DTE_VERSION cannot be changed"); + return; + } + + const size_t nr_args = a->nr_args; + int res; + if (nr_args == 2) { + res = setenv(name, a->args[1], true); + } else { + BUG_ON(nr_args != 1); + res = unsetenv(name); + } + + if (unlikely(res != 0)) { + if (errno == EINVAL) { + error_msg("Invalid environment variable name '%s'", name); + } else { + perror_msg(nr_args == 2 ? "setenv" : "unsetenv"); + } + } +} + +static void cmd_shift(const CommandArgs *a) +{ + const char *arg = a->args[0]; + int count; + if (!str_to_int(arg, &count)) { + error_msg("Invalid number: %s", arg); + return; + } + if (count == 0) { + error_msg("Count must be non-zero."); + return; + } + shift_lines(count); +} + +static void cmd_show(const CommandArgs *a) +{ + BUG_ON(a->nr_args == 1 && a->args[1]); + bool cflag = a->nr_flags != 0; + if (cflag && a->nr_args < 2) { + error_msg("\"show -c\" requires 2 arguments"); + return; + } + show(a->args[0], a->args[1], cflag); +} + +static void cmd_suspend(const CommandArgs* UNUSED_ARG(a)) +{ + suspend(); +} + +static void cmd_tag(const CommandArgs *a) +{ + if (has_flag(a, 'r')) { + pop_file_location(); + return; + } + + clear_messages(); + TagFile *tf = load_tag_file(); + if (!tf) { + error_msg("No tag file."); + return; + } + + const char *name = a->args[0]; + char *word = NULL; + if (!name) { + StringView w = view_get_word_under_cursor(view); + if (w.length == 0) { + return; + } + word = xstrcut(w.data, w.length); + name = word; + } + + PointerArray tags = PTR_ARRAY_INIT; + // Filename helps to find correct tags + tag_file_find_tags(tf, buffer->abs_filename, name, &tags); + if (tags.count == 0) { + error_msg("Tag '%s' not found", name); + } else { + for (size_t i = 0, n = tags.count; i < n; i++) { + Tag *t = tags.ptrs[i]; + char buf[512]; + size_t len = xsnprintf(buf, sizeof(buf), "Tag %s", name); + Message *m = new_message(buf, len); + m->loc = xnew0(FileLocation, 1); + m->loc->filename = tag_file_get_tag_filename(tf, t); + if (t->pattern) { + m->loc->pattern = t->pattern; + t->pattern = NULL; + } else { + m->loc->line = t->line; + } + add_message(m); + } + free_tags(&tags); + activate_current_message_save(); + } + free(word); +} + +static void cmd_title(const CommandArgs *a) +{ + if (buffer->abs_filename) { + error_msg("saved buffers can't be retitled"); + return; + } + set_display_filename(buffer, xstrdup(a->args[0])); + mark_buffer_tabbars_changed(buffer); +} + +static void cmd_toggle(const CommandArgs *a) +{ + bool global = has_flag(a, 'g'); + bool verbose = has_flag(a, 'v'); + const char *option_name = a->args[0]; + size_t nr_values = a->nr_args - 1; + if (nr_values) { + char **values = a->args + 1; + toggle_option_values(option_name, global, verbose, values, nr_values); + } else { + toggle_option(option_name, global, verbose); + } +} + +static void cmd_undo(const CommandArgs* UNUSED_ARG(a)) +{ + if (undo()) { + unselect(); + } +} + +static void cmd_unselect(const CommandArgs* UNUSED_ARG(a)) +{ + unselect(); +} + +static void cmd_up(const CommandArgs *a) +{ + handle_select_chars_or_lines_flags(a); + move_up(1); +} + +static void cmd_view(const CommandArgs *a) +{ + BUG_ON(window->views.count == 0); + const char *arg = a->args[0]; + size_t idx; + if (streq(arg, "last")) { + idx = window->views.count - 1; + } else { + if (!str_to_size(arg, &idx) || idx == 0) { + error_msg("Invalid view index: %s", arg); + return; + } + idx--; + if (idx > window->views.count - 1) { + idx = window->views.count - 1; + } + } + set_view(window->views.ptrs[idx]); +} + +static void cmd_wclose(const CommandArgs *a) +{ + bool force = has_flag(a, 'f'); + bool prompt = has_flag(a, 'p'); + View *v = window_find_unclosable_view(window); + if (v && !force) { + set_view(v); + if (prompt) { + if (dialog_prompt("Close window without saving? [y/N]", "ny") != 'y') { + return; + } + } else { + error_msg ( + "Save modified files or run 'wclose -f' to close " + "window without saving." + ); + return; + } + } + window_close_current(); +} + +static void cmd_wflip(const CommandArgs* UNUSED_ARG(a)) +{ + Frame *f = window->frame; + if (!f->parent) { + return; + } + f->parent->vertical ^= 1; + mark_everything_changed(); +} + +static void cmd_wnext(const CommandArgs* UNUSED_ARG(a)) +{ + window = next_window(window); + set_view(window->view); + mark_everything_changed(); + debug_frames(); +} + +static void cmd_word_bwd(const CommandArgs *a) +{ + handle_select_chars_flag(a); + bool skip_non_word = has_flag(a, 's'); + word_bwd(&view->cursor, skip_non_word); + view_reset_preferred_x(view); +} + +static void cmd_word_fwd(const CommandArgs *a) +{ + handle_select_chars_flag(a); + bool skip_non_word = has_flag(a, 's'); + word_fwd(&view->cursor, skip_non_word); + view_reset_preferred_x(view); +} + +static void cmd_wprev(const CommandArgs* UNUSED_ARG(a)) +{ + window = prev_window(window); + set_view(window->view); + mark_everything_changed(); + debug_frames(); +} + +static void cmd_wrap_paragraph(const CommandArgs *a) +{ + const char *arg = a->args[0]; + size_t width = (size_t)buffer->options.text_width; + if (arg) { + if (!str_to_size(arg, &width) || width == 0 || width > 1000) { + error_msg("Invalid paragraph width: %s", arg); + return; + } + } + format_paragraph(width); +} + +static void cmd_wresize(const CommandArgs *a) +{ + if (!window->frame->parent) { + // Only window + return; + } + + ResizeDirection dir = RESIZE_DIRECTION_AUTO; + if (a->nr_flags) { + switch (a->flags[a->nr_flags - 1]) { + case 'h': + dir = RESIZE_DIRECTION_HORIZONTAL; + break; + case 'v': + dir = RESIZE_DIRECTION_VERTICAL; + break; + } + } + + const char *arg = a->args[0]; + if (arg) { + int n; + if (!str_to_int(arg, &n)) { + error_msg("Invalid resize value: %s", arg); + return; + } + if (arg[0] == '+' || arg[0] == '-') { + add_to_frame_size(window->frame, dir, n); + } else { + resize_frame(window->frame, dir, n); + } + } else { + equalize_frame_sizes(window->frame->parent); + } + mark_everything_changed(); + debug_frames(); +} + +static void cmd_wsplit(const CommandArgs *a) +{ + bool before = has_flag(a, 'b'); + bool use_glob = has_flag(a, 'g') && a->nr_args > 0; + bool vertical = has_flag(a, 'h'); + bool root = has_flag(a, 'r'); + bool temporary = has_flag(a, 't'); + bool empty = temporary || has_flag(a, 'n'); + + if (empty && a->nr_args > 0) { + error_msg("flags -n and -t can't be used with filename arguments"); + return; + } + + char **paths = a->args; + glob_t globbuf; + if (use_glob) { + if (!xglob(a->args, &globbuf)) { + return; + } + paths = globbuf.gl_pathv; + } + + Frame *f; + if (root) { + f = split_root(vertical, before); + } else { + f = split_frame(window, vertical, before); + } + + View *save = view; + window = f->window; + view = NULL; + buffer = NULL; + mark_everything_changed(); + + if (empty) { + window_open_new_file(window); + buffer->temporary = temporary; + } else if (paths[0]) { + window_open_files(window, paths, NULL); + } else { + View *new = window_add_buffer(window, save->buffer); + new->cursor = save->cursor; + set_view(new); + } + + if (use_glob) { + globfree(&globbuf); + } + + if (window->views.count == 0) { + // Open failed, remove new window + remove_frame(window->frame); + view = save; + buffer = view->buffer; + window = view->window; + } + + debug_frames(); +} + +static void cmd_wswap(const CommandArgs* UNUSED_ARG(a)) +{ + Frame *parent = window->frame->parent; + if (!parent) { + return; + } + + size_t i = ptr_array_idx(&parent->frames, window->frame); + BUG_ON(i >= parent->frames.count); + size_t j = i + 1; + if (j == parent->frames.count) { + j = 0; + } + + Frame *tmp = parent->frames.ptrs[i]; + parent->frames.ptrs[i] = parent->frames.ptrs[j]; + parent->frames.ptrs[j] = tmp; + mark_everything_changed(); +} + +static const Command cmds[] = { + {"alias", "-", true, 2, 2, cmd_alias}, + {"bind", "-", true, 1, 2, cmd_bind}, + {"blkdown", "cl", false, 0, 0, cmd_blkdown}, + {"blkup", "cl", false, 0, 0, cmd_blkup}, + {"bof", "", false, 0, 0, cmd_bof}, + {"bol", "cs", false, 0, 0, cmd_bol}, + {"bolsf", "", false, 0, 0, cmd_bolsf}, + {"bookmark", "r", false, 0, 0, cmd_bookmark}, + {"case", "lu", false, 0, 0, cmd_case}, + {"cd", "", true, 1, 1, cmd_cd}, + {"center-view", "", false, 0, 0, cmd_center_view}, + {"clear", "", false, 0, 0, cmd_clear}, + {"close", "fpqw", false, 0, 0, cmd_close}, + {"command", "-", false, 0, 1, cmd_command}, + {"compile", "-1ps", false, 2, -1, cmd_compile}, + {"copy", "k", false, 0, 0, cmd_copy}, + {"cut", "", false, 0, 0, cmd_cut}, + {"delete", "", false, 0, 0, cmd_delete}, + {"delete-eol", "n", false, 0, 0, cmd_delete_eol}, + {"delete-line", "", false, 0, 0, cmd_delete_line}, + {"delete-word", "s", false, 0, 0, cmd_delete_word}, + {"down", "cl", false, 0, 0, cmd_down}, + {"eof", "", false, 0, 0, cmd_eof}, + {"eol", "c", false, 0, 0, cmd_eol}, + {"eolsf", "", false, 0, 0, cmd_eolsf}, + {"erase", "", false, 0, 0, cmd_erase}, + {"erase-bol", "", false, 0, 0, cmd_erase_bol}, + {"erase-word", "s", false, 0, 0, cmd_erase_word}, + {"errorfmt", "i", true, 2, 18, cmd_errorfmt}, + {"eval", "-", false, 1, -1, cmd_eval}, + {"exec-open", "-s", false, 1, -1, cmd_exec_open}, + {"exec-tag", "-s", false, 1, -1, cmd_exec_tag}, + {"filter", "-l", false, 1, -1, cmd_filter}, + {"ft", "-bcfi", true, 2, -1, cmd_ft}, + {"hi", "-c", true, 0, -1, cmd_hi}, + {"include", "bq", true, 1, 1, cmd_include}, + {"insert", "km", false, 1, 1, cmd_insert}, + {"join", "", false, 0, 0, cmd_join}, + {"left", "c", false, 0, 0, cmd_left}, + {"line", "", false, 1, 1, cmd_line}, + {"load-syntax", "", true, 1, 1, cmd_load_syntax}, + {"macro", "", false, 1, 1, cmd_macro}, + {"match-bracket", "", false, 0, 0, cmd_match_bracket}, + {"move-tab", "", false, 1, 1, cmd_move_tab}, + {"msg", "np", false, 0, 0, cmd_msg}, + {"new-line", "", false, 0, 0, cmd_new_line}, + {"next", "", false, 0, 0, cmd_next}, + {"open", "e=gt", false, 0, -1, cmd_open}, + {"option", "-r", true, 3, -1, cmd_option}, + {"paste", "c", false, 0, 0, cmd_paste}, + {"pgdown", "cl", false, 0, 0, cmd_pgdown}, + {"pgup", "cl", false, 0, 0, cmd_pgup}, + {"pipe-from", "-ms", false, 1, -1, cmd_pipe_from}, + {"pipe-to", "-l", false, 1, -1, cmd_pipe_to}, + {"prev", "", false, 0, 0, cmd_prev}, + {"quit", "fp", false, 0, 1, cmd_quit}, + {"redo", "", false, 0, 1, cmd_redo}, + {"refresh", "", false, 0, 0, cmd_refresh}, + {"repeat", "-", false, 2, -1, cmd_repeat}, + {"replace", "bcgi", false, 2, 2, cmd_replace}, + {"right", "c", false, 0, 0, cmd_right}, + {"run", "-ps", false, 1, -1, cmd_run}, + {"save", "Bbde=fpu", false, 0, 1, cmd_save}, + {"scroll-down", "", false, 0, 0, cmd_scroll_down}, + {"scroll-pgdown", "", false, 0, 0, cmd_scroll_pgdown}, + {"scroll-pgup", "", false, 0, 0, cmd_scroll_pgup}, + {"scroll-up", "", false, 0, 0, cmd_scroll_up}, + {"search", "Hnprw", false, 0, 1, cmd_search}, + {"select", "bkl", false, 0, 0, cmd_select}, + {"set", "gl", true, 1, -1, cmd_set}, + {"setenv", "", true, 1, 2, cmd_setenv}, + {"shift", "", false, 1, 1, cmd_shift}, + {"show", "c", false, 1, 2, cmd_show}, + {"suspend", "", false, 0, 0, cmd_suspend}, + {"tag", "r", false, 0, 1, cmd_tag}, + {"title", "", false, 1, 1, cmd_title}, + {"toggle", "glv", false, 1, -1, cmd_toggle}, + {"undo", "", false, 0, 0, cmd_undo}, + {"unselect", "", false, 0, 0, cmd_unselect}, + {"up", "cl", false, 0, 0, cmd_up}, + {"view", "", false, 1, 1, cmd_view}, + {"wclose", "fp", false, 0, 0, cmd_wclose}, + {"wflip", "", false, 0, 0, cmd_wflip}, + {"wnext", "", false, 0, 0, cmd_wnext}, + {"word-bwd", "cs", false, 0, 0, cmd_word_bwd}, + {"word-fwd", "cs", false, 0, 0, cmd_word_fwd}, + {"wprev", "", false, 0, 0, cmd_wprev}, + {"wrap-paragraph", "", false, 0, 1, cmd_wrap_paragraph}, + {"wresize", "hv", false, 0, 1, cmd_wresize}, + {"wsplit", "bghnrt", false, 0, -1, cmd_wsplit}, + {"wswap", "", false, 0, 0, cmd_wswap}, +}; + +static bool allow_macro_recording(const Command *cmd, char **args) +{ + IGNORE_WARNING("-Wpedantic") + static const void *const non_recordable[] = { + cmd_macro, cmd_command, cmd_exec_open, cmd_exec_tag, + }; + for (size_t i = 0; i < ARRAY_COUNT(non_recordable); i++) { + if (cmd->cmd == non_recordable[i]) { + return false; + } + } + UNIGNORE_WARNINGS + + if (cmd->cmd == cmd_search) { + char **args_copy = copy_string_array(args, string_array_length(args)); + CommandArgs a = {.args = args_copy}; + bool ret = true; + if (do_parse_args(cmd, &a) == 0) { + if (a.nr_args == 0 && !strpbrk(a.flags, "npw")) { + // If command is "search" with no pattern argument and without + // flags -n, -p or -w, the command would put the editor into + // search mode, which shouldn't be recorded. + ret = false; + } + } + free_string_array(args_copy); + return ret; + } + return true; +} + +const Command *find_normal_command(const char *name) +{ + return BSEARCH(name, cmds, command_cmp); +} + +const CommandSet commands = { + .lookup = find_normal_command, + .allow_recording = allow_macro_recording, +}; + +void collect_normal_commands(const char *prefix) +{ + for (size_t i = 0, n = ARRAY_COUNT(cmds); i < n; i++) { + const Command *c = &cmds[i]; + if (str_has_prefix(c->name, prefix)) { + add_completion(xstrdup(c->name)); + } + } +} + +UNITTEST { + CHECK_BSEARCH_ARRAY(cmds, name, strcmp); + + for (size_t i = 0, n = ARRAY_COUNT(cmds); i < n; i++) { + // Check that flags arrays is null-terminated within bounds + const char *const flags = cmds[i].flags; + BUG_ON(flags[ARRAY_COUNT(cmds[0].flags) - 1] != '\0'); + + // Count number of real flags (i.e. not including '-' or '=') + size_t nr_real_flags = 0; + for (size_t j = (flags[0] == '-' ? 1 : 0); flags[j]; j++) { + unsigned char flag = flags[j]; + if (ascii_isalnum(flag)) { + nr_real_flags++; + } else if (flag != '=') { + BUG("invalid command flag: 0x%02hhX", flag); + } + } + + // Check that max. number of real flags fits in CommandArgs::flags + // array (and also leaves 1 byte for null-terminator) + CommandArgs a; + BUG_ON(nr_real_flags >= ARRAY_COUNT(a.flags)); + } +} diff -Nru dte-1.9.1/src/commands.h dte-1.10/src/commands.h --- dte-1.9.1/src/commands.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/commands.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,12 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +#include "command/run.h" +#include "util/macros.h" + +extern const CommandSet commands; + +const Command *find_normal_command(const char *name) NONNULL_ARGS; +void collect_normal_commands(const char *prefix) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/compiler.c dte-1.10/src/compiler.c --- dte-1.9.1/src/compiler.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/compiler.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,34 +1,30 @@ +#include #include "compiler.h" +#include "command/serialize.h" +#include "completion.h" #include "error.h" #include "regexp.h" +#include "util/hashmap.h" +#include "util/hashset.h" #include "util/str-util.h" #include "util/xmalloc.h" -static PointerArray compilers = PTR_ARRAY_INIT; +static HashMap compilers = HASHMAP_INIT; static Compiler *add_compiler(const char *name) { Compiler *c = find_compiler(name); - if (c) { return c; } - c = xnew0(Compiler, 1); - c->name = xstrdup(name); - ptr_array_add(&compilers, c); + hashmap_insert(&compilers, xstrdup(name), c); return c; } Compiler *find_compiler(const char *name) { - for (size_t i = 0; i < compilers.count; i++) { - Compiler *c = compilers.ptrs[i]; - if (streq(c->name, name)) { - return c; - } - } - return NULL; + return hashmap_get(&compilers, name); } void add_error_fmt ( @@ -39,7 +35,6 @@ ) { static const char names[][8] = {"file", "line", "column", "message"}; int idx[ARRAY_COUNT(names)] = {-1, -1, -1, 0}; - ErrorFormat *f; for (size_t i = 0, j = 0; desc[i]; i++) { for (j = 0; j < ARRAY_COUNT(names); j++) { @@ -51,13 +46,13 @@ if (streq(desc[i], "_")) { continue; } - if (j == ARRAY_COUNT(names)) { + if (unlikely(j == ARRAY_COUNT(names))) { error_msg("Unknown substring name %s.", desc[i]); return; } } - f = xnew0(ErrorFormat, 1); + ErrorFormat *f = xnew(ErrorFormat, 1); f->ignore = ignore; f->msg_idx = idx[3]; f->file_idx = idx[0]; @@ -68,9 +63,10 @@ free(f); return; } + for (size_t i = 0; i < ARRAY_COUNT(idx); i++) { // NOTE: -1 is larger than 0UL - if (idx[i] > (int)f->re.re_nsub) { + if (unlikely(idx[i] > (int)f->re.re_nsub)) { error_msg("Invalid substring count."); regfree(&f->re); free(f); @@ -78,5 +74,67 @@ } } - ptr_array_add(&add_compiler(compiler)->error_formats, f); + f->pattern = str_intern(format); + ptr_array_append(&add_compiler(compiler)->error_formats, f); +} + +void collect_compilers(const char *prefix) +{ + collect_hashmap_keys(&compilers, prefix); +} + +static void append_compiler(String *s, const Compiler *c, const char *name) +{ + for (size_t i = 0, n = c->error_formats.count; i < n; i++) { + ErrorFormat *e = c->error_formats.ptrs[i]; + string_append_literal(s, "errorfmt "); + if (e->ignore) { + string_append_literal(s, "-i "); + } + if (unlikely(name[0] == '-' || e->pattern[0] == '-')) { + string_append_literal(s, "-- "); + } + string_append_escaped_arg(s, name, true); + string_append_byte(s, ' '); + string_append_escaped_arg(s, e->pattern, true); + + int max_idx = MAX4(e->file_idx, e->line_idx, e->column_idx, e->msg_idx); + BUG_ON(max_idx > 16); + + for (int j = 1; j <= max_idx; j++) { + const char *idx_type = "_"; + if (j == e->file_idx) { + idx_type = "file"; + } else if (j == e->line_idx) { + idx_type = "line"; + } else if (j == e->column_idx) { + idx_type = "column"; + } else if (j == e->msg_idx) { + idx_type = "message"; + } + string_append_byte(s, ' '); + string_append_cstring(s, idx_type); + } + + string_append_byte(s, '\n'); + } +} + +String dump_compiler(const Compiler *c, const char *name) +{ + String buf = string_new(512); + append_compiler(&buf, c, name); + return buf; +} + +String dump_compilers(void) +{ + String buf = string_new(4096); + for (HashMapIter it = hashmap_iter(&compilers); hashmap_next(&it); ) { + const char *name = it.entry->key; + const Compiler *c = it.entry->value; + append_compiler(&buf, c, name); + string_append_byte(&buf, '\n'); + } + return buf; } diff -Nru dte-1.9.1/src/compiler.h dte-1.10/src/compiler.h --- dte-1.9.1/src/compiler.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/compiler.h 2021-04-03 21:08:53.000000000 +0000 @@ -4,7 +4,9 @@ #include #include #include +#include "util/macros.h" #include "util/ptr-array.h" +#include "util/string.h" typedef struct { bool ignore; @@ -12,15 +14,18 @@ int8_t file_idx; int8_t line_idx; int8_t column_idx; - regex_t re; + const char *pattern; // Original pattern string (interned) + regex_t re; // Compiled pattern } ErrorFormat; typedef struct { - char *name; PointerArray error_formats; } Compiler; -void add_error_fmt(const char *compiler, bool ignore, const char *format, char **desc); -Compiler *find_compiler(const char *name); +void add_error_fmt(const char *compiler, bool ignore, const char *format, char **desc) NONNULL_ARGS; +Compiler *find_compiler(const char *name) NONNULL_ARGS; +void collect_compilers(const char *prefix) NONNULL_ARGS; +String dump_compiler(const Compiler *c, const char *name) NONNULL_ARGS; +String dump_compilers(void); #endif diff -Nru dte-1.9.1/src/completion.c dte-1.10/src/completion.c --- dte-1.9.1/src/completion.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/completion.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,16 +2,23 @@ #include #include "completion.h" #include "alias.h" +#include "bind.h" #include "cmdline.h" -#include "command.h" -#include "debug.h" +#include "command/args.h" +#include "command/env.h" +#include "command/parse.h" +#include "command/serialize.h" +#include "commands.h" +#include "compiler.h" #include "config.h" #include "editor.h" -#include "env.h" +#include "filetype.h" #include "options.h" +#include "show.h" #include "syntax/color.h" #include "tag.h" #include "util/ascii.h" +#include "util/debug.h" #include "util/path.h" #include "util/ptr-array.h" #include "util/str-util.h" @@ -48,18 +55,17 @@ void add_completion(char *str) { - ptr_array_add(&completion.completions, str); + ptr_array_append(&completion.completions, str); } -static void collect_commands(const char *prefix) +void collect_hashmap_keys(const HashMap *map, const char *prefix) { - for (size_t i = 0; commands[i].cmd; i++) { - const Command *c = &commands[i]; - if (str_has_prefix(c->name, prefix)) { - add_completion(xstrdup(c->name)); + for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); ) { + const char *name = it.entry->key; + if (str_has_prefix(name, prefix)) { + add_completion(xstrdup(name)); } } - collect_aliases(prefix); } static void do_collect_files ( @@ -122,16 +128,16 @@ continue; } - String buf = STRING_INIT; + String buf = string_new(strlen(dirprefix) + len + 4); if (dirprefix[0]) { - string_add_str(&buf, dirprefix); + string_append_cstring(&buf, dirprefix); if (!str_has_suffix(dirprefix, "/")) { - string_add_byte(&buf, '/'); + string_append_byte(&buf, '/'); } } - string_add_str(&buf, name); + string_append_cstring(&buf, name); if (is_dir) { - string_add_byte(&buf, '/'); + string_append_byte(&buf, '/'); } add_completion(string_steal_cstring(&buf)); } @@ -191,35 +197,11 @@ } } -static void collect_env(const char *prefix) +static void collect_str(const char *const strs[], size_t n, const char *prefix) { - extern char **environ; - - for (size_t i = 0; environ[i]; i++) { - const char *e = environ[i]; - - if (str_has_prefix(e, prefix)) { - const char *end = strchr(e, '='); - if (end) { - add_completion(xstrcut(e, end - e)); - } - } - } - collect_builtin_env(prefix); -} - -static void collect_colors_and_attributes(const char *prefix) -{ - static const char names[][16] = { - "keep", "default", "black", "red", "green", "yellow", - "blue", "magenta", "cyan", "gray", "darkgray", "lightred", - "lightgreen", "lightyellow", "lightblue", "lightmagenta", - "lightcyan", "white", "underline", "reverse", "blink", - "dim", "bold", "invisible", "italic", "strikethrough" - }; - for (size_t i = 0; i < ARRAY_COUNT(names); i++) { - if (str_has_prefix(names[i], prefix)) { - add_completion(xstrdup(names[i])); + for (size_t i = 0; i < n; i++) { + if (str_has_prefix(strs[i], prefix)) { + add_completion(xstrdup(strs[i])); } } } @@ -227,107 +209,140 @@ static void collect_completions(char **args, size_t argc) { if (!argc) { - collect_commands(completion.parsed); + collect_normal_commands(completion.parsed); + collect_aliases(completion.parsed); return; } - const Command *cmd = find_command(commands, args[0]); - if (!cmd) { + const Command *cmd = find_normal_command(args[0]); + if (!cmd || cmd->max_args == 0) { return; } - const StringView cmd_name = string_view_from_cstring(cmd->name); + char **args_copy = copy_string_array(args + 1, argc - 1); + CommandArgs a = {.args = args_copy}; + ArgParseError err = do_parse_args(cmd, &a); if ( - string_view_equal_literal(&cmd_name, "open") - || string_view_equal_literal(&cmd_name, "wsplit") - || string_view_equal_literal(&cmd_name, "save") - || string_view_equal_literal(&cmd_name, "compile") - || string_view_equal_literal(&cmd_name, "run") - || string_view_equal_literal(&cmd_name, "pipe-from") - || string_view_equal_literal(&cmd_name, "pipe-to") + (err != 0 && err != ARGERR_TOO_FEW_ARGUMENTS) + || (a.nr_args >= cmd->max_args && cmd->max_args != 0xFF) ) { - collect_files(false); - return; + goto out; } - if (string_view_equal_literal(&cmd_name, "cd")) { - collect_files(true); - return; - } - if (string_view_equal_literal(&cmd_name, "include")) { - switch (argc) { - case 1: + + const StringView cmd_name = strview_from_cstring(args[0]); + + if ( + strview_equal_cstring(&cmd_name, "save") + || strview_equal_cstring(&cmd_name, "run") + || strview_equal_cstring(&cmd_name, "pipe-from") + || strview_equal_cstring(&cmd_name, "pipe-to") + ) { + collect_files(false); + } else if (strview_equal_cstring(&cmd_name, "open")) { + if (!cmdargs_has_flag(&a, 't')) { collect_files(false); - break; - case 2: - if (streq(args[1], "-b")) { - collect_builtin_configs(completion.parsed, false); + } + } else if (strview_equal_cstring(&cmd_name, "wsplit")) { + if (!cmdargs_has_flag(&a, 't') && !cmdargs_has_flag(&a, 'n')) { + collect_files(false); + } + } else if (strview_equal_cstring(&cmd_name, "cd")) { + collect_files(true); + } else if (strview_equal_cstring(&cmd_name, "include")) { + if (a.nr_args == 0) { + if (cmdargs_has_flag(&a, 'b')) { + collect_builtin_configs(completion.parsed); + } else { + collect_files(false); } - break; } - return; - } - if (string_view_equal_literal(&cmd_name, "insert-builtin")) { - if (argc == 1) { - collect_builtin_configs(completion.parsed, true); + } else if (strview_equal_cstring(&cmd_name, "ft")) { + if (a.nr_args == 0) { + collect_ft(completion.parsed); } - return; - } - if (string_view_equal_literal(&cmd_name, "hi")) { - switch (argc) { - case 1: + } else if (strview_equal_cstring(&cmd_name, "hi")) { + if (a.nr_args == 0) { collect_hl_colors(completion.parsed); - break; - default: + } else { collect_colors_and_attributes(completion.parsed); - break; } - return; - } - if (string_view_equal_literal(&cmd_name, "set")) { - if (argc % 2) { - collect_options(completion.parsed); + } else if (strview_equal_cstring(&cmd_name, "option")) { + if (a.nr_args == 0) { + if (!cmdargs_has_flag(&a, 'r')) { + collect_ft(completion.parsed); + } + } else if (a.nr_args & 1) { + collect_auto_options(completion.parsed); } else { - collect_option_values(args[argc - 1], completion.parsed); + collect_option_values(a.args[a.nr_args - 1], completion.parsed); } - return; - } - if (string_view_equal_literal(&cmd_name, "toggle") && argc == 1) { - collect_toggleable_options(completion.parsed); - return; - } - if (string_view_equal_literal(&cmd_name, "tag") && argc == 1) { - TagFile *tf = load_tag_file(); - if (tf != NULL) { - collect_tags(tf, completion.parsed); + } else if (strview_equal_cstring(&cmd_name, "set")) { + if ((a.nr_args + 1) & 1) { + bool local = cmdargs_has_flag(&a, 'l'); + bool global = cmdargs_has_flag(&a, 'g'); + collect_options(completion.parsed, local, global); + } else { + collect_option_values(a.args[a.nr_args - 1], completion.parsed); } - return; - } - if (string_view_equal_literal(&cmd_name, "show")) { - switch (argc) { - case 1: - if (str_has_prefix("alias", completion.parsed)) { - add_completion(xstrdup("alias")); - } - if (str_has_prefix("bind", completion.parsed)) { - add_completion(xstrdup("bind")); - } - break; - case 2: - if (streq(args[1], "alias")) { - collect_aliases(completion.parsed); - } - break; - case 3: - if ( - (streq(args[1], "alias") && streq(args[2], "-c")) - || (streq(args[1], "-c") && streq(args[2], "alias")) - ) { - collect_aliases(completion.parsed); + } else if (strview_equal_cstring(&cmd_name, "setenv")) { + if (a.nr_args == 0) { + collect_env(completion.parsed); + } else if (a.nr_args == 1 && completion.parsed[0] == '\0') { + BUG_ON(!a.args[0]); + const char *value = getenv(a.args[0]); + if (value) { + add_completion(xstrdup(value)); } - break; } - return; + } else if (strview_equal_cstring(&cmd_name, "toggle")) { + if (a.nr_args == 0) { + bool global = cmdargs_has_flag(&a, 'g'); + collect_toggleable_options(completion.parsed, global); + } + } else if (strview_equal_cstring(&cmd_name, "tag")) { + if (a.nr_args == 0 && !cmdargs_has_flag(&a, 'r')) { + TagFile *tf = load_tag_file(); + if (tf) { + collect_tags(tf, completion.parsed); + } + } + } else if (strview_equal_cstring(&cmd_name, "compile")) { + if (a.nr_args == 0) { + collect_compilers(completion.parsed); + } + } else if (strview_equal_cstring(&cmd_name, "errorfmt")) { + static const char *const names[] = { + "file", "line", "column", "message", "_" + }; + if (a.nr_args == 0) { + collect_compilers(completion.parsed); + } else if (a.nr_args >= 2 && !cmdargs_has_flag(&a, 'i')) { + collect_str(names, ARRAY_COUNT(names), completion.parsed); + } + } else if (strview_equal_cstring(&cmd_name, "repeat")) { + if (a.nr_args == 1) { + collect_normal_commands(completion.parsed); + } else if (a.nr_args >= 2) { + collect_completions(args + 2, argc - 2); + } + } else if (strview_equal_cstring(&cmd_name, "show")) { + if (a.nr_args == 0) { + collect_show_subcommands(completion.parsed); + } else if (a.nr_args == 1) { + BUG_ON(!a.args[0]); + collect_show_subcommand_args(a.args[0], completion.parsed); + } + } else if (strview_equal_cstring(&cmd_name, "macro")) { + static const char *const verbs[] = { + "record", "stop", "toggle", "cancel", "play" + }; + if (a.nr_args == 0) { + collect_str(verbs, ARRAY_COUNT(verbs), completion.parsed); + } } + +out: + free_string_array(args_copy); } static void init_completion(void) @@ -354,14 +369,14 @@ if (cmd[pos] == ';') { semicolon = array.count; - ptr_array_add(&array, NULL); + ptr_array_append(&array, NULL); pos++; continue; } - CommandParseError err = 0; + CommandParseError err; size_t end = find_end(cmd, pos, &err); - if (err || end >= editor.cmdline.pos) { + if (err != CMDERR_NONE || end >= editor.cmdline.pos) { completion_pos = pos; break; } @@ -372,23 +387,23 @@ if (value) { size_t save = array.count; - if (!parse_commands(&array, value, &err)) { - for (size_t i = save; i < array.count; i++) { + if (parse_commands(&array, value) != CMDERR_NONE) { + for (size_t i = save, n = array.count; i < n; i++) { free(array.ptrs[i]); array.ptrs[i] = NULL; } array.count = save; - ptr_array_add(&array, parse_command_arg(name, end - pos, true)); + ptr_array_append(&array, parse_command_arg(name, end - pos, true)); } else { // Remove NULL array.count--; } } else { - ptr_array_add(&array, parse_command_arg(name, end - pos, true)); + ptr_array_append(&array, parse_command_arg(name, end - pos, true)); } free(name); } else { - ptr_array_add(&array, parse_command_arg(cmd + pos, end - pos, true)); + ptr_array_append(&array, parse_command_arg(cmd + pos, end - pos, true)); } pos = end; } @@ -418,6 +433,7 @@ completion.head = xstrcut(cmd, completion_pos); completion.tail = xstrdup(cmd + editor.cmdline.pos); collect_env(name); + collect_builtin_env(name); sort_completions(); free(name); ptr_array_free(&array); @@ -431,55 +447,22 @@ completion.tail = xstrdup(cmd + editor.cmdline.pos); completion.add_space = true; - collect_completions ( - (char **)array.ptrs + 1 + semicolon, - array.count - semicolon - 1 - ); - sort_completions(); - ptr_array_free(&array); -} - -static char *escape(const char *str) -{ - String buf = STRING_INIT; - - if (!str[0]) { - return xmemdup_literal("\"\""); + char **args = NULL; + size_t argc = 0; + if (array.count) { + args = (char**)array.ptrs + 1 + semicolon; + argc = array.count - semicolon - 1; } - if (str[0] == '~' && !completion.tilde_expanded) { - string_add_byte(&buf, '\\'); - } - - for (size_t i = 0; str[i]; i++) { - const char ch = str[i]; - switch (ch) { - case ' ': - case '"': - case '$': - case '\'': - case ';': - case '\\': - string_add_byte(&buf, '\\'); - // Fallthrough - default: - string_add_byte(&buf, ch); - break; - } - } - return string_steal_cstring(&buf); + collect_completions(args, argc); + sort_completions(); + ptr_array_free(&array); } -void complete_command(void) +static void do_complete_command(void) { - if (!completion.head) { - init_completion(); - } - if (!completion.completions.count) { - return; - } - - char *middle = escape(completion.completions.ptrs[completion.idx]); + const char *arg = completion.completions.ptrs[completion.idx]; + char *middle = escape_command_arg(arg, !completion.tilde_expanded); size_t middle_len = strlen(middle); size_t head_len = strlen(completion.head); size_t tail_len = strlen(completion.tail); @@ -498,12 +481,49 @@ free(middle); free(str); - completion.idx = (completion.idx + 1) % completion.completions.count; if (completion.completions.count == 1) { reset_completion(); } } +void complete_command_next(void) +{ + const bool init = !completion.head; + if (init) { + init_completion(); + } + if (!completion.completions.count) { + return; + } + if (!init) { + if (completion.idx >= completion.completions.count - 1) { + completion.idx = 0; + } else { + completion.idx++; + } + } + do_complete_command(); +} + +void complete_command_prev(void) +{ + const bool init = !completion.head; + if (init) { + init_completion(); + } + if (!completion.completions.count) { + return; + } + if (!init) { + if (completion.idx == 0) { + completion.idx = completion.completions.count - 1; + } else { + completion.idx--; + } + } + do_complete_command(); +} + void reset_completion(void) { free(completion.escaped); diff -Nru dte-1.9.1/src/completion.h dte-1.10/src/completion.h --- dte-1.9.1/src/completion.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/completion.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,13 @@ #ifndef COMPLETION_H #define COMPLETION_H -void complete_command(void); +#include "util/hashmap.h" +#include "util/macros.h" + +void complete_command_next(void); +void complete_command_prev(void); void reset_completion(void); void add_completion(char *str); +void collect_hashmap_keys(const HashMap *map, const char *prefix) NONNULL_ARGS; #endif diff -Nru dte-1.9.1/src/config.c dte-1.10/src/config.c --- dte-1.9.1/src/config.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/config.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,19 +1,21 @@ -#include +#include +#include #include #include "config.h" +#include "commands.h" #include "completion.h" -#include "debug.h" #include "error.h" +#include "syntax/color.h" #include "terminal/terminal.h" #include "util/ascii.h" +#include "util/debug.h" #include "util/readfile.h" #include "util/str-util.h" #include "util/string.h" #include "util/xmalloc.h" #include "../build/builtin-config.h" -const char *config_file; -int config_line; +ConfigState current_config; static bool is_command(const char *str, size_t len) { @@ -38,50 +40,54 @@ return (len - 1 - pos) % 2; } -void exec_config(const Command *cmds, const char *buf, size_t size) +void exec_config(const CommandSet *cmds, const char *buf, size_t size) { const char *ptr = buf; - String line = STRING_INIT; + String line = string_new(1024); while (ptr < buf + size) { size_t n = buf + size - ptr; char *end = memchr(ptr, '\n', n); - if (end) { n = end - ptr; } if (line.len || is_command(ptr, n)) { if (has_line_continuation(ptr, n)) { - string_add_buf(&line, ptr, n - 1); + string_append_buf(&line, ptr, n - 1); } else { - string_add_buf(&line, ptr, n); - handle_command(cmds, string_borrow_cstring(&line)); + string_append_buf(&line, ptr, n); + handle_command(cmds, string_borrow_cstring(&line), false); string_clear(&line); } } - config_line++; + + current_config.line++; ptr += n + 1; } + if (line.len) { - handle_command(cmds, string_borrow_cstring(&line)); + handle_command(cmds, string_borrow_cstring(&line), false); } + string_free(&line); } -void list_builtin_configs(void) +String dump_builtin_configs(void) { + String str = string_new(1024); for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { - fputs(builtin_configs[i].name, stdout); - fputc('\n', stdout); + string_append_cstring(&str, builtin_configs[i].name); + string_append_byte(&str, '\n'); } + return str; } -void collect_builtin_configs(const char *const prefix, bool syntaxes) +void collect_builtin_configs(const char *prefix) { for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { const BuiltinConfig *cfg = &builtin_configs[i]; - if (syntaxes == false && str_has_prefix(cfg->name, "syntax/")) { + if (str_has_prefix(cfg->name, "syntax/")) { return; } else if (str_has_prefix(cfg->name, prefix)) { add_completion(xstrdup(cfg->name)); @@ -89,7 +95,7 @@ } } -const BuiltinConfig *get_builtin_config(const char *const name) +const BuiltinConfig *get_builtin_config(const char *name) { for (size_t i = 0; i < ARRAY_COUNT(builtin_configs); i++) { if (streq(name, builtin_configs[i].name)) { @@ -99,7 +105,13 @@ return NULL; } -int do_read_config(const Command *cmds, const char *filename, ConfigFlags flags) +const BuiltinConfig *get_builtin_configs_array(size_t *nconfigs) +{ + *nconfigs = ARRAY_COUNT(builtin_configs); + return &builtin_configs[0]; +} + +int do_read_config(const CommandSet *cmds, const char *filename, ConfigFlags flags) { const bool must_exist = flags & CFG_MUST_EXIST; const bool builtin = flags & CFG_BUILTIN; @@ -107,8 +119,8 @@ if (builtin) { const BuiltinConfig *cfg = get_builtin_config(filename); if (cfg) { - config_file = filename; - config_line = 1; + current_config.file = filename; + current_config.line = 1; exec_config(cmds, cfg->text.data, cfg->text.length); return 0; } else if (must_exist) { @@ -133,30 +145,35 @@ return err; } - config_file = filename; - config_line = 1; + current_config.file = filename; + current_config.line = 1; exec_config(cmds, buf, size); free(buf); return 0; } -int read_config(const Command *cmds, const char *filename, ConfigFlags flags) +int read_config(const CommandSet *cmds, const char *filename, ConfigFlags flags) { // Recursive - const char *saved_config_file = config_file; - int saved_config_line = config_line; + const ConfigState saved = current_config; int ret = do_read_config(cmds, filename, flags); - config_file = saved_config_file; - config_line = saved_config_line; + current_config = saved; return ret; } -void exec_reset_colors_rc(void) +void exec_builtin_color_reset(void) { + clear_hl_colors(); bool colors = terminal.color_type >= TERM_8_COLOR; const char *cfg = colors ? "color/reset" : "color/reset-basic"; - read_config(commands, cfg, CFG_MUST_EXIST | CFG_BUILTIN); + read_config(&commands, cfg, CFG_MUST_EXIST | CFG_BUILTIN); +} + +void exec_builtin_rc(void) +{ + exec_builtin_color_reset(); + read_config(&commands, "rc", CFG_MUST_EXIST | CFG_BUILTIN); } UNITTEST { diff -Nru dte-1.9.1/src/config.h dte-1.10/src/config.h --- dte-1.9.1/src/config.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/config.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,8 +2,10 @@ #define CONFIG_H #include -#include "command.h" +#include "command/run.h" +#include "util/macros.h" #include "util/string-view.h" +#include "util/string.h" typedef enum { CFG_NOFLAGS = 0, @@ -16,15 +18,21 @@ const StringView text; } BuiltinConfig; -extern const char *config_file; -extern int config_line; +typedef struct { + const char *file; + int line; +} ConfigState; + +extern ConfigState current_config; -void list_builtin_configs(void); -void collect_builtin_configs(const char *prefix, bool syntaxes); +String dump_builtin_configs(void); +void collect_builtin_configs(const char *prefix); const BuiltinConfig *get_builtin_config(const char *name) PURE; -void exec_config(const Command *cmds, const char *buf, size_t size); -int do_read_config(const Command *cmds, const char *filename, ConfigFlags f); -int read_config(const Command *cmds, const char *filename, ConfigFlags f); -void exec_reset_colors_rc(void); +const BuiltinConfig *get_builtin_configs_array(size_t *nconfigs); +void exec_config(const CommandSet *cmds, const char *buf, size_t size); +int do_read_config(const CommandSet *cmds, const char *filename, ConfigFlags f); +int read_config(const CommandSet *cmds, const char *filename, ConfigFlags f); +void exec_builtin_color_reset(void); +void exec_builtin_rc(void); #endif diff -Nru dte-1.9.1/src/convert.c dte-1.10/src/convert.c --- dte-1.9.1/src/convert.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/convert.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,671 @@ +#include +#include +#include +#include "convert.h" +#include "util/debug.h" +#include "util/str-util.h" +#include "util/xmalloc.h" +#include "util/xreadwrite.h" + +struct FileEncoder { + struct cconv *cconv; + unsigned char *nbuf; + size_t nsize; + bool crlf; + int fd; +}; + +struct FileDecoder { + const char *encoding; + const unsigned char *ibuf; + ssize_t ipos, isize; + struct cconv *cconv; + bool (*read_line)(struct FileDecoder *dec, const char **linep, size_t *lenp); +}; + +const char *file_decoder_get_encoding(const FileDecoder *dec) +{ + return dec->encoding; +} + +static bool read_utf8_line(FileDecoder *dec, const char **linep, size_t *lenp) +{ + char *line = (char *)dec->ibuf + dec->ipos; + const char *nl = memchr(line, '\n', dec->isize - dec->ipos); + size_t len; + + if (nl) { + len = nl - line; + dec->ipos += len + 1; + } else { + len = dec->isize - dec->ipos; + if (len == 0) { + return false; + } + dec->ipos += len; + } + + *linep = line; + *lenp = len; + return true; +} + +static size_t unix_to_dos ( + FileEncoder *enc, + const unsigned char *buf, + size_t size +) { + if (enc->nsize < size * 2) { + enc->nsize = size * 2; + xrenew(enc->nbuf, enc->nsize); + } + size_t d = 0; + for (size_t s = 0; s < size; s++) { + unsigned char ch = buf[s]; + if (ch == '\n') { + enc->nbuf[d++] = '\r'; + } + enc->nbuf[d++] = ch; + } + return d; +} + +#ifdef ICONV_DISABLE // iconv not available; use basic, UTF-8 implementation: + +bool conversion_supported_by_iconv ( + const char* UNUSED_ARG(from), + const char* UNUSED_ARG(to) +) { + errno = EINVAL; + return false; +} + +FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) +{ + if (unlikely(encoding->type != UTF8)) { + errno = EINVAL; + return NULL; + } + FileEncoder *enc = xnew0(FileEncoder, 1); + enc->crlf = crlf; + enc->fd = fd; + return enc; +} + +void free_file_encoder(FileEncoder *enc) +{ + free(enc->nbuf); + free(enc); +} + +ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t n) +{ + if (enc->crlf) { + n = unix_to_dos(enc, buf, n); + buf = enc->nbuf; + } + return xwrite(enc->fd, buf, n); +} + +size_t file_encoder_get_nr_errors(const FileEncoder* UNUSED_ARG(enc)) +{ + return 0; +} + +FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t n) +{ + if (unlikely(encoding && !streq(encoding, "UTF-8"))) { + errno = EINVAL; + return NULL; + } + FileDecoder *dec = xnew0(FileDecoder, 1); + dec->ibuf = buf; + dec->isize = n; + return dec; +} + +void free_file_decoder(FileDecoder *dec) +{ + free(dec); +} + +bool file_decoder_read_line(FileDecoder *dec, const char **linep, size_t *lenp) +{ + return read_utf8_line(dec, linep, lenp); +} + +#else // ICONV_DISABLE is undefined; use full iconv implementation: + +#include +#include +#include "editor.h" +#include "util/hashset.h" +#include "util/str-util.h" +#include "util/utf8.h" + +static unsigned char replacement[2] = "\xc2\xbf"; // U+00BF + +struct cconv { + iconv_t cd; + + char *obuf; + size_t osize; + size_t opos; + + size_t consumed; + size_t errors; + + // Temporary input buffer + char tbuf[16]; + size_t tcount; + + // Replacement character 0xBF (inverted question mark) + char rbuf[4]; + size_t rcount; + + // Input character size in bytes. zero for UTF-8. + size_t char_size; +}; + +static struct cconv *create(iconv_t cd) +{ + struct cconv *c = xnew0(struct cconv, 1); + c->cd = cd; + c->osize = 8192; + c->obuf = xmalloc(c->osize); + return c; +} + +static size_t encoding_char_size(const char *encoding) +{ + if (str_has_prefix(encoding, "UTF-16")) { + return 2; + } + if (str_has_prefix(encoding, "UTF-32")) { + return 4; + } + return 1; +} + +static size_t iconv_wrapper ( + iconv_t cd, + char **restrict inbuf, + size_t *restrict inbytesleft, + char **restrict outbuf, + size_t *restrict outbytesleft +) { + // POSIX defines the second parameter of iconv(3) as "char **restrict" + // but NetBSD declares it as "const char **restrict". + #ifdef __NetBSD__ + #if HAS_WARNING("-Wincompatible-pointer-types-discards-qualifiers") + IGNORE_WARNING("-Wincompatible-pointer-types-discards-qualifiers") + #else + IGNORE_WARNING("-Wincompatible-pointer-types") + #endif + #endif + + return iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); + + #ifdef __NetBSD__ + UNIGNORE_WARNINGS + #endif +} + +static void encode_replacement(struct cconv *c) +{ + char *ib = replacement; + char *ob = c->rbuf; + size_t ic = sizeof(replacement); + size_t oc = sizeof(c->rbuf); + size_t rc = iconv_wrapper(c->cd, &ib, &ic, &ob, &oc); + + if (rc == (size_t)-1) { + c->rbuf[0] = '\xbf'; + c->rcount = 1; + } else { + c->rcount = ob - c->rbuf; + } +} + +static void resize_obuf(struct cconv *c) +{ + c->osize *= 2; + xrenew(c->obuf, c->osize); +} + +static void add_replacement(struct cconv *c) +{ + if (c->osize - c->opos < 4) { + resize_obuf(c); + } + + memcpy(c->obuf + c->opos, c->rbuf, c->rcount); + c->opos += c->rcount; +} + +static size_t handle_invalid(struct cconv *c, const char *buf, size_t count) +{ + DEBUG_LOG("%zu %zu", c->char_size, count); + add_replacement(c); + if (c->char_size == 0) { + // Converting from UTF-8 + size_t idx = 0; + CodePoint u = u_get_char(buf, count, &idx); + DEBUG_LOG("U+%04" PRIX32, u); + return idx; + } + if (c->char_size > count) { + // wtf + return 1; + } + return c->char_size; +} + +static int xiconv(struct cconv *c, char **ib, size_t *ic) +{ + while (1) { + char *ob = c->obuf + c->opos; + size_t oc = c->osize - c->opos; + size_t rc = iconv_wrapper(c->cd, ib, ic, &ob, &oc); + + c->opos = ob - c->obuf; + if (rc == (size_t)-1) { + switch (errno) { + case EILSEQ: + c->errors++; + // Reset + iconv(c->cd, NULL, NULL, NULL, NULL); + return errno; + case EINVAL: + return errno; + case E2BIG: + resize_obuf(c); + continue; + default: + BUG("iconv: %s", strerror(errno)); + } + } else { + c->errors += rc; + } + return 0; + } +} + +static size_t convert_incomplete(struct cconv *c, const char *input, size_t len) +{ + size_t ic, ipos = 0; + char *ib; + + while (c->tcount < sizeof(c->tbuf) && ipos < len) { + size_t skip; + + c->tbuf[c->tcount++] = input[ipos++]; + + ib = c->tbuf; + ic = c->tcount; + int rc = xiconv(c, &ib, &ic); + + if (ic > 0) { + memmove(c->tbuf, ib, ic); + } + c->tcount = ic; + + switch (rc) { + case EINVAL: + // Incomplete character at end of input buffer. + // Try again with more input data. + continue; + case EILSEQ: + // Invalid multibyte sequence + skip = handle_invalid(c, c->tbuf, c->tcount); + c->tcount -= skip; + if (c->tcount > 0) { + DEBUG_LOG("tcount=%zu, skip=%zu", c->tcount, skip); + memmove(c->tbuf, c->tbuf + skip, c->tcount); + continue; + } + return ipos; + } + break; + } + DEBUG_LOG("%zu %zu", ipos, c->tcount); + return ipos; +} + +static void cconv_process(struct cconv *c, const char *input, size_t len) +{ + if (c->consumed > 0) { + size_t fill = c->opos - c->consumed; + memmove(c->obuf, c->obuf + c->consumed, fill); + c->opos = fill; + c->consumed = 0; + } + + if (c->tcount > 0) { + size_t ipos = convert_incomplete(c, input, len); + input += ipos; + len -= ipos; + } + + char *ib = (char *)input; + size_t ic = len; + while (ic > 0) { + size_t skip; + + switch (xiconv(c, &ib, &ic)) { + case EINVAL: + // Incomplete character at end of input buffer. + if (ic < sizeof(c->tbuf)) { + memcpy(c->tbuf, ib, ic); + c->tcount = ic; + } else { + // FIXME + } + ic = 0; + break; + case EILSEQ: + // Invalid multibyte sequence. + skip = handle_invalid(c, ib, ic); + ic -= skip; + ib += skip; + break; + } + } +} + +static struct cconv *cconv_to_utf8(const char *encoding) +{ + iconv_t cd = iconv_open("UTF-8", encoding); + if (cd == (iconv_t)-1) { + return NULL; + } + struct cconv *c = create(cd); + memcpy(c->rbuf, replacement, sizeof(replacement)); + c->rcount = sizeof(replacement); + c->char_size = encoding_char_size(encoding); + return c; +} + +static struct cconv *cconv_from_utf8(const char *encoding) +{ + iconv_t cd = iconv_open(encoding, "UTF-8"); + if (cd == (iconv_t)-1) { + return NULL; + } + struct cconv *c = create(cd); + encode_replacement(c); + return c; +} + +static void cconv_flush(struct cconv *c) +{ + if (c->tcount > 0) { + // Replace incomplete character at end of input buffer. + DEBUG_LOG("incomplete character at EOF"); + add_replacement(c); + c->tcount = 0; + } +} + +static char *cconv_consume_line(struct cconv *c, size_t *len) +{ + char *line = c->obuf + c->consumed; + char *nl = memchr(line, '\n', c->opos - c->consumed); + + if (!nl) { + *len = 0; + return NULL; + } + + size_t n = nl - line + 1; + c->consumed += n; + *len = n; + return line; +} + +static char *cconv_consume_all(struct cconv *c, size_t *len) +{ + char *buf = c->obuf + c->consumed; + *len = c->opos - c->consumed; + c->consumed = c->opos; + return buf; +} + +static void cconv_free(struct cconv *c) +{ + iconv_close(c->cd); + free(c->obuf); + free(c); +} + +bool conversion_supported_by_iconv(const char *from, const char *to) +{ + if (unlikely(from[0] == '\0' || to[0] == '\0')) { + errno = EINVAL; + return false; + } + iconv_t cd = iconv_open(to, from); + if (cd == (iconv_t)-1) { + return false; + } + iconv_close(cd); + return true; +} + +FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) +{ + FileEncoder *enc = xnew0(FileEncoder, 1); + enc->crlf = crlf; + enc->fd = fd; + + if (encoding->type != UTF8) { + enc->cconv = cconv_from_utf8(encoding->name); + if (!enc->cconv) { + free(enc); + return NULL; + } + } + + return enc; +} + +void free_file_encoder(FileEncoder *enc) +{ + if (enc->cconv) { + cconv_free(enc->cconv); + } + free(enc->nbuf); + free(enc); +} + +// NOTE: buf must contain whole characters! +ssize_t file_encoder_write ( + FileEncoder *enc, + const unsigned char *buf, + size_t size +) { + if (enc->crlf) { + size = unix_to_dos(enc, buf, size); + buf = enc->nbuf; + } + if (enc->cconv) { + cconv_process(enc->cconv, buf, size); + cconv_flush(enc->cconv); + buf = cconv_consume_all(enc->cconv, &size); + } + return xwrite(enc->fd, buf, size); +} + +size_t file_encoder_get_nr_errors(const FileEncoder *enc) +{ + return enc->cconv ? enc->cconv->errors : 0; +} + +static bool fill(FileDecoder *dec) +{ + size_t icount = dec->isize - dec->ipos; + + // Smaller than cconv.obuf to make realloc less likely + size_t max = 7 * 1024; + + if (icount > max) { + icount = max; + } + + if (dec->ipos == dec->isize) { + return false; + } + + cconv_process(dec->cconv, dec->ibuf + dec->ipos, icount); + dec->ipos += icount; + if (dec->ipos == dec->isize) { + // Must be flushed after all input has been fed + cconv_flush(dec->cconv); + } + return true; +} + +static bool decode_and_read_line(FileDecoder *dec, const char **linep, size_t *lenp) +{ + char *line; + size_t len; + while (1) { + line = cconv_consume_line(dec->cconv, &len); + if (line) { + break; + } + + if (!fill(dec)) { + break; + } + } + + if (line) { + // Newline not wanted + len--; + } else { + line = cconv_consume_all(dec->cconv, &len); + if (len == 0) { + return false; + } + } + + *linep = line; + *lenp = len; + return true; +} + +static bool set_encoding(FileDecoder *dec, const char *encoding) +{ + if (strcmp(encoding, "UTF-8") == 0) { + dec->read_line = read_utf8_line; + } else { + dec->cconv = cconv_to_utf8(encoding); + if (!dec->cconv) { + return false; + } + dec->read_line = decode_and_read_line; + } + dec->encoding = str_intern(encoding); + return true; +} + +static bool detect(FileDecoder *dec, const unsigned char *line, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (line[i] >= 0x80) { + size_t idx = i; + CodePoint u = u_get_nonascii(line, len, &idx); + const char *encoding; + if (u_is_unicode(u)) { + encoding = "UTF-8"; + } else if (editor.term_utf8) { + if (dec->isize <= (32 * 1024 * 1024)) { + // If locale is UTF-8 but file doesn't contain valid + // UTF-8 and is also fairly small, just assume latin1 + encoding = "ISO-8859-1"; + } else { + // Large files are likely binary; just decode as + // UTF-8 to avoid costly charset conversion + encoding = "UTF-8"; + } + } else { + // Assume encoding is same as locale + encoding = editor.charset.name; + } + if (!set_encoding(dec, encoding)) { + // FIXME: error message? + set_encoding(dec, "UTF-8"); + } + return true; + } + } + + // ASCII + return false; +} + +static bool detect_and_read_line(FileDecoder *dec, const char **linep, size_t *lenp) +{ + char *line = (char *)dec->ibuf + dec->ipos; + const char *nl = memchr(line, '\n', dec->isize - dec->ipos); + size_t len; + + if (nl) { + len = nl - line; + } else { + len = dec->isize - dec->ipos; + if (len == 0) { + return false; + } + } + + if (detect(dec, line, len)) { + // Encoding detected + return dec->read_line(dec, linep, lenp); + } + + // Only ASCII so far + dec->ipos += len; + if (nl) { + dec->ipos++; + } + *linep = line; + *lenp = len; + return true; +} + +FileDecoder *new_file_decoder ( + const char *encoding, + const unsigned char *buf, + size_t size +) { + FileDecoder *dec = xnew0(FileDecoder, 1); + dec->ibuf = buf; + dec->isize = size; + dec->read_line = detect_and_read_line; + + if (encoding) { + if (!set_encoding(dec, encoding)) { + free_file_decoder(dec); + return NULL; + } + } + + return dec; +} + +void free_file_decoder(FileDecoder *dec) +{ + if (dec->cconv) { + cconv_free(dec->cconv); + } + free(dec); +} + +bool file_decoder_read_line(FileDecoder *dec, const char **linep, size_t *lenp) +{ + return dec->read_line(dec, linep, lenp); +} + +#endif diff -Nru dte-1.9.1/src/convert.h dte-1.10/src/convert.h --- dte-1.9.1/src/convert.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/convert.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,24 @@ +#ifndef ENCODING_CONVERT_H +#define ENCODING_CONVERT_H + +#include +#include +#include "encoding.h" +#include "util/macros.h" + +typedef struct FileDecoder FileDecoder; +typedef struct FileEncoder FileEncoder; + +bool conversion_supported_by_iconv(const char *from, const char *to) NONNULL_ARGS; + +FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t size); +void free_file_decoder(FileDecoder *dec); +bool file_decoder_read_line(FileDecoder *dec, const char **line, size_t *len) NONNULL_ARGS; +const char *file_decoder_get_encoding(const FileDecoder *dec) NONNULL_ARGS; + +FileEncoder *new_file_encoder(const Encoding *encoding, bool crlf, int fd) NONNULL_ARGS; +void free_file_encoder(FileEncoder *enc); +ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t size) NONNULL_ARGS; +size_t file_encoder_get_nr_errors(const FileEncoder *enc) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/ctags.c dte-1.10/src/ctags.c --- dte-1.9.1/src/ctags.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/ctags.c 2021-04-03 21:08:53.000000000 +0000 @@ -115,7 +115,7 @@ len = ei - si; if (len == 1) { t->kind = buf[si]; - } else if (len == 5 && !memcmp(buf + si, "file:", 5)) { + } else if (len == 5 && mem_equal(buf + si, "file:", 5)) { t->local = true; } // FIXME: struct/union/typeref @@ -134,35 +134,21 @@ bool exact, Tag *t ) { - size_t prefix_len = strlen(prefix); - size_t pos = *posp; - - while (pos < tf->size) { - size_t len = tf->size - pos; - char *line = tf->buf + pos; - char *end = memchr(line, '\n', len); - - if (end) { - len = end - line; - } - pos += len + 1; - - if (!len || line[0] == '!') { + size_t pflen = strlen(prefix); + for (size_t pos = *posp, size = tf->size; pos < size; ) { + StringView line = buf_slice_next_line(tf->buf, &pos, size); + if (line.length == 0 || line.data[0] == '!') { continue; } - - if (len <= prefix_len || memcmp(line, prefix, prefix_len) != 0) { + if (line.length <= pflen || !mem_equal(line.data, prefix, pflen)) { continue; } - - if (exact && line[prefix_len] != '\t') { + if (exact && line.data[pflen] != '\t') { continue; } - - if (!parse_line(t, line, len)) { + if (!parse_line(t, line.data, line.length)) { continue; } - *posp = pos; return true; } diff -Nru dte-1.9.1/src/debug.c dte-1.10/src/debug.c --- dte-1.9.1/src/debug.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/debug.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include "debug.h" -#include "editor.h" -#include "terminal/terminal.h" -#include "util/xreadwrite.h" -#include "util/xsnprintf.h" - -void term_cleanup(void) -{ - if (editor.status == EDITOR_INITIALIZING) { - return; - } - if (!editor.child_controls_terminal) { - editor.ui_end(); - } - if (terminal.control_codes.deinit.length) { - xwrite ( - STDOUT_FILENO, - terminal.control_codes.deinit.data, - terminal.control_codes.deinit.length - ); - } -} - -NORETURN -void fatal_error(const char *msg, int err) -{ - term_cleanup(); - errno = err; - perror(msg); - abort(); -} - -#if DEBUG >= 1 -NORETURN -void bug(const char *file, int line, const char *func, const char *fmt, ...) { - term_cleanup(); - - fprintf(stderr, "\n%s:%d: **BUG** in %s() function: '", file, line, func); - - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - fputs("'\n", stderr); - fflush(stderr); - - abort(); -} -#endif - -#ifdef DEBUG_PRINT -void debug_print(const char *function, const char *fmt, ...) -{ - static int fd = -1; - if (fd < 0) { - char *filename = editor_file("debug.log"); - fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0666); - free(filename); - BUG_ON(fd < 0); - - // Don't leak file descriptor to parent processes - int r = fcntl(fd, F_SETFD, FD_CLOEXEC); - BUG_ON(r == -1); - } - - char buf[4096]; - size_t write_max = ARRAY_COUNT(buf); - const size_t len1 = xsnprintf(buf, write_max, "%s: ", function); - write_max -= len1; - - va_list ap; - va_start(ap, fmt); - const size_t len2 = xvsnprintf(buf + len1, write_max, fmt, ap); - va_end(ap); - - xwrite(fd, buf, len1 + len2); -} -#endif diff -Nru dte-1.9.1/src/debug.h dte-1.10/src/debug.h --- dte-1.9.1/src/debug.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/debug.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,36 +0,0 @@ -#ifndef DEBUG_H -#define DEBUG_H - -#include "util/macros.h" - -#define BUG_ON(a) do { \ - if (unlikely(a)) { \ - BUG("%s", #a); \ - } \ -} while (0) - -#if DEBUG >= 1 - #define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__) - - NORETURN COLD PRINTF(4) - void bug(const char *file, int line, const char *func, const char *fmt, ...); -#else - #define BUG(...) UNREACHABLE() -#endif - -#ifdef DEBUG_PRINT - #define d_print(...) debug_print(__func__, __VA_ARGS__) - - PRINTF(2) - void debug_print(const char *function, const char *fmt, ...); -#else - PRINTF(1) - static inline void d_print(const char* UNUSED_ARG(fmt), ...) {} -#endif - -void term_cleanup(void); - -NORETURN COLD NONNULL_ARGS -void fatal_error(const char *msg, int err); - -#endif diff -Nru dte-1.9.1/src/edit.c dte-1.10/src/edit.c --- dte-1.9.1/src/edit.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/edit.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,4 +1,3 @@ -#include #include "edit.h" #include "buffer.h" #include "change.h" @@ -6,7 +5,9 @@ #include "move.h" #include "regexp.h" #include "selection.h" +#include "util/debug.h" #include "util/string.h" +#include "util/string-view.h" #include "util/utf8.h" #include "util/xmalloc.h" #include "view.h" @@ -38,29 +39,29 @@ void select_block(void) { BlockIter sbi, ebi, bi = view->cursor; - LineRef lr; + StringView line; int level = 0; // If current line does not match \{\s*$ but matches ^\s*\} then // cursor is likely at end of the block you want to select. - fetch_this_line(&bi, &lr); + fetch_this_line(&bi, &line); if ( - !regexp_match_nosub(spattern, lr.line, lr.size) - && regexp_match_nosub(epattern, lr.line, lr.size) + !regexp_match_nosub(spattern, &line) + && regexp_match_nosub(epattern, &line) ) { block_iter_prev_line(&bi); } while (1) { - fetch_this_line(&bi, &lr); - if (regexp_match_nosub(spattern, lr.line, lr.size)) { + fetch_this_line(&bi, &line); + if (regexp_match_nosub(spattern, &line)) { if (level++ == 0) { sbi = bi; block_iter_next_line(&bi); break; } } - if (regexp_match_nosub(epattern, lr.line, lr.size)) { + if (regexp_match_nosub(epattern, &line)) { level--; } @@ -70,14 +71,14 @@ } while (1) { - fetch_this_line(&bi, &lr); - if (regexp_match_nosub(epattern, lr.line, lr.size)) { + fetch_this_line(&bi, &line); + if (regexp_match_nosub(epattern, &line)) { if (--level == 0) { ebi = bi; break; } } - if (regexp_match_nosub(spattern, lr.line, lr.size)) { + if (regexp_match_nosub(spattern, &line)) { level++; } @@ -97,19 +98,19 @@ static int get_indent_of_matching_brace(void) { BlockIter bi = view->cursor; - LineRef lr; + StringView line; int level = 0; while (block_iter_prev_line(&bi)) { - fetch_this_line(&bi, &lr); - if (regexp_match_nosub(spattern, lr.line, lr.size)) { + fetch_this_line(&bi, &line); + if (regexp_match_nosub(spattern, &line)) { if (level++ == 0) { IndentInfo info; - get_indent_info(lr.line, lr.size, &info); + get_indent_info(&line, &info); return info.width; } } - if (regexp_match_nosub(epattern, lr.line, lr.size)) { + if (regexp_match_nosub(epattern, &line)) { level--; } } @@ -151,16 +152,17 @@ } } -void insert_text(const char *text, size_t size) +void insert_text(const char *text, size_t size, bool move_after) { size_t del_count = 0; - if (view->selection) { del_count = prepare_selection(view); unselect(); } buffer_replace_bytes(del_count, text, size); - block_iter_skip_bytes(&view->cursor, size); + if (move_after) { + block_iter_skip_bytes(&view->cursor, size); + } } void paste(bool at_cursor) @@ -257,10 +259,10 @@ return count; } -static bool ws_only(const LineRef *lr) +static bool ws_only(const StringView *line) { - for (size_t i = 0, n = lr->size; i < n; i++) { - char ch = lr->line[i]; + for (size_t i = 0, n = line->length; i < n; i++) { + char ch = line->data[i]; if (ch != ' ' && ch != '\t') { return false; } @@ -273,9 +275,9 @@ { block_iter_bol(bi); do { - LineRef lr; - fill_line_ref(bi, &lr); - if (!ws_only(&lr)) { + StringView line; + fill_line_ref(bi, &line); + if (!ws_only(&line)) { return true; } } while (block_iter_prev_line(bi)); @@ -302,19 +304,18 @@ // Current line will be split at cursor position BlockIter bi = view->cursor; size_t len = block_iter_bol(&bi); - LineRef lr; - - fill_line_ref(&bi, &lr); - lr.size = len; - if (ws_only(&lr)) { + StringView line; + fill_line_ref(&bi, &line); + line.length = len; + if (ws_only(&line)) { // This line is (or will become) white space only. // Find previous non whitespace only line. if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { - fill_line_ref(&bi, &lr); - ins = get_indent_for_next_line(lr.line, lr.size); + fill_line_ref(&bi, &line); + ins = get_indent_for_next_line(&line); } } else { - ins = get_indent_for_next_line(lr.line, lr.size); + ins = get_indent_for_next_line(&line); } } @@ -358,17 +359,15 @@ && buffer->options.brace_indent ) { BlockIter bi = view->cursor; - LineRef curlr; - + StringView curlr; block_iter_bol(&bi); fill_line_ref(&bi, &curlr); if (ws_only(&curlr)) { int width = get_indent_of_matching_brace(); - if (width >= 0) { // Replace current (ws only) line with some indent + '}' block_iter_bol(&view->cursor); - del_count = curlr.size; + del_count = curlr.length; if (width) { free(ins); ins = make_indent(width); @@ -499,16 +498,13 @@ static void shift_right(size_t nr_lines, size_t count) { size_t indent_size; - char *indent; - - indent = alloc_indent(count, &indent_size); + char *indent = alloc_indent(count, &indent_size); size_t i = 0; while (1) { IndentInfo info; - LineRef lr; - - fetch_this_line(&view->cursor, &lr); - get_indent_info(lr.line, lr.size, &info); + StringView line; + fetch_this_line(&view->cursor, &line); + get_indent_info(&line, &info); if (info.wsonly) { if (info.bytes) { // Remove indentation @@ -537,10 +533,9 @@ size_t i = 0; while (1) { IndentInfo info; - LineRef lr; - - fetch_this_line(&view->cursor, &lr); - get_indent_info(lr.line, lr.size, &info); + StringView line; + fetch_this_line(&view->cursor, &line); + get_indent_info(&line, &info); if (info.wsonly) { if (info.bytes) { // Remove indentation @@ -627,11 +622,10 @@ if (buffer->options.auto_indent) { BlockIter bi = view->cursor; - if (block_iter_prev_line(&bi) && find_non_empty_line_bwd(&bi)) { - LineRef lr; - fill_line_ref(&bi, &lr); - indent = get_indent_for_next_line(lr.line, lr.size); + StringView line; + fill_line_ref(&bi, &line); + indent = get_indent_for_next_line(&line); } } @@ -661,6 +655,23 @@ block_iter_skip_bytes(&view->cursor, ins_count); } +void delete_lines(void) +{ + long x = view_get_preferred_x(view); + size_t del_count; + if (view->selection) { + view->selection = SELECT_LINES; + del_count = prepare_selection(view); + unselect(); + } else { + block_iter_bol(&view->cursor); + BlockIter tmp = view->cursor; + del_count = block_iter_eat_line(&tmp); + } + buffer_delete_bytes(del_count); + move_to_preferred_x(x); +} + void new_line(void) { size_t ins_count = 1; @@ -670,11 +681,10 @@ if (buffer->options.auto_indent) { BlockIter bi = view->cursor; - if (find_non_empty_line_bwd(&bi)) { - LineRef lr; - fill_line_ref(&bi, &lr); - ins = get_indent_for_next_line(lr.line, lr.size); + StringView line; + fill_line_ref(&bi, &line); + ins = get_indent_for_next_line(&line); } } @@ -696,67 +706,65 @@ { size_t i = 0; size_t word_width = 0; - while (i < len) { word_width += u_char_width(u_get_char(word, len, &i)); } if (pf->cur_width && pf->cur_width + 1 + word_width > pf->text_width) { - string_add_byte(&pf->buf, '\n'); + string_append_byte(&pf->buf, '\n'); pf->cur_width = 0; } if (pf->cur_width == 0) { if (pf->indent_len) { - string_add_buf(&pf->buf, pf->indent, pf->indent_len); + string_append_buf(&pf->buf, pf->indent, pf->indent_len); } pf->cur_width = pf->indent_width; } else { - string_add_byte(&pf->buf, ' '); + string_append_byte(&pf->buf, ' '); pf->cur_width++; } - string_add_buf(&pf->buf, word, len); + string_append_buf(&pf->buf, word, len); pf->cur_width += word_width; } -static bool is_paragraph_separator(const char *line, size_t size) +static bool is_paragraph_separator(const StringView *line) { - return regexp_match_nosub("^[ \t]*(/\\*|\\*/)?[ \t]*$", line, size); + return regexp_match_nosub("^[ \t]*(/\\*|\\*/)?[ \t]*$", line); } -static size_t get_indent_width(const char *line, size_t size) +static size_t get_indent_width(const StringView *line) { IndentInfo info; - get_indent_info(line, size, &info); + get_indent_info(line, &info); return info.width; } -static bool in_paragraph(const char *line, size_t size, size_t indent_width) +static bool in_paragraph(const StringView *line, size_t indent_width) { - if (get_indent_width(line, size) != indent_width) { + if (get_indent_width(line) != indent_width) { return false; } - return !is_paragraph_separator(line, size); + return !is_paragraph_separator(line); } static size_t paragraph_size(void) { BlockIter bi = view->cursor; - LineRef lr; - + StringView line; block_iter_bol(&bi); - fill_line_ref(&bi, &lr); - if (is_paragraph_separator(lr.line, lr.size)) { + fill_line_ref(&bi, &line); + if (is_paragraph_separator(&line)) { // Not in paragraph return 0; } - size_t indent_width = get_indent_width(lr.line, lr.size); + size_t indent_width = get_indent_width(&line); // Go to beginning of paragraph while (block_iter_prev_line(&bi)) { - fill_line_ref(&bi, &lr); - if (!in_paragraph(lr.line, lr.size, indent_width)) { + fill_line_ref(&bi, &line); + if (!in_paragraph(&line, indent_width)) { block_iter_eat_line(&bi); break; } @@ -767,14 +775,12 @@ size_t size = 0; do { size_t bytes = block_iter_eat_line(&bi); - if (!bytes) { break; } - size += bytes; - fill_line_ref(&bi, &lr); - } while (in_paragraph(lr.line, lr.size, indent_width)); + fill_line_ref(&bi, &line); + } while (in_paragraph(&line, indent_width)); return size; } @@ -792,7 +798,8 @@ } char *sel = block_iter_get_bytes(&view->cursor, len); - size_t indent_width = get_indent_width(sel, len); + StringView sv = string_view(sel, len); + size_t indent_width = get_indent_width(&sv); char *indent = make_indent(indent_width); ParagraphFormatter pf = { @@ -832,7 +839,7 @@ } if (pf.buf.len) { - string_add_byte(&pf.buf, '\n'); + string_append_byte(&pf.buf, '\n'); } buffer_replace_bytes(len, pf.buf.buffer, pf.buf.len); if (pf.buf.len) { @@ -845,14 +852,11 @@ unselect(); } -void change_case(int mode) +void change_case(char mode) { bool was_selecting = false; bool move = true; - size_t text_len, i; - char *src; - String dst = STRING_INIT; - + size_t text_len; if (view->selection) { SelectionInfo info; init_selection(view, &info); @@ -869,27 +873,25 @@ text_len = u_char_size(u); } - src = block_iter_get_bytes(&view->cursor, text_len); - i = 0; + String dst = string_new(text_len); + char *src = block_iter_get_bytes(&view->cursor, text_len); + size_t i = 0; while (i < text_len) { CodePoint u = u_get_char(src, text_len, &i); - switch (mode) { case 't': - if (iswupper(u)) { - u = towlower(u); - } else { - u = towupper(u); - } + u = u_is_upper(u) ? u_to_lower(u) : u_to_upper(u); break; case 'l': - u = towlower(u); + u = u_to_lower(u); break; case 'u': - u = towupper(u); + u = u_to_upper(u); break; + default: + BUG("unhandled case mode"); } - string_add_ch(&dst, u); + string_append_codepoint(&dst, u); } buffer_replace_bytes(text_len, dst.buffer, dst.len); diff -Nru dte-1.9.1/src/edit.h dte-1.10/src/edit.h --- dte-1.9.1/src/edit.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/edit.h 2021-04-03 21:08:53.000000000 +0000 @@ -9,7 +9,7 @@ void unselect(void); void cut(size_t len, bool is_lines); void copy(size_t len, bool is_lines); -void insert_text(const char *text, size_t size); +void insert_text(const char *text, size_t size, bool move_after); void paste(bool at_cursor); void delete_ch(void); void erase(void); @@ -17,8 +17,9 @@ void join_lines(void); void shift_lines(int count); void clear_lines(void); +void delete_lines(void); void new_line(void); void format_paragraph(size_t text_width); -void change_case(int mode); +void change_case(char mode); #endif diff -Nru dte-1.9.1/src/editor.c dte-1.10/src/editor.c --- dte-1.9.1/src/editor.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editor.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,49 +1,58 @@ +#include #include #include -#include #include +#include +#include #include #include "editor.h" +#include "alias.h" #include "buffer.h" -#include "command.h" -#include "config.h" -#include "debug.h" #include "error.h" #include "mode.h" +#include "regexp.h" #include "screen.h" -#include "search.h" #include "terminal/input.h" +#include "terminal/mode.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/ascii.h" -#include "util/str-util.h" +#include "util/debug.h" +#include "util/exitcode.h" +#include "util/hashset.h" +#include "util/utf8.h" #include "util/xmalloc.h" +#include "util/xsnprintf.h" #include "view.h" #include "window.h" #include "../build/version.h" -static volatile sig_atomic_t terminal_resized; - -static void resize(void); -static void ui_end(void); - EditorState editor = { .status = EDITOR_INITIALIZING, .input_mode = INPUT_NORMAL, .child_controls_terminal = false, .everything_changed = false, - .search_history = PTR_ARRAY_INIT, - .command_history = PTR_ARRAY_INIT, + .resized = false, + .exit_code = EX_OK, + .buffers = PTR_ARRAY_INIT, .version = version, .cmdline_x = 0, - .resize = resize, - .ui_end = ui_end, .cmdline = { .buf = STRING_INIT, .pos = 0, - .search_pos = -1, + .search_pos = NULL, .search_text = NULL }, + .command_history = { + .filename = NULL, + .max_entries = 512, + .entries = HASHMAP_INIT + }, + .search_history = { + .filename = NULL, + .max_entries = 128, + .entries = HASHMAP_INIT + }, .options = { .auto_indent = true, .detect_indent = 0, @@ -59,50 +68,56 @@ // Global-only options .case_sensitive_search = CSS_TRUE, + .crlf_newlines = false, .display_invisible = false, .display_special = false, .esc_timeout = 100, + .filesize_limit = 250, .lock_files = true, - .newline = NEWLINE_UNIX, .scroll_margin = 0, + .select_cursor_char = true, .set_window_title = false, .show_line_numbers = false, - .statusline_left = NULL, - .statusline_right = NULL, - .tab_bar = TAB_BAR_HORIZONTAL, - .tab_bar_max_components = 0, - .tab_bar_width = 25, - .filesize_limit = 250, - }, - .mode_ops = { - [INPUT_NORMAL] = &normal_mode_ops, - [INPUT_COMMAND] = &command_mode_ops, - [INPUT_SEARCH] = &search_mode_ops, - [INPUT_GIT_OPEN] = &git_open_ops + .statusline_left = " %f%s%m%r%s%M", + .statusline_right = " %y,%X %u %E%s%b%s%n %t %p ", + .tab_bar = true, + .utf8_bom = false, } }; void init_editor_state(void) { - const char *const pager = getenv("PAGER"); - const char *const home = getenv("HOME"); - const char *const dte_home = getenv("DTE_HOME"); + const char *home = getenv("HOME"); + const char *dte_home = getenv("DTE_HOME"); + editor.xdg_runtime_dir = getenv("XDG_RUNTIME_DIR"); - editor.pager = xstrdup(pager ? pager : "less"); - editor.home_dir = xstrdup(home ? home : ""); + editor.home_dir = strview_intern(home ? home : ""); if (dte_home) { editor.user_config_dir = xstrdup(dte_home); } else { - editor.user_config_dir = xasprintf("%s/.dte", editor.home_dir); + editor.user_config_dir = xasprintf("%s/.dte", editor.home_dir.data); + } + + log_init("DTE_LOG"); + DEBUG_LOG("version: %s", version); + + const char *locale = setlocale(LC_CTYPE, ""); + if (unlikely(!locale)) { + fatal_error("setlocale", errno); } - setlocale(LC_CTYPE, ""); + DEBUG_LOG("locale: %s", locale); editor.charset = encoding_from_name(nl_langinfo(CODESET)); editor.term_utf8 = (editor.charset.type == UTF8); - editor.options.statusline_left = " %f%s%m%r%s%M"; - editor.options.statusline_right = " %y,%X %u %E %n %t %p "; + // Allow child processes to detect that they're running under dte + if (unlikely(setenv("DTE_VERSION", version, true) != 0)) { + fatal_error("setenv", errno); + } + + regexp_init_word_boundary_tokens(); + init_aliases(); } static void sanity_check(void) @@ -148,20 +163,19 @@ static void restore_cursor(void) { - View *v = window->view; switch (editor.input_mode) { case INPUT_NORMAL: - terminal.move_cursor ( - window->edit_x + v->cx_display - v->vx, - window->edit_y + v->cy - v->vy + term_move_cursor ( + window->edit_x + view->cx_display - view->vx, + window->edit_y + view->cy - view->vy ); break; case INPUT_COMMAND: case INPUT_SEARCH: - terminal.move_cursor(editor.cmdline_x, terminal.height - 1); - break; - case INPUT_GIT_OPEN: + term_move_cursor(editor.cmdline_x, terminal.height - 1); break; + default: + BUG("unhandled input mode"); } } @@ -181,8 +195,8 @@ term_show_cursor(); term_output_flush(); - window->view->buffer->changed_line_min = INT_MAX; - window->view->buffer->changed_line_max = -1; + buffer->changed_line_min = LONG_MAX; + buffer->changed_line_max = -1; for_each_window(clear_update_tabbar); } @@ -202,7 +216,7 @@ View *v = w->view; if (editor.options.show_line_numbers) { // Force updating lines numbers if all lines changed - update_line_numbers(w, v->buffer->changed_line_max == INT_MAX); + update_line_numbers(w, v->buffer->changed_line_max == LONG_MAX); } long y1 = v->buffer->changed_line_min; @@ -219,7 +233,7 @@ } // Update all visible views containing this buffer -static void update_buffer_windows(const Buffer *const b) +static void update_buffer_windows(const Buffer *b) { for (size_t i = 0, n = b->views.count; i < n; i++) { View *v = b->views.ptrs[i]; @@ -243,51 +257,52 @@ void normal_update(void) { start_update(); - update_term_title(window->view->buffer); + update_term_title(buffer); update_all_windows(); update_command_line(); end_update(); } -void handle_sigwinch(int UNUSED_ARG(signum)) +static void ui_resize(void) { - terminal_resized = true; + if (editor.status == EDITOR_INITIALIZING) { + return; + } + editor.resized = false; + update_screen_size(); + normal_update(); } -static void resize(void) +void ui_start(void) { - terminal_resized = false; - update_screen_size(); - - // Turn keypad on (makes cursor keys work) + if (editor.status == EDITOR_INITIALIZING) { + return; + } + terminal.put_control_code(terminal.control_codes.init); terminal.put_control_code(terminal.control_codes.keypad_on); - - // Use alternate buffer if possible terminal.put_control_code(terminal.control_codes.cup_mode_on); - - editor.mode_ops[editor.input_mode]->update(); + ui_resize(); } -static void ui_end(void) +void ui_end(void) { - terminal.put_control_code(terminal.control_codes.reset_colors); - terminal.put_control_code(terminal.control_codes.reset_attrs); + if (editor.status == EDITOR_INITIALIZING) { + return; + } terminal.clear_screen(); - - terminal.move_cursor(0, terminal.height - 1); + term_move_cursor(0, terminal.height - 1); term_show_cursor(); - terminal.put_control_code(terminal.control_codes.cup_mode_off); terminal.put_control_code(terminal.control_codes.keypad_off); - + terminal.put_control_code(terminal.control_codes.deinit); term_output_flush(); - terminal.cooked(); + term_cooked(); } void suspend(void) { if (getpid() == getsid(0)) { - // Session leader can't suspend + error_msg("Session leader can't suspend"); return; } if ( @@ -296,110 +311,163 @@ ) { ui_end(); } - kill(0, SIGSTOP); + int r = kill(0, SIGSTOP); + if (unlikely(r != 0)) { + perror_msg("kill"); + term_raw(); + ui_start(); + } } -char *editor_file(const char *name) +const char *editor_file(const char *name) { - return xasprintf("%s/%s", editor.user_config_dir, name); + char buf[4096]; + size_t n = xsnprintf(buf, sizeof buf, "%s/%s", editor.user_config_dir, name); + return mem_intern(buf, n); } -char get_confirmation(const char *choices, const char *format, ...) +static char get_choice(const char *choices) { - char buf[4096]; - va_list ap; - va_start(ap, format); - vsnprintf(buf, sizeof(buf), format, ap); - va_end(ap); - - size_t pos = strlen(buf); - buf[pos++] = ' '; - buf[pos++] = '['; - - unsigned char def = 0; - for (size_t i = 0, n = strlen(choices); i < n; i++) { - if (ascii_isupper(choices[i])) { - def = ascii_tolower(choices[i]); + KeyCode key; + if (!term_read_key(&key)) { + return 0; + } + + switch (key) { + case KEY_PASTE: + term_discard_paste(); + return 0; + case CTRL('C'): + case CTRL('G'): + case CTRL('['): + return 0x18; // Cancel + case KEY_ENTER: + return choices[0]; // Default + } + + if (key < 128) { + char ch = ascii_tolower(key); + if (strchr(choices, ch)) { + return ch; } - buf[pos++] = choices[i]; - buf[pos++] = '/'; } + return 0; +} - buf[pos - 1] = ']'; - buf[pos] = '\0'; +static void show_dialog(const char *question) +{ + unsigned int question_width = u_str_width(question); + unsigned int min_width = question_width + 2; + if (terminal.height < 12 || terminal.width < min_width) { + return; + } + + unsigned int height = terminal.height / 4; + unsigned int mid = terminal.height / 2; + unsigned int top = mid - (height / 2); + unsigned int bot = top + height; + unsigned int width = MAX(terminal.width / 2, min_width); + unsigned int x = (terminal.width - width) / 2; + + // The "underline" and "strikethrough" attributes should only apply + // to the text, not the whole dialog background: + const TermColor *text_color = &builtin_colors[BC_DIALOG]; + TermColor dialog_color = *text_color; + dialog_color.attr &= ~(ATTR_UNDERLINE | ATTR_STRIKETHROUGH); + set_color(&dialog_color); + + for (unsigned int y = top; y < bot; y++) { + term_output_reset(x, width, 0); + term_move_cursor(x, y); + if (y == mid) { + term_set_bytes(' ', (width - question_width) / 2); + set_color(text_color); + term_add_str(question); + set_color(&dialog_color); + } + term_clear_eol(); + } +} + +char dialog_prompt(const char *question, const char *choices) +{ + normal_update(); + term_hide_cursor(); + show_dialog(question); + show_message(question, false); + term_output_flush(); + char choice; + while ((choice = get_choice(choices)) == 0) { + if (!editor.resized) { + continue; + } + ui_resize(); + term_hide_cursor(); + show_dialog(question); + show_message(question, false); + term_output_flush(); + } + + mark_everything_changed(); + return (choice >= 'a') ? choice : 0; +} + +char status_prompt(const char *question, const char *choices) +{ // update_windows() assumes these have been called for the current view - View *v = window->view; - view_update_cursor_x(v); - view_update_cursor_y(v); - view_update(v); + view_update_cursor_x(view); + view_update_cursor_y(view); + view_update(view); // Set changed_line_min and changed_line_max before calling update_range() - mark_all_lines_changed(v->buffer); + mark_all_lines_changed(buffer); start_update(); - update_term_title(v->buffer); - update_buffer_windows(v->buffer); - show_message(buf, false); + update_term_title(buffer); + update_buffer_windows(buffer); + show_message(question, false); end_update(); - KeyCode key; - while (1) { - if (term_read_key(&key)) { - if (key == KEY_PASTE) { - term_discard_paste(); - continue; - } - if (key == CTRL('C') || key == CTRL('G') || key == CTRL('[')) { - key = 0; - break; - } - if (key == KEY_ENTER && def) { - key = def; - break; - } - if (key > 127) { - continue; - } - key = ascii_tolower(key); - if (strchr(choices, key)) { - break; - } - if (key == def) { - break; - } - } else if (terminal_resized) { - resize(); + char choice; + while ((choice = get_choice(choices)) == 0) { + if (!editor.resized) { + continue; } + ui_resize(); + term_hide_cursor(); + show_message(question, false); + restore_cursor(); + term_show_cursor(); + term_output_flush(); } - return key; + + return (choice >= 'a') ? choice : 0; } typedef struct { bool is_modified; - unsigned int id; + unsigned long id; long cy; long vx; long vy; } ScreenState; -static void update_screen(const ScreenState *const s) +static void update_screen(const ScreenState *s) { if (editor.everything_changed) { - editor.mode_ops[editor.input_mode]->update(); + normal_update(); editor.everything_changed = false; return; } - View *v = window->view; - view_update_cursor_x(v); - view_update_cursor_y(v); - view_update(v); - - Buffer *b = v->buffer; - if (s->id == b->id) { - if (s->vx != v->vx || s->vy != v->vy) { - mark_all_lines_changed(b); + view_update_cursor_x(view); + view_update_cursor_y(view); + view_update(view); + + if (s->id == buffer->id) { + if (s->vx != view->vx || s->vy != view->vy) { + mark_all_lines_changed(buffer); } else { // Because of trailing whitespace highlighting and // highlighting current line in different color @@ -407,21 +475,21 @@ // to be updated. // // Always update at least current line. - buffer_mark_lines_changed(b, s->cy, v->cy); + buffer_mark_lines_changed(buffer, s->cy, view->cy); } - if (s->is_modified != buffer_modified(b)) { - mark_buffer_tabbars_changed(b); + if (s->is_modified != buffer_modified(buffer)) { + mark_buffer_tabbars_changed(buffer); } } else { window->update_tabbar = true; - mark_all_lines_changed(b); + mark_all_lines_changed(buffer); } start_update(); if (window->update_tabbar) { - update_term_title(b); + update_term_title(buffer); } - update_buffer_windows(b); + update_buffer_windows(buffer); update_command_line(); end_update(); } @@ -429,8 +497,8 @@ void main_loop(void) { while (editor.status == EDITOR_RUNNING) { - if (terminal_resized) { - resize(); + if (editor.resized) { + ui_resize(); } KeyCode key; @@ -439,25 +507,16 @@ } clear_error(); - if (editor.input_mode == INPUT_GIT_OPEN) { - editor.mode_ops[editor.input_mode]->keypress(key); - editor.mode_ops[editor.input_mode]->update(); - } else { - const View *v = window->view; - const ScreenState s = { - .is_modified = buffer_modified(v->buffer), - .id = v->buffer->id, - .cy = v->cy, - .vx = v->vx, - .vy = v->vy - }; - editor.mode_ops[editor.input_mode]->keypress(key); - sanity_check(); - if (editor.input_mode == INPUT_GIT_OPEN) { - editor.mode_ops[INPUT_GIT_OPEN]->update(); - } else { - update_screen(&s); - } - } + const ScreenState s = { + .is_modified = buffer_modified(buffer), + .id = buffer->id, + .cy = view->cy, + .vx = view->vx, + .vy = view->vy + }; + + handle_input(key); + sanity_check(); + update_screen(&s); } } diff -Nru dte-1.9.1/src/editorconfig/editorconfig.c dte-1.10/src/editorconfig/editorconfig.c --- dte-1.9.1/src/editorconfig/editorconfig.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/editorconfig.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,16 +1,14 @@ #include -#include #include -#include #include "editorconfig.h" #include "ini.h" #include "match.h" -#include "../debug.h" -#include "../util/ascii.h" -#include "../util/path.h" -#include "../util/string.h" -#include "../util/string-view.h" -#include "../util/strtonum.h" +#include "util/debug.h" +#include "util/path.h" +#include "util/string.h" +#include "util/string-view.h" +#include "util/strtonum.h" +#include "util/str-util.h" typedef struct { const char *const pathname; @@ -27,7 +25,7 @@ EC_UNKNOWN_PROPERTY, } PropertyType; -#define CMP(s, val) if (!memcmp(name->data, s, STRLEN(s))) return val; break +#define CMP(s, val) if (mem_equal(name->data, s, STRLEN(s))) return val; break static PropertyType lookup_property(const StringView *name) { @@ -48,16 +46,16 @@ unsigned int n = 0; switch (lookup_property(name)) { case EC_INDENT_STYLE: - if (string_view_equal_literal_icase(val, "space")) { + if (strview_equal_cstring_icase(val, "space")) { options->indent_style = INDENT_STYLE_SPACE; - } else if (string_view_equal_literal_icase(val, "tab")) { + } else if (strview_equal_cstring_icase(val, "tab")) { options->indent_style = INDENT_STYLE_TAB; } else { options->indent_style = INDENT_STYLE_UNSPECIFIED; } break; case EC_INDENT_SIZE: - if (string_view_equal_literal_icase(val, "tab")) { + if (strview_equal_cstring_icase(val, "tab")) { options->indent_size_is_tab = true; options->indent_size = 0; } else { @@ -78,16 +76,19 @@ break; case EC_UNKNOWN_PROPERTY: break; + default: + BUG("unhandled property type"); } } -static int ini_handler(const IniData *data, void *ud) { +static int ini_handler(const IniData *data, void *ud) +{ UserData *userdata = ud; if (data->section.length == 0) { if ( - string_view_equal_literal_icase(&data->name, "root") - && string_view_equal_literal_icase(&data->value, "true") + strview_equal_cstring_icase(&data->name, "root") + && strview_equal_cstring_icase(&data->value, "true") ) { // root=true, clear all previous values userdata->options = editorconfig_options_init(); @@ -109,22 +110,22 @@ case '*': case ',': case '-': case '?': case '[': case '\\': case ']': case '{': case '}': - string_add_byte(&pattern, '\\'); + string_append_byte(&pattern, '\\'); // Fallthrough default: - string_add_byte(&pattern, ch); + string_append_byte(&pattern, ch); } } - if (!string_view_memchr(&data->section, '/')) { + if (!strview_memchr(&data->section, '/')) { // No slash in pattern, append "**/" - string_add_literal(&pattern, "**/"); + string_append_literal(&pattern, "**/"); } else if (data->section.data[0] != '/') { // Pattern contains at least one slash but not at the start, add one - string_add_byte(&pattern, '/'); + string_append_byte(&pattern, '/'); } - string_add_string_view(&pattern, &data->section); + string_append_strview(&pattern, &data->section); userdata->match = ec_pattern_match ( pattern.buffer, pattern.len, @@ -168,8 +169,8 @@ return err_num; } - const char *const slash = strchr(ptr, '/'); - if (slash == NULL) { + const char *slash = strchr(ptr, '/'); + if (!slash) { break; } diff -Nru dte-1.9.1/src/editorconfig/editorconfig.h dte-1.10/src/editorconfig/editorconfig.h --- dte-1.9.1/src/editorconfig/editorconfig.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/editorconfig.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,7 +2,7 @@ #define EDITORCONFIG_H #include -#include "../util/macros.h" +#include "util/macros.h" typedef enum { INDENT_STYLE_UNSPECIFIED, diff -Nru dte-1.9.1/src/editorconfig/ini.c dte-1.10/src/editorconfig/ini.c --- dte-1.9.1/src/editorconfig/ini.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/ini.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,17 +1,10 @@ #include -#include #include "ini.h" -#include "../debug.h" -#include "../util/ascii.h" -#include "../util/readfile.h" - -static char *trim_left(char *str) -{ - while (ascii_isspace(*str)) { - str++; - } - return str; -} +#include "util/ascii.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/readfile.h" +#include "util/str-util.h" static void strip_trailing_comments_and_whitespace(StringView *line) { @@ -38,9 +31,9 @@ UNITTEST { StringView tmp = STRING_VIEW(" \t key = val # inline comment "); - string_view_trim_left(&tmp); + strview_trim_left(&tmp); strip_trailing_comments_and_whitespace(&tmp); - BUG_ON(!string_view_equal_literal(&tmp, "key = val")); + BUG_ON(!strview_equal_cstring(&tmp, "key = val")); } int ini_parse(const char *filename, IniCallback callback, void *userdata) @@ -53,7 +46,7 @@ const size_t size = ssize; size_t pos = 0; - if (size >= 3 && memcmp(buf, "\xEF\xBB\xBF", 3) == 0) { + if (size >= 3 && mem_equal(buf, "\xEF\xBB\xBF", 3)) { // Skip past UTF-8 BOM pos += 3; } @@ -63,18 +56,13 @@ while (pos < size) { StringView line = buf_slice_next_line(buf, &pos, size); - string_view_trim_left(&line); - - if (line.length < 2) { + strview_trim_left(&line); + if (line.length < 2 || line.data[0] == '#' || line.data[0] == ';') { continue; } - switch (line.data[0]) { - case ';': - case '#': - continue; - case '[': - strip_trailing_comments_and_whitespace(&line); + strip_trailing_comments_and_whitespace(&line); + if (line.data[0] == '[') { if (line.length > 1 && line.data[line.length - 1] == ']') { section = string_view(line.data + 1, line.length - 2); nameidx = 0; @@ -82,32 +70,29 @@ continue; } - strip_trailing_comments_and_whitespace(&line); - char *delim = string_view_memchr(&line, '='); - if (delim) { - const size_t before_delim_len = delim - line.data; - size_t name_len = before_delim_len; - while (name_len > 0 && ascii_isblank(line.data[name_len - 1])) { - name_len--; - } - if (name_len == 0) { - continue; - } - - char *after_delim = delim + 1; - char *value = trim_left(after_delim); - size_t diff = value - after_delim; - size_t value_len = line.length - before_delim_len - 1 - diff; - - const IniData data = { - .section = section, - .name = string_view(line.data, name_len), - .value = string_view(value, value_len), - .name_idx = nameidx++, - }; + size_t val_offset = 0; + StringView name = get_delim(line.data, &val_offset, line.length, '='); + if (val_offset >= line.length) { + continue; + } - callback(&data, userdata); + strview_trim_right(&name); + if (name.length == 0) { + continue; } + + StringView value = line; + strview_remove_prefix(&value, val_offset); + strview_trim_left(&value); + + const IniData data = { + .section = section, + .name = name, + .value = value, + .name_idx = nameidx++, + }; + + callback(&data, userdata); } free(buf); diff -Nru dte-1.9.1/src/editorconfig/ini.h dte-1.10/src/editorconfig/ini.h --- dte-1.9.1/src/editorconfig/ini.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/ini.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,7 @@ #ifndef EDITORCONFIG_INI_H #define EDITORCONFIG_INI_H -#include "../util/string-view.h" +#include "util/string-view.h" typedef struct { StringView section; diff -Nru dte-1.9.1/src/editorconfig/match.c dte-1.10/src/editorconfig/match.c --- dte-1.9.1/src/editorconfig/match.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/match.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,11 +1,10 @@ #include -#include #include -#include #include "match.h" -#include "../debug.h" -#include "../util/str-util.h" -#include "../util/string.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/str-util.h" +#include "util/string.h" static size_t get_last_paired_brace_index(const char *str, size_t len) { @@ -45,7 +44,7 @@ { BUG_ON(len == 0); if (len == 1) { - string_add_literal(buf, "\\["); + string_append_literal(buf, "\\["); return 0; } @@ -63,20 +62,20 @@ } if (!closed) { - string_add_literal(buf, "\\["); + string_append_literal(buf, "\\["); return 0; } // TODO: interpret characters according to editorconfig instead - // of just copying the bracket expression to be interpretted as + // of just copying the bracket expression to be interpreted as // regex - string_add_byte(buf, '['); + string_append_byte(buf, '['); if (pat[0] == '!') { - string_add_byte(buf, '^'); - string_add_buf(buf, pat + 1, i - 1); + string_append_byte(buf, '^'); + string_append_buf(buf, pat + 1, i - 1); } else { - string_add_buf(buf, pat, i); + string_append_buf(buf, pat, i); } return i; } @@ -94,7 +93,7 @@ bool ec_pattern_match(const char *pattern, size_t pattern_len, const char *path) { - String buf = STRING_INIT; + String buf = string_new(pattern_len * 2); size_t brace_level = 0; size_t last_paired_brace_index = get_last_paired_brace_index(pattern, pattern_len); bool brace_group_has_empty_alternate[32]; @@ -107,22 +106,22 @@ if (i + 1 < pattern_len) { ch = pattern[++i]; if (is_regex_special_char(ch)) { - string_add_byte(&buf, '\\'); + string_append_byte(&buf, '\\'); } - string_add_byte(&buf, ch); + string_append_byte(&buf, ch); } else { - string_add_literal(&buf, "\\\\"); + string_append_literal(&buf, "\\\\"); } break; case '?': - string_add_literal(&buf, "[^/]"); + string_append_literal(&buf, "[^/]"); break; case '*': if (i + 1 < pattern_len && pattern[i + 1] == '*') { - string_add_literal(&buf, ".*"); + string_append_literal(&buf, ".*"); i++; } else { - string_add_literal(&buf, "[^/]*"); + string_append_literal(&buf, "[^/]*"); } break; case '[': @@ -133,7 +132,7 @@ break; case '{': { if (i >= last_paired_brace_index) { - string_add_literal(&buf, "\\{"); + string_append_literal(&buf, "\\{"); break; } brace_level++; @@ -148,7 +147,7 @@ i++; brace_level--; } else { - string_add_byte(&buf, '('); + string_append_byte(&buf, '('); } break; } @@ -156,9 +155,9 @@ if (i > last_paired_brace_index || brace_level == 0) { goto add_byte; } - string_add_byte(&buf, ')'); + string_append_byte(&buf, ')'); if (brace_group_has_empty_alternate[brace_level]) { - string_add_byte(&buf, '?'); + string_append_byte(&buf, '?'); } brace_group_has_empty_alternate[brace_level] = false; brace_level--; @@ -175,13 +174,13 @@ if (i + 1 < pattern_len && pattern[i + 1] == '}') { brace_group_has_empty_alternate[brace_level] = true; } else { - string_add_byte(&buf, '|'); + string_append_byte(&buf, '|'); } break; } case '/': - if (i + 3 < pattern_len && memcmp(pattern + i, "/**/", 4) == 0) { - string_add_literal(&buf, "(/|/.*/)"); + if (i + 3 < pattern_len && mem_equal(pattern + i, "/**/", 4)) { + string_append_literal(&buf, "(/|/.*/)"); i += 3; break; } @@ -191,15 +190,15 @@ case ')': case '|': case '+': - string_add_byte(&buf, '\\'); + string_append_byte(&buf, '\\'); // Fallthrough default: add_byte: - string_add_byte(&buf, ch); + string_append_byte(&buf, ch); } } - string_add_byte(&buf, '$'); + string_append_byte(&buf, '$'); char *regex_pattern = string_steal_cstring(&buf); regex_t re; diff -Nru dte-1.9.1/src/editorconfig/match.h dte-1.10/src/editorconfig/match.h --- dte-1.9.1/src/editorconfig/match.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editorconfig/match.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,7 +2,8 @@ #define EDITORCONFIG_MATCH_H #include -#include "../util/macros.h" +#include +#include "util/macros.h" NONNULL_ARGS bool ec_pattern_match(const char *pattern, size_t pat_len, const char *path); diff -Nru dte-1.9.1/src/editor.h dte-1.10/src/editor.h --- dte-1.9.1/src/editor.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/editor.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,14 +1,16 @@ #ifndef EDITOR_H #define EDITOR_H +#include #include +#include #include "cmdline.h" -#include "mode.h" +#include "encoding.h" +#include "history.h" #include "options.h" -#include "encoding/encoding.h" -#include "terminal/color.h" #include "util/macros.h" #include "util/ptr-array.h" +#include "util/string-view.h" typedef enum { EDITOR_INITIALIZING, @@ -20,28 +22,27 @@ INPUT_NORMAL, INPUT_COMMAND, INPUT_SEARCH, - INPUT_GIT_OPEN, } InputMode; typedef struct { EditorStatus status; - const EditorModeOps *mode_ops[4]; InputMode input_mode; CommandLine cmdline; GlobalOptions options; - const char *home_dir; + StringView home_dir; const char *user_config_dir; + const char *xdg_runtime_dir; Encoding charset; - const char *pager; bool child_controls_terminal; bool everything_changed; bool term_utf8; + int exit_code; size_t cmdline_x; - PointerArray search_history; - PointerArray command_history; + PointerArray buffers; + History search_history; + History command_history; const char *const version; - void (*resize)(void); - void (*ui_end)(void); + volatile sig_atomic_t resized; } EditorState; extern EditorState editor; @@ -57,12 +58,14 @@ } void init_editor_state(void); -char *editor_file(const char *name) XSTRDUP; -char get_confirmation(const char *choices, const char *format, ...) PRINTF(2); +const char *editor_file(const char *name) NONNULL_ARGS_AND_RETURN; +char status_prompt(const char *question, const char *choices) NONNULL_ARGS; +char dialog_prompt(const char *question, const char *choices) NONNULL_ARGS; void any_key(void); void normal_update(void); -void handle_sigwinch(int signum); void suspend(void); void main_loop(void); +void ui_start(void); +void ui_end(void); #endif diff -Nru dte-1.9.1/src/encoding/bom.c dte-1.10/src/encoding/bom.c --- dte-1.9.1/src/encoding/bom.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/bom.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,31 +0,0 @@ -#include -#include "bom.h" -#include "../debug.h" -#include "../util/macros.h" - -static const ByteOrderMark boms[NR_ENCODING_TYPES] = { - [UTF8] = {{0xef, 0xbb, 0xbf}, 3}, - [UTF16BE] = {{0xfe, 0xff}, 2}, - [UTF16LE] = {{0xff, 0xfe}, 2}, - [UTF32BE] = {{0x00, 0x00, 0xfe, 0xff}, 4}, - [UTF32LE] = {{0xff, 0xfe, 0x00, 0x00}, 4}, -}; - -EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size) -{ - // Iterate array backwards to ensure UTF32LE is checked before UTF16LE - for (int i = NR_ENCODING_TYPES - 1; i >= 0; i--) { - const unsigned int bom_len = boms[i].len; - if (bom_len > 0 && size >= bom_len && !memcmp(buf, boms[i].bytes, bom_len)) { - return (EncodingType) i; - } - } - return UNKNOWN_ENCODING; -} - -const ByteOrderMark *get_bom_for_encoding(EncodingType encoding) -{ - BUG_ON(encoding >= NR_ENCODING_TYPES); - const ByteOrderMark *bom = &boms[encoding]; - return bom->len ? bom : NULL; -} diff -Nru dte-1.9.1/src/encoding/bom.h dte-1.10/src/encoding/bom.h --- dte-1.9.1/src/encoding/bom.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/bom.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -#ifndef ENCODING_BOM_H -#define ENCODING_BOM_H - -#include -#include "encoding.h" - -typedef struct { - const unsigned char bytes[4]; - unsigned int len; -} ByteOrderMark; - -const ByteOrderMark *get_bom_for_encoding(EncodingType encoding); -EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size); - -#endif diff -Nru dte-1.9.1/src/encoding/convert.c dte-1.10/src/encoding/convert.c --- dte-1.9.1/src/encoding/convert.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/convert.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,358 +0,0 @@ -#include -#include "convert.h" -#include "../debug.h" -#include "../util/macros.h" - -#ifndef ICONV_DISABLE - -#include -#include -#include -#include -#include "encoding.h" -#include "../util/ascii.h" -#include "../util/str-util.h" -#include "../util/utf8.h" -#include "../util/xmalloc.h" - -static unsigned char replacement[2] = "\xc2\xbf"; // U+00BF - -struct cconv { - iconv_t cd; - - char *obuf; - size_t osize; - size_t opos; - - size_t consumed; - size_t errors; - - // Temporary input buffer - char tbuf[16]; - size_t tcount; - - // Replacement character 0xBF (inverted question mark) - char rbuf[4]; - size_t rcount; - - // Input character size in bytes. zero for UTF-8. - size_t char_size; -}; - -static struct cconv *create(iconv_t cd) -{ - struct cconv *c = xnew0(struct cconv, 1); - c->cd = cd; - c->osize = 8192; - c->obuf = xmalloc(c->osize); - return c; -} - -static size_t encoding_char_size(const char *encoding) -{ - if (str_has_prefix(encoding, "UTF-16")) { - return 2; - } - if (str_has_prefix(encoding, "UTF-32")) { - return 4; - } - return 1; -} - -static size_t iconv_wrapper ( - iconv_t cd, - char **restrict inbuf, - size_t *restrict inbytesleft, - char **restrict outbuf, - size_t *restrict outbytesleft -) { - // POSIX defines the second parameter of iconv(3) as "char **restrict" - // but NetBSD declares it as "const char **restrict". - #ifdef __NetBSD__ - #if HAS_WARNING("-Wincompatible-pointer-types-discards-qualifiers") - IGNORE_WARNING("-Wincompatible-pointer-types-discards-qualifiers") - #else - IGNORE_WARNING("-Wincompatible-pointer-types") - #endif - #endif - - return iconv(cd, inbuf, inbytesleft, outbuf, outbytesleft); - - #ifdef __NetBSD__ - UNIGNORE_WARNINGS - #endif -} - -static void encode_replacement(struct cconv *c) -{ - char *ib = replacement; - char *ob = c->rbuf; - size_t ic = sizeof(replacement); - size_t oc = sizeof(c->rbuf); - size_t rc = iconv_wrapper(c->cd, &ib, &ic, &ob, &oc); - - if (rc == (size_t)-1) { - c->rbuf[0] = '\xbf'; - c->rcount = 1; - } else { - c->rcount = ob - c->rbuf; - } -} - -static void resize_obuf(struct cconv *c) -{ - c->osize *= 2; - xrenew(c->obuf, c->osize); -} - -static void add_replacement(struct cconv *c) -{ - if (c->osize - c->opos < 4) { - resize_obuf(c); - } - - memcpy(c->obuf + c->opos, c->rbuf, c->rcount); - c->opos += c->rcount; -} - -static size_t handle_invalid(struct cconv *c, const char *buf, size_t count) -{ - d_print("%zu %zu\n", c->char_size, count); - add_replacement(c); - if (c->char_size == 0) { - // Converting from UTF-8 - size_t idx = 0; - CodePoint u = u_get_char(buf, count, &idx); - d_print("U+%04" PRIX32 "\n", u); - return idx; - } - if (c->char_size > count) { - // wtf - return 1; - } - return c->char_size; -} - -static int xiconv(struct cconv *c, char **ib, size_t *ic) -{ - while (1) { - char *ob = c->obuf + c->opos; - size_t oc = c->osize - c->opos; - size_t rc = iconv_wrapper(c->cd, ib, ic, &ob, &oc); - - c->opos = ob - c->obuf; - if (rc == (size_t)-1) { - switch (errno) { - case EILSEQ: - c->errors++; - // Reset - iconv(c->cd, NULL, NULL, NULL, NULL); - return errno; - case EINVAL: - return errno; - case E2BIG: - resize_obuf(c); - continue; - default: - BUG("iconv: %s", strerror(errno)); - } - } else { - c->errors += rc; - } - return 0; - } -} - -static size_t convert_incomplete(struct cconv *c, const char *input, size_t len) -{ - size_t ic, ipos = 0; - char *ib; - - while (c->tcount < sizeof(c->tbuf) && ipos < len) { - size_t skip; - - c->tbuf[c->tcount++] = input[ipos++]; - - ib = c->tbuf; - ic = c->tcount; - int rc = xiconv(c, &ib, &ic); - - if (ic > 0) { - memmove(c->tbuf, ib, ic); - } - c->tcount = ic; - - switch (rc) { - case EINVAL: - // Incomplete character at end of input buffer. - // Try again with more input data. - continue; - case EILSEQ: - // Invalid multibyte sequence - skip = handle_invalid(c, c->tbuf, c->tcount); - c->tcount -= skip; - if (c->tcount > 0) { - d_print("tcount=%zu, skip=%zu\n", c->tcount, skip); - memmove(c->tbuf, c->tbuf + skip, c->tcount); - continue; - } - return ipos; - } - break; - } - d_print("%zu %zu\n", ipos, c->tcount); - return ipos; -} - -void cconv_process(struct cconv *c, const char *input, size_t len) -{ - if (c->consumed > 0) { - size_t fill = c->opos - c->consumed; - memmove(c->obuf, c->obuf + c->consumed, fill); - c->opos = fill; - c->consumed = 0; - } - - if (c->tcount > 0) { - size_t ipos = convert_incomplete(c, input, len); - input += ipos; - len -= ipos; - } - - char *ib = (char *)input; - size_t ic = len; - while (ic > 0) { - size_t skip; - - switch (xiconv(c, &ib, &ic)) { - case EINVAL: - // Incomplete character at end of input buffer. - if (ic < sizeof(c->tbuf)) { - memcpy(c->tbuf, ib, ic); - c->tcount = ic; - } else { - // FIXME - } - ic = 0; - break; - case EILSEQ: - // Invalid multibyte sequence. - skip = handle_invalid(c, ib, ic); - ic -= skip; - ib += skip; - break; - } - } -} - -struct cconv *cconv_to_utf8(const char *encoding) -{ - iconv_t cd = iconv_open("UTF-8", encoding); - if (cd == (iconv_t)-1) { - return NULL; - } - struct cconv *c = create(cd); - memcpy(c->rbuf, replacement, sizeof(replacement)); - c->rcount = sizeof(replacement); - c->char_size = encoding_char_size(encoding); - return c; -} - -struct cconv *cconv_from_utf8(const char *encoding) -{ - iconv_t cd = iconv_open(encoding, "UTF-8"); - if (cd == (iconv_t)-1) { - return NULL; - } - struct cconv *c = create(cd); - encode_replacement(c); - return c; -} - -void cconv_flush(struct cconv *c) -{ - if (c->tcount > 0) { - // Replace incomplete character at end of input buffer. - d_print("incomplete character at EOF\n"); - add_replacement(c); - c->tcount = 0; - } -} - -size_t cconv_nr_errors(const struct cconv *c) -{ - return c->errors; -} - -char *cconv_consume_line(struct cconv *c, size_t *len) -{ - char *line = c->obuf + c->consumed; - char *nl = memchr(line, '\n', c->opos - c->consumed); - - if (nl == NULL) { - *len = 0; - return NULL; - } - - size_t n = nl - line + 1; - c->consumed += n; - *len = n; - return line; -} - -char *cconv_consume_all(struct cconv *c, size_t *len) -{ - char *buf = c->obuf + c->consumed; - *len = c->opos - c->consumed; - c->consumed = c->opos; - return buf; -} - -void cconv_free(struct cconv *c) -{ - iconv_close(c->cd); - free(c->obuf); - free(c); -} - -bool encoding_supported_by_iconv(const char *encoding) -{ - iconv_t cd = iconv_open("UTF-8", encoding); - if (cd == (iconv_t) -1) { - return false; - } - iconv_close(cd); - return true; -} - -#else // Not using iconv -- replace conversion routines with stubs - -DISABLE_WARNING("-Wunused-parameter") - -bool encoding_supported_by_iconv(const char *encoding) -{ - return false; -} - -struct cconv *cconv_to_utf8(const char *encoding) -{ - errno = EILSEQ; - return NULL; -} - -struct cconv *cconv_from_utf8(const char *encoding) -{ - errno = EILSEQ; - return NULL; -} - -#define FAIL() BUG("unsupported"); fatal_error(__func__, ENOTSUP) - -void cconv_process(struct cconv *c, const char *input, size_t len) {FAIL();} -void cconv_flush(struct cconv *c) {FAIL();} -size_t cconv_nr_errors(const struct cconv *c) {FAIL();} -char *cconv_consume_line(struct cconv *c, size_t *len) {FAIL();} -char *cconv_consume_all(struct cconv *c, size_t *len) {FAIL();} -void cconv_free(struct cconv *c) {FAIL();} - -#endif diff -Nru dte-1.9.1/src/encoding/convert.h dte-1.10/src/encoding/convert.h --- dte-1.9.1/src/encoding/convert.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/convert.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -#ifndef ENCODING_CONVERT_H -#define ENCODING_CONVERT_H - -#include -#include - -struct cconv; - -struct cconv *cconv_to_utf8(const char *encoding); -struct cconv *cconv_from_utf8(const char *encoding); -void cconv_process(struct cconv *c, const char *input, size_t len); -void cconv_flush(struct cconv *c); -size_t cconv_nr_errors(const struct cconv *c); -char *cconv_consume_line(struct cconv *c, size_t *len); -char *cconv_consume_all(struct cconv *c, size_t *len); -void cconv_free(struct cconv *c); - -bool encoding_supported_by_iconv(const char *encoding); - -#endif diff -Nru dte-1.9.1/src/encoding/decoder.c dte-1.10/src/encoding/decoder.c --- dte-1.9.1/src/encoding/decoder.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/decoder.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,197 +0,0 @@ -#include -#include "decoder.h" -#include "convert.h" -#include "../editor.h" -#include "../util/hashset.h" -#include "../util/utf8.h" -#include "../util/xmalloc.h" - -static bool fill(FileDecoder *dec) -{ - size_t icount = dec->isize - dec->ipos; - - // Smaller than cconv.obuf to make realloc less likely - size_t max = 7 * 1024; - - if (icount > max) { - icount = max; - } - - if (dec->ipos == dec->isize) { - return false; - } - - cconv_process(dec->cconv, dec->ibuf + dec->ipos, icount); - dec->ipos += icount; - if (dec->ipos == dec->isize) { - // Must be flushed after all input has been fed - cconv_flush(dec->cconv); - } - return true; -} - -static bool decode_and_read_line(FileDecoder *dec, char **linep, size_t *lenp) -{ - char *line; - size_t len; - - while (1) { - line = cconv_consume_line(dec->cconv, &len); - if (line) { - break; - } - - if (!fill(dec)) { - break; - } - } - - if (line) { - // Newline not wanted - len--; - } else { - line = cconv_consume_all(dec->cconv, &len); - if (len == 0) { - return false; - } - } - - *linep = line; - *lenp = len; - return true; -} - -static bool read_utf8_line(FileDecoder *dec, char **linep, size_t *lenp) -{ - char *line = (char *)dec->ibuf + dec->ipos; - const char *nl = memchr(line, '\n', dec->isize - dec->ipos); - size_t len; - - if (nl) { - len = nl - line; - dec->ipos += len + 1; - } else { - len = dec->isize - dec->ipos; - if (len == 0) { - return false; - } - dec->ipos += len; - } - - *linep = line; - *lenp = len; - return true; -} - -static int set_encoding(FileDecoder *dec, const char *encoding) -{ - if (strcmp(encoding, "UTF-8") == 0) { - dec->read_line = read_utf8_line; - } else { - dec->cconv = cconv_to_utf8(encoding); - if (dec->cconv == NULL) { - return -1; - } - dec->read_line = decode_and_read_line; - } - dec->encoding = str_intern(encoding); - return 0; -} - -static bool detect(FileDecoder *dec, const unsigned char *line, size_t len) -{ - for (size_t i = 0; i < len; i++) { - if (line[i] >= 0x80) { - size_t idx = i; - CodePoint u = u_get_nonascii(line, len, &idx); - const char *encoding; - if (u_is_unicode(u)) { - encoding = "UTF-8"; - } else if (editor.term_utf8) { - if (dec->isize <= (32 * 1024 * 1024)) { - // If locale is UTF-8 but file doesn't contain valid - // UTF-8 and is also fairly small, just assume latin1 - encoding = "ISO-8859-1"; - } else { - // Large files are likely binary; just decode as - // UTF-8 to avoid costly charset conversion - encoding = "UTF-8"; - } - } else { - // Assume encoding is same as locale - encoding = editor.charset.name; - } - if (set_encoding(dec, encoding)) { - // FIXME: error message? - set_encoding(dec, "UTF-8"); - } - return true; - } - } - - // ASCII - return false; -} - -static bool detect_and_read_line(FileDecoder *dec, char **linep, size_t *lenp) -{ - char *line = (char *)dec->ibuf + dec->ipos; - const char *nl = memchr(line, '\n', dec->isize - dec->ipos); - size_t len; - - if (nl) { - len = nl - line; - } else { - len = dec->isize - dec->ipos; - if (len == 0) { - return false; - } - } - - if (detect(dec, line, len)) { - // Encoding detected - return dec->read_line(dec, linep, lenp); - } - - // Only ASCII so far - dec->ipos += len; - if (nl) { - dec->ipos++; - } - *linep = line; - *lenp = len; - return true; -} - -FileDecoder *new_file_decoder ( - const char *encoding, - const unsigned char *buf, - size_t size -) { - FileDecoder *dec = xnew0(FileDecoder, 1); - dec->ibuf = buf; - dec->isize = size; - dec->read_line = detect_and_read_line; - - if (encoding) { - if (set_encoding(dec, encoding)) { - free_file_decoder(dec); - return NULL; - } - } - - return dec; -} - -void free_file_decoder(FileDecoder *dec) -{ - if (dec->cconv != NULL) { - cconv_free(dec->cconv); - } - free(dec); -} - -bool file_decoder_read_line(FileDecoder *dec, char **linep, size_t *lenp) -{ - return dec->read_line(dec, linep, lenp); -} diff -Nru dte-1.9.1/src/encoding/decoder.h dte-1.10/src/encoding/decoder.h --- dte-1.9.1/src/encoding/decoder.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/decoder.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,19 +0,0 @@ -#ifndef ENCODING_DECODER_H -#define ENCODING_DECODER_H - -#include -#include - -typedef struct FileDecoder { - const char *encoding; - const unsigned char *ibuf; - ssize_t ipos, isize; - struct cconv *cconv; - bool (*read_line)(struct FileDecoder *dec, char **linep, size_t *lenp); -} FileDecoder; - -FileDecoder *new_file_decoder(const char *encoding, const unsigned char *buf, size_t size); -void free_file_decoder(FileDecoder *dec); -bool file_decoder_read_line(FileDecoder *dec, char **line, size_t *len); - -#endif diff -Nru dte-1.9.1/src/encoding/encoder.c dte-1.10/src/encoding/encoder.c --- dte-1.9.1/src/encoding/encoder.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/encoder.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,72 +0,0 @@ -#include -#include -#include "encoder.h" -#include "convert.h" -#include "../util/utf8.h" -#include "../util/xmalloc.h" -#include "../util/xreadwrite.h" - -FileEncoder *new_file_encoder(const Encoding *encoding, LineEndingType nls, int fd) -{ - FileEncoder *enc = xnew0(FileEncoder, 1); - enc->nls = nls; - enc->fd = fd; - - if (encoding->type != UTF8) { - enc->cconv = cconv_from_utf8(encoding->name); - if (enc->cconv == NULL) { - free(enc); - return NULL; - } - } - - return enc; -} - -void free_file_encoder(FileEncoder *enc) -{ - if (enc->cconv != NULL) { - cconv_free(enc->cconv); - } - free(enc->nbuf); - free(enc); -} - -static size_t unix_to_dos ( - FileEncoder *enc, - const unsigned char *buf, - size_t size -) { - if (enc->nsize < size * 2) { - enc->nsize = size * 2; - xrenew(enc->nbuf, enc->nsize); - } - size_t d = 0; - for (size_t s = 0; s < size; s++) { - unsigned char ch = buf[s]; - if (ch == '\n') { - enc->nbuf[d++] = '\r'; - } - enc->nbuf[d++] = ch; - } - return d; -} - -// NOTE: buf must contain whole characters! -ssize_t file_encoder_write ( - FileEncoder *enc, - const unsigned char *buf, - size_t size -) { - if (enc->nls == NEWLINE_DOS) { - size = unix_to_dos(enc, buf, size); - buf = enc->nbuf; - } - if (enc->cconv == NULL) { - return xwrite(enc->fd, buf, size); - } - cconv_process(enc->cconv, buf, size); - cconv_flush(enc->cconv); - buf = cconv_consume_all(enc->cconv, &size); - return xwrite(enc->fd, buf, size); -} diff -Nru dte-1.9.1/src/encoding/encoder.h dte-1.10/src/encoding/encoder.h --- dte-1.9.1/src/encoding/encoder.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/encoder.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,25 +0,0 @@ -#ifndef ENCODING_ENCODER_H -#define ENCODING_ENCODER_H - -#include -#include "../util/macros.h" -#include "../encoding/encoding.h" - -typedef enum { - NEWLINE_UNIX, - NEWLINE_DOS, -} LineEndingType; - -typedef struct { - struct cconv *cconv; - unsigned char *nbuf; - size_t nsize; - LineEndingType nls; - int fd; -} FileEncoder; - -FileEncoder *new_file_encoder(const Encoding *encoding, LineEndingType nls, int fd) NONNULL_ARGS; -void free_file_encoder(FileEncoder *enc); -ssize_t file_encoder_write(FileEncoder *enc, const unsigned char *buf, size_t size) NONNULL_ARGS; - -#endif diff -Nru dte-1.9.1/src/encoding/encoding.c dte-1.10/src/encoding/encoding.c --- dte-1.9.1/src/encoding/encoding.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/encoding.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,87 +0,0 @@ -#include -#include "encoding.h" -#include "../util/ascii.h" -#include "../util/hashset.h" -#include "../util/xmalloc.h" - -static const char encoding_names[][16] = { - [UTF8] = "UTF-8", - [UTF16] = "UTF-16", - [UTF16BE] = "UTF-16BE", - [UTF16LE] = "UTF-16LE", - [UTF32] = "UTF-32", - [UTF32BE] = "UTF-32BE", - [UTF32LE] = "UTF-32LE", -}; - -static_assert(ARRAY_COUNT(encoding_names) == NR_ENCODING_TYPES - 1); - -static const struct { - const char alias[8]; - EncodingType encoding; -} encoding_aliases[] = { - {"UTF8", UTF8}, - {"UTF16", UTF16}, - {"UTF16BE", UTF16BE}, - {"UTF16LE", UTF16LE}, - {"UTF32", UTF32}, - {"UTF32BE", UTF32BE}, - {"UTF32LE", UTF32LE}, - {"UCS2", UTF16}, - {"UCS-2", UTF16}, - {"UCS-2BE", UTF16BE}, - {"UCS-2LE", UTF16LE}, - {"UCS4", UTF32}, - {"UCS-4", UTF32}, - {"UCS-4BE", UTF32BE}, - {"UCS-4LE", UTF32LE}, -}; - -EncodingType lookup_encoding(const char *name) -{ - for (size_t i = 0; i < ARRAY_COUNT(encoding_names); i++) { - if (ascii_streq_icase(name, encoding_names[i])) { - return (EncodingType) i; - } - } - for (size_t i = 0; i < ARRAY_COUNT(encoding_aliases); i++) { - if (ascii_streq_icase(name, encoding_aliases[i].alias)) { - return encoding_aliases[i].encoding; - } - } - return UNKNOWN_ENCODING; -} - -static const char *encoding_type_to_string(EncodingType type) -{ - if (type < NR_ENCODING_TYPES && type != UNKNOWN_ENCODING) { - return str_intern(encoding_names[type]); - } - return NULL; -} - -Encoding encoding_from_name(const char *name) -{ - const EncodingType type = lookup_encoding(name); - const char *normalized_name; - if (type == UNKNOWN_ENCODING) { - char *upper = xstrdup_toupper(name); - normalized_name = str_intern(upper); - free(upper); - } else { - normalized_name = encoding_type_to_string(type); - } - - return (Encoding) { - .type = type, - .name = normalized_name - }; -} - -Encoding encoding_from_type(EncodingType type) -{ - return (Encoding) { - .type = type, - .name = encoding_type_to_string(type) - }; -} diff -Nru dte-1.9.1/src/encoding/encoding.h dte-1.10/src/encoding/encoding.h --- dte-1.9.1/src/encoding/encoding.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/encoding/encoding.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -#ifndef ENCODING_ENCODING_H -#define ENCODING_ENCODING_H - -#include "../util/macros.h" - -typedef enum { - UTF8, - UTF16, - UTF16BE, - UTF16LE, - UTF32, - UTF32BE, - UTF32LE, - UNKNOWN_ENCODING, - NR_ENCODING_TYPES, - - // This value is used by the "open" command to instruct other - // routines that no specific encoding was requested and that - // it should be detected instead. It is always replaced by - // some other value by the time a file is successfully opened. - ENCODING_AUTODETECT -} EncodingType; - -typedef struct { - EncodingType type; - // An interned encoding name compatible with iconv_open(3) - const char *name; -} Encoding; - -Encoding encoding_from_type(EncodingType type); -Encoding encoding_from_name(const char *name) NONNULL_ARGS; -EncodingType lookup_encoding(const char *name) NONNULL_ARGS; - -#endif diff -Nru dte-1.9.1/src/encoding.c dte-1.10/src/encoding.c --- dte-1.9.1/src/encoding.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/encoding.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,131 @@ +#include +#include "encoding.h" +#include "util/ascii.h" +#include "util/bsearch.h" +#include "util/debug.h" +#include "util/hashset.h" +#include "util/str-util.h" + +typedef struct { + const char alias[8]; + EncodingType encoding; +} EncodingAlias; + +static const char encoding_names[][16] = { + [UTF8] = "UTF-8", + [UTF16BE] = "UTF-16BE", + [UTF16LE] = "UTF-16LE", + [UTF32BE] = "UTF-32BE", + [UTF32LE] = "UTF-32LE", +}; + +static const EncodingAlias encoding_aliases[] = { + {"UCS-2", UTF16BE}, + {"UCS-2BE", UTF16BE}, + {"UCS-2LE", UTF16LE}, + {"UCS-4", UTF32BE}, + {"UCS-4BE", UTF32BE}, + {"UCS-4LE", UTF32LE}, + {"UCS2", UTF16BE}, + {"UCS4", UTF32BE}, + {"UTF-16", UTF16BE}, + {"UTF-32", UTF32BE}, + {"UTF16", UTF16BE}, + {"UTF16BE", UTF16BE}, + {"UTF16LE", UTF16LE}, + {"UTF32", UTF32BE}, + {"UTF32BE", UTF32BE}, + {"UTF32LE", UTF32LE}, + {"UTF8", UTF8}, +}; + +static const ByteOrderMark boms[NR_ENCODING_TYPES] = { + [UTF8] = {{0xef, 0xbb, 0xbf}, 3}, + [UTF16BE] = {{0xfe, 0xff}, 2}, + [UTF16LE] = {{0xff, 0xfe}, 2}, + [UTF32BE] = {{0x00, 0x00, 0xfe, 0xff}, 4}, + [UTF32LE] = {{0xff, 0xfe, 0x00, 0x00}, 4}, +}; + +UNITTEST { + CHECK_BSEARCH_ARRAY(encoding_aliases, alias, ascii_strcmp_icase); +} + +EncodingType lookup_encoding(const char *name) +{ + static_assert(ARRAY_COUNT(encoding_names) == NR_ENCODING_TYPES - 1); + for (size_t i = 0; i < ARRAY_COUNT(encoding_names); i++) { + if (ascii_streq_icase(name, encoding_names[i])) { + return (EncodingType) i; + } + } + + static_assert(offsetof(EncodingAlias, alias) == 0); + const EncodingAlias *a = BSEARCH ( + name, + encoding_aliases, + (CompareFunction)ascii_strcmp_icase + ); + return a ? a->encoding : UNKNOWN_ENCODING; +} + +static const char *encoding_type_to_string(EncodingType type) +{ + if (type < NR_ENCODING_TYPES && type != UNKNOWN_ENCODING) { + return str_intern(encoding_names[type]); + } + return NULL; +} + +Encoding encoding_from_name(const char *name) +{ + const EncodingType type = lookup_encoding(name); + const char *normalized_name; + if (type == UNKNOWN_ENCODING) { + char upper[256]; + size_t n; + for (n = 0; n < sizeof(upper) && name[n]; n++) { + upper[n] = ascii_toupper(name[n]); + } + normalized_name = mem_intern(upper, n); + } else { + normalized_name = encoding_type_to_string(type); + } + return (Encoding) { + .type = type, + .name = normalized_name + }; +} + +Encoding encoding_from_type(EncodingType type) +{ + return (Encoding) { + .type = type, + .name = encoding_type_to_string(type) + }; +} + +EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size) +{ + // Skip exhaustive checks if there's clearly no BOM + if (size < 2 || ((unsigned int)buf[0]) - 1 < 0xEE) { + return UNKNOWN_ENCODING; + } + + // Iterate array backwards to ensure UTF32LE is checked before UTF16LE + for (int i = NR_ENCODING_TYPES - 1; i >= 0; i--) { + const unsigned int bom_len = boms[i].len; + if (bom_len > 0 && size >= bom_len && mem_equal(buf, boms[i].bytes, bom_len)) { + return (EncodingType) i; + } + } + return UNKNOWN_ENCODING; +} + +const ByteOrderMark *get_bom_for_encoding(EncodingType encoding) +{ + static_assert(ARRAY_COUNT(boms) == NR_ENCODING_TYPES); + BUG_ON(encoding >= ARRAY_COUNT(boms)); + const ByteOrderMark *bom = &boms[encoding]; + return bom->len ? bom : NULL; +} diff -Nru dte-1.9.1/src/encoding.h dte-1.10/src/encoding.h --- dte-1.9.1/src/encoding.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/encoding.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,40 @@ +#ifndef ENCODING_ENCODING_H +#define ENCODING_ENCODING_H + +#include +#include "util/macros.h" + +typedef enum { + UTF8, + UTF16BE, + UTF16LE, + UTF32BE, + UTF32LE, + UNKNOWN_ENCODING, + NR_ENCODING_TYPES, + + // This value is used by the "open" command to instruct other + // routines that no specific encoding was requested and that + // it should be detected instead. It is always replaced by + // some other value by the time a file is successfully opened. + ENCODING_AUTODETECT +} EncodingType; + +typedef struct { + EncodingType type; + // An interned encoding name compatible with iconv_open(3) + const char *name; +} Encoding; + +typedef struct { + const unsigned char bytes[4]; + unsigned int len; +} ByteOrderMark; + +Encoding encoding_from_type(EncodingType type); +Encoding encoding_from_name(const char *name) NONNULL_ARGS; +EncodingType lookup_encoding(const char *name) NONNULL_ARGS; +EncodingType detect_encoding_from_bom(const unsigned char *buf, size_t size); +const ByteOrderMark *get_bom_for_encoding(EncodingType encoding); + +#endif diff -Nru dte-1.9.1/src/env.c dte-1.10/src/env.c --- dte-1.9.1/src/env.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/env.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,101 +0,0 @@ -#include "env.h" -#include "completion.h" -#include "debug.h" -#include "editor.h" -#include "error.h" -#include "selection.h" -#include "util/str-util.h" -#include "util/xmalloc.h" -#include "window.h" - -typedef struct { - const char *name; - char *(*expand)(void); -} BuiltinEnv; - -static char *expand_dte_home(void) -{ - return xstrdup(editor.user_config_dir); -} - -static char *expand_file(void) -{ - if (editor.status != EDITOR_RUNNING) { - return NULL; - } - const char *filename = buffer->abs_filename; - return filename ? xstrdup(filename) : NULL; -} - -static char *expand_filetype(void) -{ - if (editor.status != EDITOR_RUNNING) { - return NULL; - } - return xstrdup(buffer->options.filetype); -} - -static char *expand_lineno(void) -{ - if (editor.status != EDITOR_RUNNING) { - return NULL; - } - return xasprintf("%ld", view->cy + 1); -} - -static char *expand_word(void) -{ - if (editor.status != EDITOR_RUNNING) { - return NULL; - } - size_t size; - char *str = view_get_selection(view, &size); - if (str != NULL) { - xrenew(str, size + 1); - str[size] = '\0'; - } else { - str = view_get_word_under_cursor(view); - if (str == NULL) { - str = NULL; - } - } - return str; -} - -static char *expand_pkgdatadir(void) -{ - error_msg("The $PKGDATADIR variable was removed in dte v1.4"); - return NULL; -} - -static const BuiltinEnv builtin[] = { - {"PKGDATADIR", expand_pkgdatadir}, - {"DTE_HOME", expand_dte_home}, - {"FILE", expand_file}, - {"FILETYPE", expand_filetype}, - {"LINENO", expand_lineno}, - {"WORD", expand_word}, -}; - -void collect_builtin_env(const char *prefix) -{ - for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { - const char *name = builtin[i].name; - if (str_has_prefix(name, prefix)) { - add_completion(xstrdup(name)); - } - } -} - -// Returns NULL only if name isn't in builtin array -bool expand_builtin_env(const char *name, char **value) -{ - for (size_t i = 0; i < ARRAY_COUNT(builtin); i++) { - const BuiltinEnv *be = &builtin[i]; - if (streq(be->name, name)) { - *value = be->expand(); - return true; - } - } - return false; -} diff -Nru dte-1.9.1/src/env.h dte-1.10/src/env.h --- dte-1.9.1/src/env.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/env.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -#ifndef ENV_H -#define ENV_H - -#include - -void collect_builtin_env(const char *prefix); -bool expand_builtin_env(const char *name, char **value); - -#endif diff -Nru dte-1.9.1/src/error.c dte-1.10/src/error.c --- dte-1.9.1/src/error.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/error.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,16 @@ +#include +#include #include +#include #include "error.h" +#include "command/run.h" #include "config.h" #include "editor.h" +#include "util/debug.h" static char error_buf[256]; static unsigned int nr_errors; static bool msg_is_error; -static bool supress_errors; void clear_error(void) { @@ -15,36 +19,24 @@ void error_msg(const char *format, ...) { - if (supress_errors) { - return; - } - + const char *cmd = current_command ? current_command->name : NULL; + const char *file = current_config.file; + const int line = current_config.line; + const size_t size = sizeof(error_buf); int pos = 0; - if (config_file) { - if (current_command) { - pos = snprintf ( - error_buf, - sizeof(error_buf), - "%s:%d: %s: ", - config_file, - config_line, - current_command->name - ); - } else { - pos = snprintf ( - error_buf, - sizeof(error_buf), - "%s:%d: ", - config_file, - config_line - ); - } + + if (file && cmd) { + pos = snprintf(error_buf, size, "%s:%d: %s: ", file, line, cmd); + } else if (file) { + pos = snprintf(error_buf, size, "%s:%d: ", file, line); + } else if (cmd) { + pos = snprintf(error_buf, size, "%s: ", cmd); } - if (pos >= 0 && pos < (sizeof(error_buf) - 3)) { + if (pos >= 0 && pos < (size - 3)) { va_list ap; va_start(ap, format); - vsnprintf(error_buf + pos, sizeof(error_buf) - pos, format, ap); + vsnprintf(error_buf + pos, size - pos, format, ap); va_end(ap); } @@ -55,6 +47,13 @@ fputs(error_buf, stderr); fputc('\n', stderr); } + + DEBUG_LOG("%s", error_buf); +} + +void perror_msg(const char *prefix) +{ + error_msg("%s: %s", prefix, strerror(errno)); } void info_msg(const char *format, ...) @@ -76,13 +75,3 @@ { return nr_errors; } - -void suppress_error_msg(void) -{ - supress_errors = true; -} - -void unsuppress_error_msg(void) -{ - supress_errors = false; -} diff -Nru dte-1.9.1/src/error.h dte-1.10/src/error.h --- dte-1.9.1/src/error.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/error.h 2021-04-03 21:08:53.000000000 +0000 @@ -5,11 +5,10 @@ #include "util/macros.h" void error_msg(const char *format, ...) PRINTF(1); +void perror_msg(const char *prefix) NONNULL_ARGS; void info_msg(const char *format, ...) PRINTF(1); void clear_error(void); const char *get_msg(bool *is_error) NONNULL_ARGS; unsigned int get_nr_errors(void); -void suppress_error_msg(void); -void unsuppress_error_msg(void); #endif diff -Nru dte-1.9.1/src/file-history.c dte-1.10/src/file-history.c --- dte-1.9.1/src/file-history.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/file-history.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,76 +1,81 @@ #include -#include +#include #include -#include -#include +#include #include "file-history.h" #include "error.h" #include "util/path.h" -#include "util/ptr-array.h" +#include "util/hashmap.h" #include "util/readfile.h" +#include "util/str-util.h" #include "util/strtonum.h" -#include "util/wbuf.h" #include "util/xmalloc.h" -#include "util/xsnprintf.h" +#include "util/xstdio.h" -typedef struct { - unsigned long row, col; - size_t filename_len; - char filename[]; -} HistoryEntry; - -static PointerArray history = PTR_ARRAY_INIT; - -#define max_history_size 500 +typedef struct FileHistoryEntry { + struct FileHistoryEntry *next; + struct FileHistoryEntry *prev; + char *filename; + unsigned long row; + unsigned long col; +} FileHistoryEntry; -static bool entry_match(const HistoryEntry *e, const char *filename, size_t len) -{ - return len == e->filename_len && memcmp(filename, e->filename, len) == 0; -} +typedef struct { + HashMap entries; + FileHistoryEntry *first; + FileHistoryEntry *last; +} FileHistory; + +enum { + MAX_ENTRIES = 512 +}; -static ssize_t lookup_entry_index(const char *filename, size_t filename_len) -{ - for (size_t i = 0, n = history.count; i < n; i++) { - const HistoryEntry *e = history.ptrs[i]; - if (entry_match(e, filename, filename_len)) { - return i; - } - } - return -1; -} +static FileHistory history; void add_file_history(unsigned long row, unsigned long col, const char *filename) { - const size_t filename_len = strlen(filename); - const ssize_t idx = lookup_entry_index(filename, filename_len); - if (idx >= 0) { - HistoryEntry *e = history.ptrs[idx]; - ptr_array_remove_idx(&history, (size_t)idx); - if (row > 1 || col > 1) { + HashMap *map = &history.entries; + FileHistoryEntry *e = hashmap_get(map, filename); + if (e) { + if (e == history.last) { e->row = row; e->col = col; - // Re-insert at end of array - ptr_array_add(&history, e); + return; + } + e->next->prev = e->prev; + if (unlikely(e == history.first)) { + history.first = e->next; } else { - free(e); + e->prev->next = e->next; } - return; - } - - if (row <= 1 && col <= 1) { - return; - } - - while (history.count >= max_history_size) { - free(ptr_array_remove_idx(&history, 0)); + } else { + if (map->count == MAX_ENTRIES) { + // History is full; recycle the oldest entry + FileHistoryEntry *old_first = history.first; + FileHistoryEntry *new_first = old_first->next; + new_first->prev = NULL; + history.first = new_first; + e = hashmap_remove(map, old_first->filename); + BUG_ON(e != old_first); + } else { + e = xnew(FileHistoryEntry, 1); + } + e->filename = xstrdup(filename); + hashmap_insert(map, e->filename, e); } - HistoryEntry *e = xmalloc(sizeof(HistoryEntry) + filename_len); + // Insert the entry at the end of the list + FileHistoryEntry *old_last = history.last; + e->next = NULL; + e->prev = old_last; e->row = row; e->col = col; - e->filename_len = filename_len; - memcpy(e->filename, filename, filename_len); - ptr_array_add(&history, e); + history.last = e; + if (likely(old_last)) { + old_last->next = e; + } else { + history.first = e; + } } static bool parse_ulong(const char **strp, unsigned long *valp) @@ -96,9 +101,8 @@ return; } - const size_t size = ssize; - size_t pos = 0; - while (pos < size) { + hashmap_init(&history.entries, MAX_ENTRIES); + for (size_t pos = 0, size = ssize; pos < size; ) { const char *line = buf_next_line(buf, &pos, size); unsigned long row, col; if (!parse_ulong(&line, &row) || row == 0 || *line++ != ' ') { @@ -119,33 +123,26 @@ void save_file_history(const char *filename) { - WriteBuffer buf = WBUF_INIT; - buf.fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (buf.fd < 0) { + FILE *f = xfopen(filename, "w", O_CLOEXEC, 0666); + if (!f) { error_msg("Error creating %s: %s", filename, strerror(errno)); return; } - for (size_t i = 0, n = history.count; i < n; i++) { - const HistoryEntry *e = history.ptrs[i]; - wbuf_need_space(&buf, 64); - buf.fill += xsnprintf(buf.buf + buf.fill, 64, "%lu %lu ", e->row, e->col); - wbuf_write(&buf, e->filename, e->filename_len); - wbuf_write_ch(&buf, '\n'); + for (const FileHistoryEntry *e = history.first; e; e = e->next) { + fprintf(f, "%lu %lu %s\n", e->row, e->col, e->filename); } - wbuf_flush(&buf); - close(buf.fd); + fclose(f); } bool find_file_in_history(const char *filename, unsigned long *row, unsigned long *col) { - const ssize_t idx = lookup_entry_index(filename, strlen(filename)); - if (idx >= 0) { - const HistoryEntry *e = history.ptrs[idx]; - *row = e->row; - *col = e->col; - return true; - } - return false; + const FileHistoryEntry *e = hashmap_get(&history.entries, filename); + if (!e) { + return false; + } + *row = e->row; + *col = e->col; + return true; } diff -Nru dte-1.9.1/src/file-option.c dte-1.10/src/file-option.c --- dte-1.9.1/src/file-option.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/file-option.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,13 +1,12 @@ #include #include "file-option.h" +#include "command/serialize.h" #include "editorconfig/editorconfig.h" #include "options.h" #include "regexp.h" -#include "spawn.h" +#include "util/debug.h" #include "util/ptr-array.h" #include "util/str-util.h" -#include "util/string-view.h" -#include "util/strtonum.h" #include "util/xmalloc.h" typedef struct { @@ -33,17 +32,15 @@ const char *path = b->abs_filename; char cwd[8192]; - if (path == NULL) { + if (!path) { // For buffers with no associated filename, use a dummy path of - // "$PWD/_", to obtain generic settings for the working directory + // "$PWD/__", to obtain generic settings for the working directory // or the user's default settings. - if (getcwd(cwd, sizeof(cwd) - 2) == NULL) { + static const char suffix[] = "/__"; + if (!getcwd(cwd, sizeof(cwd) - sizeof(suffix))) { return; } - size_t n = strlen(cwd); - cwd[n++] = '/'; - cwd[n++] = '_'; - cwd[n] = '\0'; + memcpy(cwd + strlen(cwd), suffix, sizeof(suffix)); path = cwd; } @@ -86,24 +83,34 @@ void set_file_options(Buffer *b) { - for (size_t i = 0; i < file_options.count; i++) { + for (size_t i = 0, n = file_options.count; i < n; i++) { const FileOption *opt = file_options.ptrs[i]; - if (opt->type == FILE_OPTIONS_FILETYPE) { + switch (opt->type) { + case FILE_OPTIONS_FILETYPE: if (streq(opt->type_or_pattern, b->options.filetype)) { set_options(opt->strs); } - continue; - } - const char *f = b->abs_filename; - if (f && regexp_match_nosub(opt->type_or_pattern, f, strlen(f))) { - set_options(opt->strs); + break; + case FILE_OPTIONS_FILENAME: + if (b->abs_filename) { + const StringView f = strview_from_cstring(b->abs_filename); + if (regexp_match_nosub(opt->type_or_pattern, &f)) { + set_options(opt->strs); + } + } + break; + default: + BUG("unhandled file option type"); } } } void add_file_options(FileOptionType type, char *to, char **strs) { - if (type == FILE_OPTIONS_FILENAME && !regexp_is_valid(to, REG_NEWLINE)) { + if ( + (type == FILE_OPTIONS_FILENAME && !regexp_is_valid(to, REG_NEWLINE)) + || (type == FILE_OPTIONS_FILETYPE && to[0] == '\0') + ) { free(to); free_string_array(strs); return; @@ -113,5 +120,29 @@ opt->type = type; opt->type_or_pattern = to; opt->strs = strs; - ptr_array_add(&file_options, opt); + ptr_array_append(&file_options, opt); +} + +void dump_file_options(String *buf) +{ + for (size_t i = 0, n = file_options.count; i < n; i++) { + const FileOption *opt = file_options.ptrs[i]; + const char *tp = opt->type_or_pattern; + char **strs = opt->strs; + string_append_literal(buf, "option "); + if (opt->type == FILE_OPTIONS_FILENAME) { + string_append_literal(buf, "-r "); + } + if (str_has_prefix(tp, "-") || string_array_contains_prefix(strs, "-")) { + string_append_literal(buf, "-- "); + } + string_append_escaped_arg(buf, tp, true); + for (size_t j = 0; strs[j]; j += 2) { + string_append_byte(buf, ' '); + string_append_cstring(buf, strs[j]); + string_append_byte(buf, ' '); + string_append_escaped_arg(buf, strs[j + 1], true); + } + string_append_byte(buf, '\n'); + } } diff -Nru dte-1.9.1/src/file-option.h dte-1.10/src/file-option.h --- dte-1.9.1/src/file-option.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/file-option.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,15 +2,17 @@ #define FILE_OPTION_H #include "buffer.h" +#include "util/macros.h" +#include "util/string.h" typedef enum { FILE_OPTIONS_FILENAME, FILE_OPTIONS_FILETYPE, } FileOptionType; -void set_file_options(Buffer *b); -void add_file_options(FileOptionType type, char *to, char **strs); - -void set_editorconfig_options(Buffer *b); +void add_file_options(FileOptionType type, char *to, char **strs) NONNULL_ARGS; +void set_file_options(Buffer *b) NONNULL_ARGS; +void set_editorconfig_options(Buffer *b) NONNULL_ARGS; +void dump_file_options(String *buf); #endif diff -Nru dte-1.9.1/src/filetype/basenames.c dte-1.10/src/filetype/basenames.c --- dte-1.9.1/src/filetype/basenames.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/basenames.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,120 +1,114 @@ -typedef struct { - const char key[16]; - const FileTypeEnum filetype; -} FileBasenameMap; - -static const FileBasenameMap basenames[] = { - {"APKBUILD", SHELL}, - {"BSDmakefile", MAKE}, - {"BUILD.bazel", PYTHON}, - {"CMakeLists.txt", CMAKE}, - {"COMMIT_EDITMSG", GITCOMMIT}, - {"Capfile", RUBY}, - {"Cargo.lock", TOML}, - {"Dockerfile", DOCKER}, - {"Doxyfile", CONFIG}, - {"GNUmakefile", MAKE}, - {"Gemfile", RUBY}, - {"Gemfile.lock", RUBY}, - {"Kbuild", MAKE}, - {"Makefile", MAKE}, - {"Makefile.am", MAKE}, - {"Makefile.in", MAKE}, - {"PKGBUILD", SHELL}, - {"Project.ede", EMACSLISP}, - {"Rakefile", RUBY}, - {"Vagrantfile", RUBY}, - {"build.gradle", GRADLE}, - {"config.ld", LUA}, - {"configure.ac", M4}, - {"fstab", CONFIG}, - {"git-rebase-todo", GITREBASE}, - {"hosts", CONFIG}, - {"ip6tables.rules", CONFIG}, - {"iptables.rules", CONFIG}, - {"krb5.conf", INI}, - {"makefile", MAKE}, - {"menu.lst", CONFIG}, - {"meson.build", MESON}, - {"mimeapps.list", INI}, - {"mkinitcpio.conf", SHELL}, - {"nginx.conf", NGINX}, - {"pacman.conf", INI}, - {"robots.txt", ROBOTSTXT}, - {"rockspec.in", LUA}, - {"terminalrc", INI}, - {"texmf.cnf", TEXMFCNF}, - {"yum.conf", INI}, -}; - -// These are matched with or without a leading dot -static const FileBasenameMap dotfiles[] = { - {"Xresources", XRESOURCES}, - {"bash_logout", SHELL}, - {"bash_profile", SHELL}, - {"bashrc", SHELL}, - {"clang-format", YAML}, - {"clang-tidy", YAML}, - {"cshrc", SHELL}, - {"drirc", XML}, - {"editorconfig", INI}, - {"emacs", EMACSLISP}, - {"gemrc", YAML}, - {"gitattributes", CONFIG}, - {"gitconfig", INI}, - {"gitmodules", INI}, - {"gnus", EMACSLISP}, - {"indent.pro", INDENT}, - {"inputrc", CONFIG}, - {"jshintrc", JSON}, - {"lcovrc", CONFIG}, - {"luacheckrc", LUA}, - {"luacov", LUA}, - {"profile", SHELL}, - {"xinitrc", SHELL}, - {"xprofile", SHELL}, - {"xserverrc", SHELL}, - {"zlogin", SHELL}, - {"zlogout", SHELL}, - {"zprofile", SHELL}, - {"zshenv", SHELL}, - {"zshrc", SHELL}, +static const struct FileBasenameMap { + const char name[16]; + const uint8_t filetype; // FileTypeEnum + bool dotfile; // If true, name is matched with or without a leading dot +} basenames[] = { + {"APKBUILD", SH, false}, + {"BSDmakefile", MAKE, false}, + {"BUILD.bazel", PYTHON, false}, + {"CMakeLists.txt", CMAKE, false}, + {"COMMIT_EDITMSG", GITCOMMIT, false}, + {"Capfile", RUBY, false}, + {"Cargo.lock", TOML, false}, + {"Dockerfile", DOCKER, false}, + {"Doxyfile", CONFIG, false}, + {"GNUmakefile", MAKE, false}, + {"Gemfile", RUBY, false}, + {"Gemfile.lock", RUBY, false}, + {"Kbuild", MAKE, false}, + {"Makefile", MAKE, false}, + {"Makefile.am", MAKE, false}, + {"Makefile.in", MAKE, false}, + {"PKGBUILD", SH, false}, + {"Project.ede", LISP, false}, + {"Rakefile", RUBY, false}, + {"Vagrantfile", RUBY, false}, + {"XCompose", CONFIG, true}, + {"Xresources", XRESOURCES, true}, + {"bash_logout", SH, true}, + {"bash_profile", SH, true}, + {"bashrc", SH, true}, + {"build.gradle", GRADLE, false}, + {"clang-format", YAML, true}, + {"clang-tidy", YAML, true}, + {"config.ld", LUA, false}, + {"configure.ac", M4, false}, + {"cshrc", SH, true}, + {"curlrc", CONFIG, true}, + {"dircolors", CONFIG, true}, + {"drirc", XML, true}, + {"editorconfig", INI, true}, + {"emacs", LISP, true}, + {"fstab", CONFIG, false}, + {"gdbinit", CONFIG, true}, + {"gemrc", YAML, true}, + {"git-rebase-todo", GITREBASE, false}, + {"gitattributes", CONFIG, true}, + {"gitconfig", INI, true}, + {"gitmodules", INI, true}, + {"gnus", LISP, true}, + {"hosts", CONFIG, false}, + {"indent.pro", INDENT, true}, + {"inputrc", CONFIG, true}, + {"ip6tables.rules", CONFIG, false}, + {"iptables.rules", CONFIG, false}, + {"jshintrc", JSON, true}, + {"krb5.conf", INI, false}, + {"lcovrc", CONFIG, true}, + {"lesskey", CONFIG, true}, + {"luacheckrc", LUA, true}, + {"luacov", LUA, true}, + {"makefile", MAKE, false}, + {"menu.lst", CONFIG, false}, + {"meson.build", MESON, false}, + {"mimeapps.list", INI, false}, + {"mkinitcpio.conf", SH, false}, + {"muttrc", CONFIG, true}, + {"nanorc", CONFIG, true}, + {"nginx.conf", NGINX, false}, + {"pacman.conf", INI, false}, + {"profile", SH, true}, + {"robots.txt", ROBOTSTXT, false}, + {"rockspec.in", LUA, false}, + {"shellcheckrc", CONFIG, true}, + {"sxhkdrc", CONFIG, true}, + {"terminalrc", INI, false}, + {"texmf.cnf", TEXMFCNF, false}, + {"tigrc", CONFIG, true}, + {"tmux.conf", TMUX, true}, + {"xinitrc", SH, true}, + {"xprofile", SH, true}, + {"xserverrc", SH, true}, + {"yum.conf", INI, false}, + {"zlogin", SH, true}, + {"zlogout", SH, true}, + {"zprofile", SH, true}, + {"zshenv", SH, true}, + {"zshrc", SH, true}, }; -static FileTypeEnum filetype_from_basename(StringView sv) +static FileTypeEnum filetype_from_basename(StringView name) { - switch (sv.length) { - case 5: case 6: case 7: case 8: - case 9: case 10: case 11: case 12: - case 13: case 14: case 15: - break; - case 17: - return memcmp(sv.data, "meson_options.txt", 17) ? NONE : MESON; - default: + if (name.length < 4) { return NONE; } - const FileBasenameMap *e = bsearch ( - &sv, - basenames, - ARRAY_COUNT(basenames), - sizeof(basenames[0]), - ft_compare - ); - if (e) { - return e->filetype; + if (name.length >= ARRAY_COUNT(basenames[0].name)) { + if (strview_equal_cstring(&name, "meson_options.txt")) { + return MESON; + } + return NONE; } - if (sv.data[0] == '.') { - sv.data++; - sv.length--; + bool dot = (name.data[0] == '.'); + if (dot) { + strview_remove_prefix(&name, 1); } - e = bsearch ( - &sv, - dotfiles, - ARRAY_COUNT(dotfiles), - sizeof(dotfiles[0]), - ft_compare - ); - return e ? e->filetype : NONE; + + const struct FileBasenameMap *e = BSEARCH(&name, basenames, ft_compare); + if (e && (!dot || e->dotfile)) { + return e->filetype; + } + + return NONE; } diff -Nru dte-1.9.1/src/filetype/extensions.c dte-1.10/src/filetype/extensions.c --- dte-1.9.1/src/filetype/extensions.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/extensions.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,27 +1,27 @@ static const struct FileExtensionMap { - const char ext[12]; - const FileTypeEnum filetype; + const char ext[11]; + const uint8_t filetype; // FileTypeEnum } extensions[] = { {"ada", ADA}, {"adb", ADA}, {"ads", ADA}, {"asd", LISP}, - {"asm", ASSEMBLY}, + {"asm", ASM}, {"auk", AWK}, {"automount", INI}, {"awk", AWK}, - {"bash", SHELL}, - {"bat", BATCHFILE}, + {"bash", SH}, + {"bat", BATCH}, {"bbl", TEX}, {"bib", BIBTEX}, - {"btm", BATCHFILE}, + {"btm", BATCH}, {"c++", CPLUSPLUS}, {"cc", CPLUSPLUS}, {"cl", LISP}, {"clj", CLOJURE}, {"cls", TEX}, {"cmake", CMAKE}, - {"cmd", BATCHFILE}, + {"cmd", BATCH}, {"coffee", COFFEESCRIPT}, {"cpp", CPLUSPLUS}, {"cr", RUBY}, @@ -39,17 +39,22 @@ {"docker", DOCKER}, {"dot", DOT}, {"doxy", CONFIG}, - {"dterc", DTERC}, + {"dterc", DTE}, + {"dts", DEVICETREE}, + {"dtsi", DEVICETREE}, {"dtx", TEX}, - {"ebuild", SHELL}, + {"ebuild", SH}, {"el", LISP}, - {"emacs", EMACSLISP}, + {"emacs", LISP}, {"eml", MAIL}, {"eps", POSTSCRIPT}, + {"erl", ERLANG}, + {"ex", ELIXIR}, + {"exs", ELIXIR}, {"flatpakref", INI}, - {"flatpakrepo", INI}, {"frag", GLSL}, {"gawk", AWK}, + {"gcode", GCODE}, {"gemspec", RUBY}, {"geojson", JSON}, {"glsl", GLSL}, @@ -65,6 +70,7 @@ {"gv", DOT}, {"hh", CPLUSPLUS}, {"hpp", CPLUSPLUS}, + {"hrl", ERLANG}, {"hs", HASKELL}, {"htm", HTML}, {"html", HTML}, @@ -72,9 +78,13 @@ {"ini", INI}, {"ins", TEX}, {"java", JAVA}, + {"jl", JULIA}, {"js", JAVASCRIPT}, {"json", JSON}, - {"ksh", SHELL}, + {"ksh", SH}, + {"kt", KOTLIN}, + {"kts", KOTLIN}, + {"latex", TEX}, {"lsp", LISP}, {"ltx", TEX}, {"lua", LUA}, @@ -87,6 +97,8 @@ {"mk", MAKE}, {"mkd", MARKDOWN}, {"mkdn", MARKDOWN}, + {"ml", OCAML}, + {"mli", OCAML}, {"moon", MOONSCRIPT}, {"mount", INI}, {"nawk", AWK}, @@ -95,7 +107,9 @@ {"nim", NIM}, {"ninja", NINJA}, {"nix", NIX}, + {"opml", XML}, {"page", XML}, + {"pas", PASCAL}, {"patch", DIFF}, {"path", INI}, {"pc", PKGCONFIG}, @@ -110,30 +124,37 @@ {"pp", RUBY}, {"proto", PROTOBUF}, {"ps", POSTSCRIPT}, + {"ps1", POWERSHELL}, + {"psd1", POWERSHELL}, + {"psm1", POWERSHELL}, {"py", PYTHON}, {"py3", PYTHON}, {"rake", RUBY}, {"rb", RUBY}, {"rdf", XML}, + {"re", C}, // re2c {"rkt", RACKET}, {"rktd", RACKET}, {"rktl", RACKET}, {"rockspec", LUA}, + {"roff", ROFF}, {"rs", RUST}, - {"rst", RESTRUCTUREDTEXT}, + {"rst", RST}, {"scala", SCALA}, {"scm", SCHEME}, {"scss", SCSS}, {"sed", SED}, {"service", INI}, - {"sh", SHELL}, + {"sh", SH}, {"sld", SCHEME}, {"slice", INI}, {"sls", SCHEME}, {"socket", INI}, + {"spec", RPMSPEC}, {"sql", SQL}, {"ss", SCHEME}, {"sty", TEX}, + {"supp", CONFIG}, {"svg", XML}, {"target", INI}, {"tcl", TCL}, @@ -143,7 +164,9 @@ {"timer", INI}, {"toml", TOML}, {"topojson", JSON}, + {"tr", ROFF}, {"ts", TYPESCRIPT}, + {"tsv", TSV}, {"tsx", TYPESCRIPT}, {"ui", XML}, {"vala", VALA}, @@ -165,14 +188,17 @@ {"yaml", YAML}, {"yml", YAML}, {"zig", ZIG}, - {"zsh", SHELL}, + {"zsh", SH}, }; -static FileTypeEnum filetype_from_extension(const StringView sv) +static FileTypeEnum filetype_from_extension(const StringView ext) { - switch (sv.length) { - case 1: - switch (sv.data[0]) { + if (ext.length >= sizeof(extensions[0].ext)) { + return NONE; + } + + if (ext.length == 1) { + switch (ext.data[0]) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': @@ -182,28 +208,16 @@ case 'C': case 'H': return CPLUSPLUS; case 'S': case 's': - return ASSEMBLY; + return ASM; case 'd': return D; case 'l': return LEX; - case 'm': return OBJECTIVEC; + case 'm': return OBJC; case 'v': return VERILOG; case 'y': return YACC; } return NONE; - case 2: case 3: case 4: case 5: - case 6: case 7: case 8: case 9: - case 10: case 11: - break; - default: - return NONE; } - const struct FileExtensionMap *e = bsearch ( - &sv, - extensions, - ARRAY_COUNT(extensions), - sizeof(extensions[0]), - ft_compare - ); + const struct FileExtensionMap *e = BSEARCH(&ext, extensions, ft_compare); return e ? e->filetype : NONE; } diff -Nru dte-1.9.1/src/filetype/ignored-exts.c dte-1.10/src/filetype/ignored-exts.c --- dte-1.9.1/src/filetype/ignored-exts.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/ignored-exts.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,33 +1,31 @@ -static bool is_ignored_extension(const char *s, size_t len) +static const char ignored_extensions[][12] = { + "bak", + "dpkg-backup", + "dpkg-bak", + "dpkg-dist", + "dpkg-new", + "dpkg-old", + "dpkg-remove", + "dpkg-tmp", + "new", + "old", + "orig", + "pacnew", + "pacorig", + "pacsave", + "rpmnew", + "rpmorig", + "rpmsave", + "ucf-dist", + "ucf-new", + "ucf-old", +}; + +static bool is_ignored_extension(const StringView sv) { - switch (len) { - case 3: - switch (s[0]) { - case 'b': return !memcmp(s, "bak", len); - case 'n': return !memcmp(s, "new", len); - case 'o': return !memcmp(s, "old", len); - } - break; - case 4: return !memcmp(s, "orig", len); - case 6: - switch (s[0]) { - case 'p': return !memcmp(s, "pacnew", len); - case 'r': return !memcmp(s, "rpmnew", len); - } - break; - case 7: - switch (s[0]) { - case 'r': return !memcmp(s, "rpmsave", len); - case 'p': - switch (s[3]) { - case 'o': return !memcmp(s, "pacorig", len); - case 's': return !memcmp(s, "pacsave", len); - } - break; - } - break; - case 8: return !memcmp(s, "dpkg-old", len); - case 9: return !memcmp(s, "dpkg-dist", len); + if (sv.length < 3 || sv.length >= sizeof(ignored_extensions[0])) { + return false; } - return false; + const char *e = BSEARCH(&sv, ignored_extensions, ft_compare); + return e != NULL; } diff -Nru dte-1.9.1/src/filetype/interpreters.c dte-1.10/src/filetype/interpreters.c --- dte-1.9.1/src/filetype/interpreters.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/interpreters.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,74 +2,77 @@ const char key[8]; const FileTypeEnum filetype; } interpreters[] = { - {"ash", SHELL}, + {"ash", SH}, {"awk", AWK}, - {"bash", SHELL}, + {"bash", SH}, {"bigloo", SCHEME}, {"ccl", LISP}, {"chicken", SCHEME}, {"clisp", LISP}, {"coffee", COFFEESCRIPT}, {"crystal", RUBY}, - {"dash", SHELL}, + {"dart", DART}, + {"dash", SH}, {"ecl", LISP}, + {"elixir", ELIXIR}, + {"escript", ERLANG}, {"gawk", AWK}, + {"gjs", JAVASCRIPT}, {"gmake", MAKE}, {"gnuplot", GNUPLOT}, {"groovy", GROOVY}, {"gsed", SED}, {"guile", SCHEME}, {"jruby", RUBY}, - {"ksh", SHELL}, + {"julia", JULIA}, + {"ksh", SH}, {"lisp", LISP}, {"lua", LUA}, {"luajit", LUA}, {"macruby", RUBY}, {"make", MAKE}, {"mawk", AWK}, - {"mksh", SHELL}, + {"mksh", SH}, {"moon", MOONSCRIPT}, {"nawk", AWK}, {"node", JAVASCRIPT}, - {"pdksh", SHELL}, + {"ocaml", OCAML}, + {"pdksh", SH}, {"perl", PERL}, {"php", PHP}, + {"pwsh", POWERSHELL}, {"python", PYTHON}, {"r6rs", SCHEME}, {"racket", SCHEME}, {"rake", RUBY}, {"ruby", RUBY}, + {"runghc", HASKELL}, {"sbcl", LISP}, + {"scala", SCALA}, + {"scheme", SCHEME}, {"sed", SED}, - {"sh", SHELL}, + {"sh", SH}, {"tcc", C}, {"tclsh", TCL}, {"wish", TCL}, - {"zsh", SHELL}, + {"zsh", SH}, }; -static FileTypeEnum filetype_from_interpreter(const StringView sv) +static FileTypeEnum filetype_from_interpreter(const StringView name) { - switch (sv.length) { - case 2: case 3: case 4: - case 5: case 6: case 7: - break; - case 10: - switch (sv.data[0]) { - case 'o': return memcmp(sv.data, "openrc-run", 10) ? NONE : SHELL; - case 'r': return memcmp(sv.data, "runhaskell", 10) ? NONE : HASKELL; + if (name.length < 2) { + return NONE; + } + + if (name.length >= ARRAY_COUNT(interpreters[0].key)) { + if (strview_equal_cstring(&name, "openrc-run")) { + return SH; + } else if (strview_equal_cstring(&name, "runhaskell")) { + return HASKELL; } - // Fallthrough - default: return NONE; } - const struct FileInterpreterMap *e = bsearch ( - &sv, - interpreters, - ARRAY_COUNT(interpreters), - sizeof(interpreters[0]), - ft_compare - ); + const struct FileInterpreterMap *e = BSEARCH(&name, interpreters, ft_compare); return e ? e->filetype : NONE; } diff -Nru dte-1.9.1/src/filetype/names.c dte-1.10/src/filetype/names.c --- dte-1.9.1/src/filetype/names.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/names.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,10 @@ typedef enum { - NONE = 0, ADA, - ASSEMBLY, + ASM, AWK, - BATCHFILE, + BATCH, BIBTEX, - C, - CPLUSPLUS, + C, CPLUSPLUS = C, CLOJURE, CMAKE, COFFEESCRIPT, @@ -16,11 +14,14 @@ CSV, D, DART, + DEVICETREE, DIFF, DOCKER, DOT, - DTERC, - EMACSLISP, + DTE, + ELIXIR, + ERLANG, + GCODE, GETTEXT, GITCOMMIT, GITREBASE, @@ -37,6 +38,8 @@ JAVA, JAVASCRIPT, JSON, + JULIA, + KOTLIN, LEX, LISP, LUA, @@ -50,30 +53,37 @@ NIM, NINJA, NIX, - OBJECTIVEC, + NONE, + OBJC, + OCAML, + PASCAL, PERL, PHP, PKGCONFIG, POSTSCRIPT, + POWERSHELL, PROTOBUF, PYTHON, RACKET, ROBOTSTXT, ROFF, - RESTRUCTUREDTEXT, + RPMSPEC, + RST, RUBY, RUST, SCALA, SCHEME, SCSS, SED, - SHELL, + SH, SQL, TCL, TEX, TEXINFO, TEXMFCNF, + TMUX, TOML, + TSV, TYPESCRIPT, VALA, VCARD, @@ -89,14 +99,12 @@ } FileTypeEnum; static const char builtin_filetype_names[NR_BUILTIN_FILETYPES][16] = { - [NONE] = "none", [ADA] = "ada", - [ASSEMBLY] = "asm", + [ASM] = "asm", [AWK] = "awk", - [BATCHFILE] = "batch", + [BATCH] = "batch", [BIBTEX] = "bibtex", [C] = "c", - [CPLUSPLUS] = "c", [CLOJURE] = "clojure", [CMAKE] = "cmake", [COFFEESCRIPT] = "coffeescript", @@ -104,13 +112,16 @@ [CSHARP] = "csharp", [CSS] = "css", [CSV] = "csv", - [DART] = "dart", [D] = "d", + [DART] = "dart", + [DEVICETREE] = "devicetree", [DIFF] = "diff", [DOCKER] = "docker", [DOT] = "dot", - [DTERC] = "dte", - [EMACSLISP] = "elisp", + [DTE] = "dte", + [ELIXIR] = "elixir", + [ERLANG] = "erlang", + [GCODE] = "gcode", [GETTEXT] = "gettext", [GITCOMMIT] = "gitcommit", [GITREBASE] = "gitrebase", @@ -127,6 +138,8 @@ [JAVA] = "java", [JAVASCRIPT] = "javascript", [JSON] = "json", + [JULIA] = "julia", + [KOTLIN] = "kotlin", [LEX] = "lex", [LISP] = "lisp", [LUA] = "lua", @@ -140,30 +153,37 @@ [NIM] = "nim", [NINJA] = "ninja", [NIX] = "nix", - [OBJECTIVEC] = "objc", + [NONE] = "none", + [OBJC] = "objc", + [OCAML] = "ocaml", + [PASCAL] = "pascal", [PERL] = "perl", [PHP] = "php", [PKGCONFIG] = "pkg-config", [POSTSCRIPT] = "postscript", + [POWERSHELL] = "powershell", [PROTOBUF] = "protobuf", [PYTHON] = "python", [RACKET] = "racket", - [RESTRUCTUREDTEXT] = "rst", [ROBOTSTXT] = "robotstxt", [ROFF] = "roff", + [RPMSPEC] = "rpmspec", + [RST] = "rst", [RUBY] = "ruby", [RUST] = "rust", [SCALA] = "scala", [SCHEME] = "scheme", [SCSS] = "scss", [SED] = "sed", - [SHELL] = "sh", + [SH] = "sh", [SQL] = "sql", [TCL] = "tcl", [TEXINFO] = "texinfo", [TEXMFCNF] = "texmfcnf", [TEX] = "tex", + [TMUX] = "tmux", [TOML] = "toml", + [TSV] = "tsv", [TYPESCRIPT] = "typescript", [VALA] = "vala", [VCARD] = "vcard", @@ -176,22 +196,3 @@ [YAML] = "yaml", [ZIG] = "zig", }; - -UNITTEST { - BUG_ON(strcmp(builtin_filetype_names[0], "none") != 0); - BUG_ON(strcmp(builtin_filetype_names[1], "ada") != 0); - for (size_t i = 2; i < ARRAY_COUNT(builtin_filetype_names); i++) { - const char *const name = builtin_filetype_names[i]; - if (name[0] == '\0') { - BUG("missing value at builtin_filetype_names[%zu]", i); - } - // Ensure fixed-size char arrays are null-terminated - BUG_ON(memchr(name, '\0', sizeof(builtin_filetype_names[0])) == NULL); - // Ensure FileTypeEnum values are sorted according to their name - // string (to allow name -> value lookups via binary search). - const char *const prev = builtin_filetype_names[i - 1]; - if (memcmp(name, prev, 16) < 0) { - BUG("Filetype names not in sorted order: %s, %s", prev, name); - } - } -} diff -Nru dte-1.9.1/src/filetype/signatures.c dte-1.10/src/filetype/signatures.c --- dte-1.9.1/src/filetype/signatures.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype/signatures.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,49 +1,37 @@ typedef struct { - const unsigned char NONSTRING bytes[11]; - uint8_t length; - FileTypeEnum filetype; + const char bytes[11]; + uint8_t filetype; // FileTypeEnum } FileSignatureMap; -#define SIG(str, ft) { \ - .bytes = str, \ - .length = STRLEN(str), \ - .filetype = ft \ -} - static const FileSignatureMap signatures[] = { - SIG("bytes, sig->length)) { - return sig->filetype; + if (strview_has_prefix(&line, signatures[i].bytes)) { + return signatures[i].filetype; } } diff -Nru dte-1.9.1/src/filetype.c dte-1.10/src/filetype.c --- dte-1.9.1/src/filetype.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,14 +1,16 @@ -#include +#include #include "filetype.h" -#include "debug.h" +#include "command/serialize.h" +#include "completion.h" #include "error.h" #include "regexp.h" #include "util/ascii.h" +#include "util/bsearch.h" +#include "util/debug.h" #include "util/macros.h" #include "util/path.h" #include "util/ptr-array.h" #include "util/str-util.h" -#include "util/string-view.h" #include "util/xmalloc.h" static int ft_compare(const void *key, const void *elem) @@ -30,12 +32,20 @@ #include "filetype/ignored-exts.c" #include "filetype/signatures.c" +UNITTEST { + CHECK_BSEARCH_ARRAY(basenames, name, strcmp); + CHECK_BSEARCH_ARRAY(extensions, ext, strcmp); + CHECK_BSEARCH_ARRAY(interpreters, key, strcmp); + CHECK_BSEARCH_STR_ARRAY(builtin_filetype_names, strcmp); + CHECK_BSEARCH_STR_ARRAY(ignored_extensions, strcmp); +} + // Filetypes dynamically added via the `ft` command. // Not grouped by name to make it possible to order them freely. typedef struct { - FileDetectionType type; - uint8_t name_len; - uint8_t str_len; + FileDetectionType type : 8; + unsigned int name_len : 8; + unsigned int str_len : 16; char data[]; // Contains name followed by str (both null-terminated) } UserFileTypeEntry; @@ -55,32 +65,25 @@ { const size_t name_len = strlen(name); const size_t str_len = strlen(str); - if (unlikely(name_len >= 256 || str_len >= 256)) { - error_msg("ft argument exceeds maximum length (255 bytes)"); + if (unlikely(name_len > 0xFF || str_len > 0xFFFF)) { + error_msg("ft argument exceeds maximum length"); return; } - regex_t re; - switch (type) { - case FT_CONTENT: - case FT_FILENAME: - if (!regexp_compile(&re, str, REG_NEWLINE | REG_NOSUB)) { + if (type == FT_CONTENT || type == FT_FILENAME) { + if (!regexp_is_valid(str, REG_NEWLINE)) { return; } - regfree(&re); - break; - default: - break; } const size_t data_len = name_len + str_len + 2; - UserFileTypeEntry *ft = xmalloc(sizeof(UserFileTypeEntry) + data_len); + UserFileTypeEntry *ft = xmalloc(sizeof(*ft) + data_len); ft->type = type; - ft->name_len = (uint8_t) name_len; - ft->str_len = (uint8_t) str_len; + ft->name_len = name_len; + ft->str_len = str_len; memcpy(ft->data, name, name_len + 1); memcpy(ft->data + name_len + 1, str, str_len + 1); - ptr_array_add(&filetypes, ft); + ptr_array_append(&filetypes, ft); } static StringView get_ext(const StringView filename) @@ -90,8 +93,8 @@ return ext; } - ext.data = string_view_memrchr(&filename, '.'); - if (ext.data == NULL) { + ext.data = strview_memrchr(&filename, '.'); + if (!ext.data) { return ext; } @@ -105,7 +108,7 @@ return ext; } - if (is_ignored_extension(ext.data, ext.length)) { + if (is_ignored_extension(ext)) { int idx = -2; while (ext.data + idx >= filename.data) { if (ext.data[idx] == '.') { @@ -157,25 +160,25 @@ { const char *str = ft_get_str(ft); const size_t len = (size_t)ft->str_len; - return sv.length > 0 && string_view_equal_strn(&sv, str, len); + return sv.length > 0 && strview_equal_strn(&sv, str, len); } static bool ft_regex_match(const UserFileTypeEntry *ft, const StringView sv) { const char *str = ft_get_str(ft); - return sv.length > 0 && regexp_match_nosub(str, sv.data, sv.length); + return sv.length > 0 && regexp_match_nosub(str, &sv); } HOT const char *find_ft(const char *filename, StringView line) { const char *b = filename ? path_basename(filename) : NULL; - const StringView base = string_view_from_cstring(b); + const StringView base = strview_from_cstring(b); const StringView ext = get_ext(base); - const StringView path = string_view_from_cstring(filename); + const StringView path = strview_from_cstring(filename); const StringView interpreter = get_interpreter(line); // Search user `ft` entries - for (size_t i = 0; i < filetypes.count; i++) { + for (size_t i = 0, n = filetypes.count; i < n; i++) { const UserFileTypeEntry *ft = filetypes.ptrs[i]; switch (ft->type) { case FT_EXTENSION: @@ -203,6 +206,8 @@ continue; } break; + default: + BUG("unhandled detection type"); } return ft_get_name(ft); } @@ -210,46 +215,56 @@ // Search built-in lookup tables if (interpreter.length) { FileTypeEnum ft = filetype_from_interpreter(interpreter); - if (ft) { + if (ft != NONE) { return builtin_filetype_names[ft]; } } if (base.length) { FileTypeEnum ft = filetype_from_basename(base); - if (ft) { + if (ft != NONE) { return builtin_filetype_names[ft]; } } if (line.length) { FileTypeEnum ft = filetype_from_signature(line); - if (ft) { + if (ft != NONE) { return builtin_filetype_names[ft]; } } if (ext.length) { FileTypeEnum ft = filetype_from_extension(ext); - if (ft) { + if (ft != NONE) { return builtin_filetype_names[ft]; } } - if (string_view_has_literal_prefix(&path, "/etc/default/")) { - return builtin_filetype_names[SHELL]; - } else if (string_view_has_literal_prefix(&path, "/etc/nginx/")) { + if (strview_has_prefix(&path, "/etc/default/")) { + return builtin_filetype_names[SH]; + } else if (strview_has_prefix(&path, "/etc/nginx/")) { return builtin_filetype_names[NGINX]; } - const StringView conf = STRING_VIEW("conf"); - if (string_view_equal(&ext, &conf)) { - if (string_view_has_literal_prefix(&path, "/etc/systemd/")) { + strview_trim_right(&line); + if (line.length >= 4) { + const char *s = line.data; + const size_t n = line.length; + if (s[0] == '[' && s[n - 1] == ']' && is_word_byte(s[1])) { + if (!strview_contains_char_type(&line, ASCII_CNTRL)) { + return builtin_filetype_names[INI]; + } + } + } + + if (strview_equal_cstring(&ext, "conf")) { + if (strview_has_prefix(&path, "/etc/systemd/")) { return builtin_filetype_names[INI]; } else if ( - string_view_has_literal_prefix(&path, "/etc/") - || string_view_has_literal_prefix(&path, "/usr/share/") - || string_view_has_literal_prefix(&path, "/usr/local/share/") + strview_has_prefix(&path, "/etc/") + || strview_has_prefix(&path, "/usr/share/") + || strview_has_prefix(&path, "/usr/local/share/") ) { return builtin_filetype_names[CONFIG]; } @@ -260,16 +275,62 @@ bool is_ft(const char *name) { - for (size_t i = 0; i < filetypes.count; i++) { + if (BSEARCH(name, builtin_filetype_names, (CompareFunction)strcmp)) { + return true; + } + + for (size_t i = 0, n = filetypes.count; i < n; i++) { const UserFileTypeEntry *ft = filetypes.ptrs[i]; if (streq(ft_get_name(ft), name)) { return true; } } + + return false; +} + +void collect_ft(const char *prefix) +{ for (size_t i = 0; i < ARRAY_COUNT(builtin_filetype_names); i++) { - if (streq(builtin_filetype_names[i], name)) { - return true; + const char *name = builtin_filetype_names[i]; + if (str_has_prefix(name, prefix)) { + add_completion(xstrdup(name)); } } - return false; + for (size_t i = 0, n = filetypes.count; i < n; i++) { + const UserFileTypeEntry *ft = filetypes.ptrs[i]; + const char *name = ft_get_name(ft); + if (str_has_prefix(name, prefix)) { + add_completion(xstrdup(name)); + } + } +} + +String dump_ft(void) +{ + static const char flags[][4] = { + [FT_EXTENSION] = "", + [FT_FILENAME] = "-f ", + [FT_CONTENT] = "-c ", + [FT_INTERPRETER] = "-i ", + [FT_BASENAME] = "-b ", + }; + + String s = string_new(4096); + for (size_t i = 0, n = filetypes.count; i < n; i++) { + const UserFileTypeEntry *ft = filetypes.ptrs[i]; + const char *name = ft_get_name(ft); + FileDetectionType type = ft->type; + BUG_ON(type >= ARRAY_COUNT(flags)); + string_append_literal(&s, "ft "); + string_append_cstring(&s, flags[type]); + if (unlikely(name[0] == '-')) { + string_append_cstring(&s, "-- "); + } + string_append_escaped_arg(&s, name, true); + string_append_byte(&s, ' '); + string_append_escaped_arg(&s, ft_get_str(ft), true); + string_append_byte(&s, '\n'); + } + return s; } diff -Nru dte-1.9.1/src/filetype.h dte-1.10/src/filetype.h --- dte-1.9.1/src/filetype.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/filetype.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,8 +2,8 @@ #define FILETYPE_H #include -#include #include "util/string-view.h" +#include "util/string.h" typedef enum { FT_EXTENSION, @@ -16,5 +16,7 @@ void add_filetype(const char *name, const char *str, FileDetectionType type); bool is_ft(const char *name); const char *find_ft(const char *filename, StringView line); +void collect_ft(const char *prefix); +String dump_ft(void); #endif diff -Nru dte-1.9.1/src/frame.c dte-1.10/src/frame.c --- dte-1.9.1/src/frame.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/frame.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,5 +1,5 @@ #include "frame.h" -#include "debug.h" +#include "util/debug.h" #include "util/xmalloc.h" #include "window.h" @@ -91,14 +91,14 @@ size_t count = f->frames.count; BUG_ON(count == 0); - int *size = xnew0(int, count); int *min = xnew(int, count); for (size_t i = 0; i < count; i++) { min[i] = get_min(f->frames.ptrs[i]); } - int q, r, used; + int *size = xnew0(int, count); int s = get_container_size(f); + int q, r, used; size_t n = count; // Consume q and r as equally as possible @@ -131,7 +131,7 @@ static void fix_size(const Frame *f) { size_t count = f->frames.count; - int *size = xnew0(int, count); + int *size = xnew(int, count); int *min = xnew(int, count); int total = 0; for (size_t i = 0; i < count; i++) { @@ -171,6 +171,7 @@ { const Frame *parent = f->parent; size_t idx = ptr_array_idx(&parent->frames, f); + BUG_ON(idx >= parent->frames.count); if (idx == parent->frames.count - 1) { f = parent->frames.ptrs[idx - 1]; } else { @@ -197,6 +198,7 @@ { const Frame *parent = f->parent; size_t idx = ptr_array_idx(&parent->frames, f); + BUG_ON(idx >= parent->frames.count); for (size_t i = idx + 1, n = parent->frames.count; i < n; i++) { count = sub(parent->frames.ptrs[i], count); @@ -248,8 +250,7 @@ static bool rightmost_frame(const Frame *f) { const Frame *parent = f->parent; - - if (parent == NULL) { + if (!parent) { return true; } if (!parent->vertical) { @@ -273,7 +274,7 @@ f->parent = parent; f->window = w; w->frame = f; - if (parent != NULL) { + if (parent) { BUG_ON(idx > parent->frames.count); ptr_array_insert(&parent->frames, f, idx); parent->window = NULL; @@ -342,7 +343,7 @@ void add_to_frame_size(Frame *f, ResizeDirection dir, int amount) { f = find_resizable(f, dir); - if (f == NULL) { + if (!f) { return; } @@ -358,7 +359,7 @@ void resize_frame(Frame *f, ResizeDirection dir, int size) { f = find_resizable(f, dir); - if (f == NULL) { + if (!f) { return; } @@ -394,7 +395,7 @@ { Frame *f = w->frame; Frame *parent = f->parent; - if (parent == NULL || parent->vertical != vertical) { + if (!parent || parent->vertical != vertical) { // Reparent w f->vertical = vertical; add_frame(f, w, 0); @@ -402,6 +403,7 @@ } size_t idx = ptr_array_idx(&parent->frames, w->frame); + BUG_ON(idx >= parent->frames.count); if (!before) { idx++; } @@ -418,20 +420,18 @@ // Doesn't really split root but adds new frame between root and its contents Frame *split_root(bool vertical, bool before) { - Frame *new_root, *f; - - new_root = new_frame(); + Frame *new_root = new_frame(); new_root->vertical = vertical; - f = new_frame(); + Frame *f = new_frame(); f->parent = new_root; f->window = new_window(); f->window->frame = f; if (before) { - ptr_array_add(&new_root->frames, f); - ptr_array_add(&new_root->frames, root_frame); + ptr_array_append(&new_root->frames, f); + ptr_array_append(&new_root->frames, root_frame); } else { - ptr_array_add(&new_root->frames, root_frame); - ptr_array_add(&new_root->frames, f); + ptr_array_append(&new_root->frames, root_frame); + ptr_array_append(&new_root->frames, f); } root_frame->parent = new_root; @@ -446,7 +446,7 @@ { f->parent = NULL; ptr_array_free_cb(&f->frames, FREE_FUNC(free_frame)); - if (f->window != NULL) { + if (f->window) { window_free(f->window); f->window = NULL; } @@ -456,8 +456,7 @@ void remove_frame(Frame *f) { Frame *parent = f->parent; - - if (parent == NULL) { + if (!parent) { free_frame(f); return; } @@ -474,6 +473,7 @@ c->h = parent->h; if (gp) { size_t idx = ptr_array_idx(&gp->frames, parent); + BUG_ON(idx >= gp->frames.count); gp->frames.ptrs[idx] = c; } else { root_frame = c; @@ -488,42 +488,56 @@ update_window_coordinates(); } -#ifdef DEBUG_FRAMES -static void debug_frame(const Frame *f, int level) +static void string_append_frame(String *str, const Frame *f, int level) { - d_print ( - "%*s%dx%d %d %d %zu\n", - level * 4, "", - f->w, f->h, - f->vertical, f->equal_size, - f->frames.count - ); + string_sprintf(str, "%*s%dx%d", level * 4, "", f->w, f->h); - if (f->window) { - d_print ( - "%*swindow %d,%d %dx%d\n", + const Window *w = f->window; + if (w) { + string_sprintf ( + str, "\n%*s%d,%d %dx%d %s\n", (level + 1) * 4, "", - f->window->x, - f->window->y, - f->window->w, - f->window->h + w->x, w->y, + w->w, w->h, + buffer_filename(w->view->buffer) ); + return; + } + + string_append_cstring(str, f->vertical ? " V" : " H"); + string_append_cstring(str, f->equal_size ? "\n" : " !\n"); + + const size_t n = f->frames.count; + BUG_ON(n == 0); + for (size_t i = 0; i < n; i++) { + const Frame *c = f->frames.ptrs[i]; + string_append_frame(str, c, level + 1); } +} +String dump_frames(void) +{ + String str = string_new(4096); + string_append_frame(&str, root_frame, 0); + return str; +} + +#if DEBUG >= 1 +static void debug_frame(const Frame *f) +{ BUG_ON(f->window && f->frames.count); if (f->window) { BUG_ON(f != f->window->frame); } - for (size_t i = 0, n = f->frames.count; i < n; i++) { const Frame *c = f->frames.ptrs[i]; BUG_ON(c->parent != f); - debug_frame(c, level + 1); + debug_frame(c); } } void debug_frames(void) { - debug_frame(root_frame, 0); + debug_frame(root_frame); } #endif diff -Nru dte-1.9.1/src/frame.h dte-1.10/src/frame.h --- dte-1.9.1/src/frame.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/frame.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,6 +3,7 @@ #include #include "util/ptr-array.h" +#include "util/string.h" typedef struct Frame { struct Frame *parent; @@ -35,8 +36,9 @@ Frame *split_frame(struct Window *w, bool vertical, bool before); Frame *split_root(bool vertical, bool before); void remove_frame(Frame *f); +String dump_frames(void); -#ifdef DEBUG_FRAMES +#if DEBUG >= 1 void debug_frames(void); #else static inline void debug_frames(void) {} diff -Nru dte-1.9.1/src/history.c dte-1.10/src/history.c --- dte-1.9.1/src/history.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/history.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,44 +1,73 @@ -#include -#include -#include +#include +#include +#include #include "history.h" #include "error.h" -#include "util/ptr-array.h" +#include "util/debug.h" #include "util/readfile.h" #include "util/str-util.h" -#include "util/wbuf.h" #include "util/xmalloc.h" +#include "util/xstdio.h" -// Add item to end of array -void history_add(PointerArray *history, const char *text, size_t max_entries) +void history_add(History *history, const char *text) { + BUG_ON(history->max_entries < 2); if (text[0] == '\0') { return; } - // Don't add identical entries - for (size_t i = 0, n = history->count; i < n; i++) { - if (streq(history->ptrs[i], text)) { - // Move identical entry to end - ptr_array_add(history, ptr_array_remove_idx(history, i)); + HashMap *map = &history->entries; + HistoryEntry *e = hashmap_get(map, text); + + if (e) { + if (e == history->last) { + // Existing entry already at end of list; nothing more to do return; } + // Remove existing entry from list + e->next->prev = e->prev; + if (unlikely(e == history->first)) { + history->first = e->next; + } else { + e->prev->next = e->next; + } + } else { + if (map->count == history->max_entries) { + // History is full; recycle oldest entry + HistoryEntry *old_first = history->first; + HistoryEntry *new_first = old_first->next; + new_first->prev = NULL; + history->first = new_first; + e = hashmap_remove(map, old_first->text); + BUG_ON(e != old_first); + } else { + e = xnew(HistoryEntry, 1); + } + e->text = xstrdup(text); + hashmap_insert(map, e->text, e); } - if (history->count == max_entries) { - free(ptr_array_remove_idx(history, 0)); + + // Insert entry at end of list + HistoryEntry *old_last = history->last; + e->next = NULL; + e->prev = old_last; + history->last = e; + if (likely(old_last)) { + old_last->next = e; + } else { + history->first = e; } - ptr_array_add(history, xstrdup(text)); } bool history_search_forward ( - const PointerArray *history, - ssize_t *pos, + const History *history, + const HistoryEntry **pos, const char *text ) { - ssize_t i = *pos; - while (--i >= 0) { - if (str_has_prefix(history->ptrs[i], text)) { - *pos = i; + const HistoryEntry *start = *pos ? (*pos)->prev : history->last; + for (const HistoryEntry *e = start; e; e = e->prev) { + if (str_has_prefix(e->text, text)) { + *pos = e; return true; } } @@ -46,22 +75,30 @@ } bool history_search_backward ( - const PointerArray *history, - ssize_t *pos, + const History *history, + const HistoryEntry **pos, const char *text ) { - ssize_t i = *pos; - while (++i < history->count) { - if (str_has_prefix(history->ptrs[i], text)) { - *pos = i; + const HistoryEntry *start = *pos ? (*pos)->next : history->first; + for (const HistoryEntry *e = start; e; e = e->next) { + if (str_has_prefix(e->text, text)) { + *pos = e; return true; } } return false; } -void history_load(PointerArray *history, const char *filename, size_t max_entries) +void history_load(History *history, const char *filename) { + BUG_ON(!history); + BUG_ON(!filename); + BUG_ON(history->filename); + BUG_ON(history->max_entries < 2); + + hashmap_init(&history->entries, history->max_entries); + history->filename = filename; + char *buf; const ssize_t ssize = read_file(filename, &buf); if (ssize < 0) { @@ -71,29 +108,30 @@ return; } - const size_t size = ssize; - size_t pos = 0; - while (pos < size) { - history_add(history, buf_next_line(buf, &pos, size), max_entries); + for (size_t pos = 0, size = ssize; pos < size; ) { + history_add(history, buf_next_line(buf, &pos, size)); } free(buf); } -void history_save(const PointerArray *history, const char *filename) +void history_save(const History *history) { - WriteBuffer buf = WBUF_INIT; - buf.fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (buf.fd < 0) { + const char *filename = history->filename; + if (!filename) { + return; + } + + FILE *f = xfopen(filename, "w", O_CLOEXEC, 0666); + if (!f) { error_msg("Error creating %s: %s", filename, strerror(errno)); return; } - for (size_t i = 0, n = history->count; i < n; i++) { - wbuf_write_str(&buf, history->ptrs[i]); - wbuf_write_ch(&buf, '\n'); + for (const HistoryEntry *e = history->first; e; e = e->next) { + fputs(e->text, f); + fputc('\n', f); } - wbuf_flush(&buf); - close(buf.fd); + fclose(f); } diff -Nru dte-1.9.1/src/history.h dte-1.10/src/history.h --- dte-1.9.1/src/history.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/history.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,16 +2,32 @@ #define HISTORY_H #include -#include -#include "util/ptr-array.h" +#include +#include "util/hashmap.h" -#define search_history_size 100 -#define command_history_size 500 +typedef struct HistoryEntry { + struct HistoryEntry *next; + struct HistoryEntry *prev; + char *text; +} HistoryEntry; -void history_add(PointerArray *history, const char *text, size_t max_entries); -bool history_search_forward(const PointerArray *history, ssize_t *pos, const char *text); -bool history_search_backward(const PointerArray *history, ssize_t *pos, const char *text); -void history_load(PointerArray *history, const char *filename, size_t max_entries); -void history_save(const PointerArray *history, const char *filename); +// This is a HashMap with a doubly-linked list running through the +// entries, in a way similar to the Java LinkedHashMap class. The +// HashMap allows duplicates to be found and re-inserted at the end +// of the list in O(1) time and the doubly-linked entries allow +// ordered traversal. +typedef struct { + const char *filename; + HashMap entries; + HistoryEntry *first; + HistoryEntry *last; + size_t max_entries; +} History; + +void history_add(History *history, const char *text); +bool history_search_forward(const History *history, const HistoryEntry **pos, const char *text); +bool history_search_backward(const History *history, const HistoryEntry **pos, const char *text); +void history_load(History *history, const char *filename); +void history_save(const History *history); #endif diff -Nru dte-1.9.1/src/indent.c dte-1.10/src/indent.c --- dte-1.9.1/src/indent.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/indent.c 2021-04-03 21:08:53.000000000 +0000 @@ -29,43 +29,46 @@ return str; } -static bool indent_inc(const char *line, size_t len) +static bool indent_inc(const StringView *line) { const char *re1 = "\\{[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; const char *re2 = "\\}[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$"; if (buffer->options.brace_indent) { - if (regexp_match_nosub(re1, line, len)) { + if (regexp_match_nosub(re1, line)) { return true; } - if (regexp_match_nosub(re2, line, len)) { + if (regexp_match_nosub(re2, line)) { return false; } } re1 = buffer->options.indent_regex; - return re1 && *re1 && regexp_match_nosub(re1, line, len); + return re1 && *re1 && regexp_match_nosub(re1, line); } -char *get_indent_for_next_line(const char *line, size_t len) +char *get_indent_for_next_line(const StringView *line) { IndentInfo info; - get_indent_info(line, len, &info); - if (indent_inc(line, len)) { + get_indent_info(line, &info); + if (indent_inc(line)) { size_t w = buffer->options.indent_width; info.width = (info.width + w) / w * w; } return make_indent(info.width); } -void get_indent_info(const char *buf, size_t len, IndentInfo *info) +void get_indent_info(const StringView *line, IndentInfo *info) { + const char *buf = line->data; + const size_t len = line->length; size_t spaces = 0; size_t tabs = 0; size_t pos = 0; MEMZERO(info); info->sane = true; + while (pos < len) { if (buf[pos] == ' ') { info->width++; @@ -79,11 +82,11 @@ } info->bytes++; pos++; - if (info->width % buffer->options.indent_width == 0 && info->sane) { info->sane = use_spaces_for_indent() ? !tabs : !spaces; } } + info->level = info->width / buffer->options.indent_width; info->wsonly = pos == len; } @@ -129,29 +132,28 @@ size_t get_indent_level_bytes_left(void) { - LineRef lr; - size_t cursor_offset = fetch_this_line(&view->cursor, &lr); + StringView line; + size_t cursor_offset = fetch_this_line(&view->cursor, &line); if (!cursor_offset) { return 0; } - ssize_t ibytes = get_current_indent_bytes(lr.line, cursor_offset); + ssize_t ibytes = get_current_indent_bytes(line.data, cursor_offset); return (ibytes < 0) ? 0 : (size_t)ibytes; } size_t get_indent_level_bytes_right(void) { - LineRef lr; - size_t cursor_offset = fetch_this_line(&view->cursor, &lr); - - ssize_t ibytes = get_current_indent_bytes(lr.line, cursor_offset); + StringView line; + size_t cursor_offset = fetch_this_line(&view->cursor, &line); + ssize_t ibytes = get_current_indent_bytes(line.data, cursor_offset); if (ibytes < 0) { return 0; } size_t tw = buffer->options.tab_width; size_t iwidth = 0; - for (size_t i = cursor_offset, n = lr.size; i < n; i++) { - switch (lr.line[i]) { + for (size_t i = cursor_offset, n = line.length; i < n; i++) { + switch (line.data[i]) { case '\t': iwidth = (iwidth + tw) / tw * tw; break; diff -Nru dte-1.9.1/src/indent.h dte-1.10/src/indent.h --- dte-1.9.1/src/indent.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/indent.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,6 +3,7 @@ #include #include +#include "util/string-view.h" typedef struct { // Size in bytes @@ -23,8 +24,8 @@ } IndentInfo; char *make_indent(size_t width); -char *get_indent_for_next_line(const char *line, size_t len); -void get_indent_info(const char *buf, size_t len, IndentInfo *info); +char *get_indent_for_next_line(const StringView *line); +void get_indent_info(const StringView *buf, IndentInfo *info); bool use_spaces_for_indent(void); size_t get_indent_level_bytes_left(void); size_t get_indent_level_bytes_right(void); diff -Nru dte-1.9.1/src/load-save.c dte-1.10/src/load-save.c --- dte-1.9.1/src/load-save.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/load-save.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,24 +1,22 @@ +#include "../build/feature.h" #include -#include #include +#include #include #include #include #include "load-save.h" #include "block.h" -#include "debug.h" +#include "convert.h" #include "editor.h" -#include "encoding/bom.h" -#include "encoding/convert.h" -#include "encoding/decoder.h" -#include "encoding/encoder.h" +#include "encoding.h" #include "error.h" +#include "util/debug.h" #include "util/macros.h" #include "util/path.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" -#include "util/xsnprintf.h" static void add_block(Buffer *b, Block *blk) { @@ -52,75 +50,48 @@ return blk; } -static int decode_and_add_blocks ( - Buffer *b, - const unsigned char *buf, - size_t size -) { +static bool decode_and_add_blocks(Buffer *b, const unsigned char *buf, size_t size) +{ EncodingType bom_type = detect_encoding_from_bom(buf, size); - - switch (b->encoding.type) { - case ENCODING_AUTODETECT: + EncodingType enc_type = b->encoding.type; + if (enc_type == ENCODING_AUTODETECT) { if (bom_type != UNKNOWN_ENCODING) { - BUG_ON(b->encoding.name != NULL); + BUG_ON(b->encoding.name); Encoding e = encoding_from_type(bom_type); - if (encoding_supported_by_iconv(e.name)) { - b->encoding = e; + if (conversion_supported_by_iconv(e.name, "UTF-8")) { + buffer_set_encoding(b, e); } else { - b->encoding = encoding_from_type(UTF8); + buffer_set_encoding(b, encoding_from_type(UTF8)); } } - break; - case UTF16: - switch (bom_type) { - case UTF16LE: - case UTF16BE: - b->encoding = encoding_from_type(bom_type); - break; - default: - // "open -e UTF-16" but incompatible or no BOM. - // Do what the user wants. Big-endian is default. - b->encoding = encoding_from_type(UTF16BE); - } - break; - case UTF32: - switch (bom_type) { - case UTF32LE: - case UTF32BE: - b->encoding = encoding_from_type(bom_type); - break; - default: - // "open -e UTF-32" but incompatible or no BOM. - // Do what the user wants. Big-endian is default. - b->encoding = encoding_from_type(UTF32BE); - } - break; - default: - break; } // Skip BOM only if it matches the specified file encoding. if (bom_type != UNKNOWN_ENCODING && bom_type == b->encoding.type) { - const size_t bom_len = get_bom_for_encoding(bom_type)->len; - buf += bom_len; - size -= bom_len; + const ByteOrderMark *bom = get_bom_for_encoding(bom_type); + if (bom) { + const size_t bom_len = bom->len; + buf += bom_len; + size -= bom_len; + b->bom = true; + } } FileDecoder *dec = new_file_decoder(b->encoding.name, buf, size); - if (dec == NULL) { - return -1; + if (!dec) { + return false; } - char *line; + const char *line; size_t len; if (file_decoder_read_line(dec, &line, &len)) { if (len && line[len - 1] == '\r') { - b->newline = NEWLINE_DOS; + b->crlf_newlines = true; len--; } Block *blk = add_utf8_line(b, NULL, line, len); while (file_decoder_read_line(dec, &line, &len)) { - if (b->newline == NEWLINE_DOS && len && line[len - 1] == '\r') { + if (b->crlf_newlines && len && line[len - 1] == '\r') { len--; } blk = add_utf8_line(b, blk, line, len); @@ -131,15 +102,12 @@ } if (b->encoding.type == ENCODING_AUTODETECT) { - if (dec->encoding) { - b->encoding = encoding_from_name(dec->encoding); - } else { - b->encoding = editor.charset; - } + const char *enc = file_decoder_get_encoding(dec); + buffer_set_encoding(b, enc ? encoding_from_name(enc) : editor.charset); } free_file_decoder(dec); - return 0; + return true; } static void fixup_blocks(Buffer *b) @@ -153,7 +121,7 @@ Block *blk = BLOCK(b->blocks.prev); if (blk->size && blk->data[blk->size - 1] != '\n') { if (blk->size == blk->alloc) { - blk->alloc = ROUND_UP(blk->size + 1, 64); + blk->alloc = round_size_to_next_multiple(blk->size + 1, 64); xrenew(blk->data, blk->alloc); } blk->data[blk->size++] = '\n'; @@ -163,112 +131,185 @@ } } -int read_blocks(Buffer *b, int fd) +static int xmadvise_sequential(void *addr, size_t len) { - size_t size = b->st.st_size; - size_t map_size = 64 * 1024; +#ifdef HAVE_POSIX_MADVISE + return posix_madvise(addr, len, POSIX_MADV_SEQUENTIAL); +#else + // "The posix_madvise() function shall have no effect on the semantics + // of access to memory in the specified range, although it may affect + // the performance of access". Ergo, doing nothing is a valid fallback. + (void)addr; + (void)len; + return 0; +#endif +} + +static void update_file_info(Buffer *b, const struct stat *st) +{ + b->file = (FileInfo) { + .size = st->st_size, + .mode = st->st_mode, + .gid = st->st_gid, + .uid = st->st_uid, + .dev = st->st_dev, + .ino = st->st_ino, + .mtime = st->st_mtime, + }; +} + +static bool buffer_stat(Buffer *b, const char *filename) +{ + struct stat st; + if (stat(filename, &st) != 0) { + return false; + } + update_file_info(b, &st); + return true; +} + +static bool buffer_fstat(Buffer *b, int fd) +{ + struct stat st; + if (fstat(fd, &st) != 0) { + return false; + } + update_file_info(b, &st); + return true; +} + +bool read_blocks(Buffer *b, int fd) +{ + const size_t map_size = 64 * 1024; + size_t size = b->file.size; unsigned char *buf = NULL; bool mapped = false; + bool ret = false; - // st_size is zero for some files in /proc. - // Can't mmap files in /proc and /sys. if (size >= map_size) { // NOTE: size must be greater than 0 buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (buf == MAP_FAILED) { - buf = NULL; - } else { + if (buf != MAP_FAILED) { + xmadvise_sequential(buf, size); mapped = true; + goto decode; } + buf = NULL; } - if (!mapped) { + if (likely(size > 0)) { + buf = malloc(size); + if (unlikely(!buf)) { + goto error; + } + ssize_t rc = xread(fd, buf, size); + if (unlikely(rc < 0)) { + goto error; + } + size = rc; + } else { + // st_size is zero for some files in /proc size_t alloc = map_size; + BUG_ON(!IS_POWER_OF_2(alloc)); + buf = malloc(alloc); + if (unlikely(!buf)) { + goto error; + } size_t pos = 0; - - buf = xmalloc(alloc); while (1) { ssize_t rc = xread(fd, buf + pos, alloc - pos); if (rc < 0) { - free(buf); - return -1; + goto error; } if (rc == 0) { break; } pos += rc; if (pos == alloc) { - alloc *= 2; - xrenew(buf, alloc); + size_t new_alloc = alloc << 1; + if (unlikely(alloc >= new_alloc)) { + errno = EOVERFLOW; + goto error; + } + alloc = new_alloc; + char *new_buf = realloc(buf, alloc); + if (unlikely(!new_buf)) { + goto error; + } + buf = new_buf; } } size = pos; } - int rc = decode_and_add_blocks(b, buf, size); +decode: + ret = decode_and_add_blocks(b, buf, size); +error: if (mapped) { munmap(buf, size); } else { free(buf); } - if (rc) { - return rc; + if (ret) { + fixup_blocks(b); } - fixup_blocks(b); - return rc; + return ret; } -int load_buffer(Buffer *b, bool must_exist, const char *filename) +bool load_buffer(Buffer *b, bool must_exist, const char *filename) { - int fd = open(filename, O_RDONLY); + int fd = xopen(filename, O_RDONLY | O_CLOEXEC, 0); if (fd < 0) { if (errno != ENOENT) { error_msg("Error opening %s: %s", filename, strerror(errno)); - return -1; + return false; } if (must_exist) { error_msg("File %s does not exist.", filename); - return -1; + return false; } fixup_blocks(b); } else { - if (fstat(fd, &b->st) != 0) { + if (!buffer_fstat(b, fd)) { error_msg("fstat failed on %s: %s", filename, strerror(errno)); - close(fd); - return -1; + goto error; } - if (!S_ISREG(b->st.st_mode)) { + if (!S_ISREG(b->file.mode)) { error_msg("Not a regular file %s", filename); - close(fd); - return -1; + goto error; } - if (b->st.st_size / 1024 / 1024 > editor.options.filesize_limit) { + if (unlikely(b->file.size < 0)) { + error_msg("Invalid file size: %jd", (intmax_t)b->file.size); + goto error; + } + if (b->file.size / 1024 / 1024 > editor.options.filesize_limit) { error_msg ( "File size exceeds 'filesize-limit' option (%uMiB): %s", editor.options.filesize_limit, filename ); - close(fd); - return -1; + goto error; } - - if (read_blocks(b, fd)) { + if (!read_blocks(b, fd)) { error_msg("Error reading %s: %s", filename, strerror(errno)); - close(fd); - return -1; + goto error; } - close(fd); + xclose(fd); } if (b->encoding.type == ENCODING_AUTODETECT) { - b->encoding = editor.charset; + buffer_set_encoding(b, editor.charset); } - return 0; + return true; + +error: + xclose(fd); + return false; } static mode_t get_umask(void) @@ -279,17 +320,16 @@ return old; } -static int write_buffer(Buffer *b, FileEncoder *enc, EncodingType bom_type) +static bool write_buffer(Buffer *b, FileEncoder *enc, int fd, EncodingType bom_type) { size_t size = 0; - if (bom_type != UTF8) { - const ByteOrderMark *bom = get_bom_for_encoding(bom_type); - if (bom) { - size = bom->len; - if (xwrite(enc->fd, bom->bytes, size) < 0) { - error_msg("Write error: %s", strerror(errno)); - return -1; - } + const ByteOrderMark *bom = get_bom_for_encoding(bom_type); + if (bom) { + size = bom->len; + BUG_ON(size == 0); + if (xwrite(fd, bom->bytes, size) < 0) { + perror_msg("write"); + return false; } } @@ -297,33 +337,37 @@ block_for_each(blk, &b->blocks) { ssize_t rc = file_encoder_write(enc, blk->data, blk->size); if (rc < 0) { - error_msg("Write error: %s", strerror(errno)); - return -1; + perror_msg("write"); + return false; } size += rc; } - if (enc->cconv != NULL && cconv_nr_errors(enc->cconv)) { + size_t nr_errors = file_encoder_get_nr_errors(enc); + if (nr_errors > 0) { // Any real error hides this message error_msg ( - "Warning: %zu nonreversible character conversions. File saved.", - cconv_nr_errors(enc->cconv) + "Warning: %zu non-reversible character conversion%s. File saved.", + nr_errors, + (nr_errors > 1) ? "s" : "" ); } // Need to truncate if writing to existing file - if (ftruncate(enc->fd, size)) { - error_msg("Truncate failed: %s", strerror(errno)); - return -1; + if (ftruncate(fd, size)) { + perror_msg("ftruncate"); + return false; } - return 0; + + return true; } -int save_buffer ( +bool save_buffer ( Buffer *b, const char *filename, const Encoding *encoding, - LineEndingType newline + bool crlf, + bool write_bom ) { FileEncoder *enc; int fd = -1; @@ -337,60 +381,89 @@ const char *base = path_basename(filename); const StringView dir = path_slice_dirname(filename); const int dlen = (int)dir.length; - xsnprintf(tmp, sizeof tmp, "%.*s/.tmp.%s.XXXXXX", dlen, dir.data, base); - fd = mkstemp(tmp); - if (fd < 0) { - // No write permission to the directory? - tmp[0] = '\0'; - } else if (b->st.st_mode) { - // Preserve ownership and mode of the original file if possible. - UNUSED int u1 = fchown(fd, b->st.st_uid, b->st.st_gid); - UNUSED int u2 = fchmod(fd, b->st.st_mode); - } else { - // New file - fchmod(fd, 0666 & ~get_umask()); + int n = snprintf(tmp, sizeof tmp, "%.*s/.tmp.%s.XXXXXX", dlen, dir.data, base); + if (likely(n > 0 && n < sizeof(tmp))) { + fd = mkstemp(tmp); + if (fd < 0) { + // No write permission to the directory? + tmp[0] = '\0'; + } else if (b->file.mode) { + // Preserve ownership and mode of the original file if possible. + UNUSED int u1 = fchown(fd, b->file.uid, b->file.gid); + UNUSED int u2 = fchmod(fd, b->file.mode); + } else { + // New file + fchmod(fd, 0666 & ~get_umask()); + } } } if (fd < 0) { // Overwrite the original file (if exists) directly. // Ownership is preserved automatically if the file exists. - mode_t mode = b->st.st_mode; + mode_t mode = b->file.mode; if (mode == 0) { // New file. mode = 0666 & ~get_umask(); } - fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, mode); + fd = xopen(filename, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, mode); if (fd < 0) { - error_msg("Error opening file: %s", strerror(errno)); - return -1; + perror_msg("open"); + return false; } } - enc = new_file_encoder(encoding, newline, fd); - if (enc == NULL) { + enc = new_file_encoder(encoding, crlf, fd); + if (unlikely(!enc)) { // This should never happen because encoding is validated early - error_msg("iconv_open: %s", strerror(errno)); - close(fd); + perror_msg("new_file_encoder"); + xclose(fd); goto error; } - if (write_buffer(b, enc, encoding->type)) { - close(fd); + + EncodingType bom_type = write_bom ? encoding->type : UNKNOWN_ENCODING; + if (!write_buffer(b, enc, fd, bom_type)) { + xclose(fd); goto error; } - if (close(fd)) { - error_msg("Close failed: %s", strerror(errno)); + +#ifdef HAVE_FSYNC + if (b->options.fsync) { + retry: + if (fsync(fd) != 0) { + switch (errno) { + // EINVAL is ignored because it just means "operation not + // possible on this descriptor" rather than indicating an + // actual error + case EINVAL: + case ENOTSUP: + case ENOSYS: + break; + case EINTR: + goto retry; + default: + perror_msg("fsync"); + xclose(fd); + goto error; + } + } + } +#endif + + if (xclose(fd)) { + perror_msg("close"); goto error; } if (*tmp && rename(tmp, filename)) { - error_msg("Rename failed: %s", strerror(errno)); + perror_msg("rename"); goto error; } free_file_encoder(enc); - stat(filename, &b->st); - return 0; + buffer_stat(b, filename); + return true; + error: - if (enc != NULL) { + if (enc) { free_file_encoder(enc); } if (*tmp) { @@ -399,7 +472,7 @@ // Not using temporary file therefore mtime may have changed. // Update stat to avoid "File has been modified by someone else" // error later when saving the file again. - stat(filename, &b->st); + buffer_stat(b, filename); } - return -1; + return false; } diff -Nru dte-1.9.1/src/load-save.h dte-1.10/src/load-save.h --- dte-1.9.1/src/load-save.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/load-save.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,12 @@ #ifndef LOAD_SAVE_H #define LOAD_SAVE_H +#include #include "buffer.h" +#include "encoding.h" -int load_buffer(Buffer *b, bool must_exist, const char *filename); -int save_buffer(Buffer *b, const char *filename, const Encoding *encoding, LineEndingType newline); -int read_blocks(Buffer *b, int fd); +bool load_buffer(Buffer *b, bool must_exist, const char *filename); +bool save_buffer(Buffer *b, const char *filename, const Encoding *encoding, bool crlf, bool write_bom); +bool read_blocks(Buffer *b, int fd); #endif diff -Nru dte-1.9.1/src/lock.c dte-1.10/src/lock.c --- dte-1.9.1/src/lock.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/lock.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,23 +1,20 @@ -#include +#include #include #include -#include +#include #include #include #include "lock.h" -#include "buffer.h" #include "editor.h" #include "error.h" #include "util/ascii.h" +#include "util/path.h" #include "util/readfile.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" #include "util/xsnprintf.h" -static char *file_locks; -static char *file_locks_lock; - static bool process_exists(pid_t pid) { return !kill(pid, 0); @@ -48,7 +45,7 @@ bool same = filename_len == next_bol - 1 - pos - && !memcmp(buf + pos, filename, filename_len) + && mem_equal(buf + pos, filename, filename_len) ; if (pid == my_pid) { if (same) { @@ -77,21 +74,33 @@ return other_pid; } -static int lock_or_unlock(const char *filename, bool lock) +static bool lock_or_unlock(const char *filename, bool lock) { + static char *file_locks; + static char *file_locks_lock; + static mode_t mode = 0666; if (!file_locks) { - file_locks = editor_file("file-locks"); - file_locks_lock = editor_file("file-locks.lock"); + const char *dir = editor.xdg_runtime_dir; + if (dir && path_is_absolute(dir)) { + // Set sticky bit (see XDG Base Directory Specification) + #ifdef S_ISVTX + mode |= S_ISVTX; + #endif + } else { + dir = editor.user_config_dir; + } + file_locks = path_join(dir, "dte-locks"); + file_locks_lock = path_join(dir, "dte-locks.lock"); } if (streq(filename, file_locks) || streq(filename, file_locks_lock)) { - return 0; + return true; } int tries = 0; int wfd; while (1) { - wfd = open(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL, 0666); + wfd = xopen(file_locks_lock, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, mode); if (wfd >= 0) { break; } @@ -102,7 +111,7 @@ file_locks_lock, strerror(errno) ); - return -1; + return false; } if (++tries == 3) { if (unlink(file_locks_lock)) { @@ -111,7 +120,7 @@ file_locks_lock, strerror(errno) ); - return -1; + return false; } error_msg("Stale lock file %s removed.", file_locks_lock); } else { @@ -131,48 +140,54 @@ goto error; } size = 0; - } - if (size > 0 && buf[size - 1] != '\n') { + } else if (size > 0 && buf[size - 1] != '\n') { buf[size++] = '\n'; } + pid_t pid = rewrite_lock_file(buf, &size, filename); if (lock) { if (pid == 0) { - const size_t n = strlen(filename) + 32; + size_t n = strlen(filename) + (4 * sizeof(pid_t)); xrenew(buf, size + n); - xsnprintf(buf + size, n, "%d %s\n", getpid(), filename); + xsnprintf(buf + size, n, "%jd %s\n", (intmax_t)getpid(), filename); size += strlen(buf + size); } else { - error_msg("File is locked (%s) by process %d", file_locks, pid); + intmax_t xpid = (intmax_t)pid; + error_msg("File is locked (%s) by process %jd", file_locks, xpid); } } + if (xwrite(wfd, buf, size) < 0) { error_msg("Error writing %s: %s", file_locks_lock, strerror(errno)); goto error; } - if (close(wfd)) { + + int r = xclose(wfd); + wfd = -1; + if (r != 0) { error_msg("Error closing %s: %s", file_locks_lock, strerror(errno)); goto error; } + if (rename(file_locks_lock, file_locks)) { - error_msg ( - "Renaming %s to %s: %s", - file_locks_lock, - file_locks, - strerror(errno) - ); + const char *err = strerror(errno); + error_msg("Renaming %s to %s: %s", file_locks_lock, file_locks, err); goto error; } + free(buf); - return pid == 0 ? 0 : -1; + return (pid == 0); + error: unlink(file_locks_lock); free(buf); - close(wfd); - return -1; + if (wfd >= 0) { + xclose(wfd); + } + return false; } -int lock_file(const char *filename) +bool lock_file(const char *filename) { return lock_or_unlock(filename, true); } diff -Nru dte-1.9.1/src/lock.h dte-1.10/src/lock.h --- dte-1.9.1/src/lock.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/lock.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,9 @@ #ifndef LOCK_H #define LOCK_H -int lock_file(const char *filename); +#include + +bool lock_file(const char *filename); void unlock_file(const char *filename); #endif diff -Nru dte-1.9.1/src/main.c dte-1.10/src/main.c --- dte-1.9.1/src/main.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/main.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,14 @@ #include -#include +#include #include #include #include +#include #include -#include "alias.h" +#include "block.h" +#include "command/serialize.h" +#include "commands.h" #include "config.h" -#include "debug.h" #include "editor.h" #include "error.h" #include "file-history.h" @@ -14,14 +16,16 @@ #include "history.h" #include "load-save.h" #include "move.h" -#include "screen.h" #include "search.h" #include "syntax/state.h" #include "syntax/syntax.h" -#include "terminal/color.h" #include "terminal/input.h" +#include "terminal/mode.h" #include "terminal/output.h" #include "terminal/terminal.h" +#include "util/debug.h" +#include "util/exitcode.h" +#include "util/macros.h" #include "util/str-util.h" #include "util/strtonum.h" #include "util/xmalloc.h" @@ -40,12 +44,26 @@ !editor.child_controls_terminal && editor.status != EDITOR_INITIALIZING ) { - terminal.raw(); - editor.resize(); + term_raw(); + ui_start(); } } -static void handle_fatal_signal(int signum) +#ifdef SIGWINCH +static void handle_sigwinch(int UNUSED_ARG(signum)) +{ + editor.resized = true; +} +#endif + +static void term_cleanup(void) +{ + if (!editor.child_controls_terminal) { + ui_end(); + } +} + +static noreturn COLD void handle_fatal_signal(int signum) { term_cleanup(); @@ -61,6 +79,11 @@ sigprocmask(SIG_UNBLOCK, &mask, NULL); raise(signum); + + // This is here just to make extra certain the handler never returns. + // If everything is working correctly, this code should be unreachable. + raise(SIGKILL); + _exit(EX_OSERR); } static void do_sigaction(int sig, const struct sigaction *action) @@ -70,14 +93,16 @@ } } +// Signals not handled by this function: +// * SIGKILL, SIGSTOP (can't be caught or ignored) +// * SIGPOLL, SIGPROF (marked "obsolete" in POSIX 2008) +// * SIGABRT (cleanup is done before calling abort()) static void set_signal_handlers(void) { - // SIGABRT is not included here, since we can always call - // term_cleanup() explicitly, before calling abort(). static const int fatal_signals[] = { SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, SIGXFSZ, - SIGALRM, SIGPROF, SIGVTALRM, + SIGALRM, SIGVTALRM, SIGHUP, SIGTERM, }; @@ -86,6 +111,11 @@ SIGUSR1, SIGUSR2, }; + static const int default_signals[] = { + SIGCHLD, SIGURG, + SIGTTIN, SIGTTOU, + }; + static const struct { int signum; void (*handler)(int); @@ -111,6 +141,11 @@ do_sigaction(ignored_signals[i], &action); } + action.sa_handler = SIG_DFL; + for (size_t i = 0; i < ARRAY_COUNT(default_signals); i++) { + do_sigaction(default_signals[i], &action); + } + sigemptyset(&action.sa_mask); for (size_t i = 0; i < ARRAY_COUNT(handled_signals); i++) { action.sa_handler = handled_signals[i].handler; @@ -118,21 +153,49 @@ } } -static int dump_builtin_config(const char *const name) +static ExitCode list_builtin_configs(void) +{ + String str = dump_builtin_configs(); + ssize_t n = xwrite(STDOUT_FILENO, str.buffer, str.len); + string_free(&str); + if (n < 0) { + perror("write"); + return EX_IOERR; + } + return EX_OK; +} + +static ExitCode dump_builtin_config(const char *name) { const BuiltinConfig *cfg = get_builtin_config(name); - if (cfg) { - xwrite(STDOUT_FILENO, cfg->text.data, cfg->text.length); - return 0; - } else { + if (!cfg) { fprintf(stderr, "Error: no built-in config with name '%s'\n", name); - return 1; + return EX_USAGE; } + if (xwrite(STDOUT_FILENO, cfg->text.data, cfg->text.length) < 0) { + perror("write"); + return EX_IOERR; + } + return EX_OK; +} + +static ExitCode lint_syntax(const char *filename) +{ + int err; + const Syntax *s = load_syntax_file(filename, CFG_MUST_EXIST, &err); + if (s) { + const size_t n = s->states.count; + const char *p = (n > 1) ? "s" : ""; + printf("OK: loaded syntax '%s' with %zu state%s\n", s->name, n, p); + } else if (err == EINVAL) { + error_msg("%s: no default syntax found", filename); + } + return get_nr_errors() ? EX_DATAERR : EX_OK; } -static void showkey_loop(void) +static ExitCode showkey_loop(void) { - terminal.raw(); + term_raw(); terminal.put_control_code(terminal.control_codes.init); terminal.put_control_code(terminal.control_codes.keypad_on); term_add_literal("Press any key combination, or use Ctrl+D to exit\r\n"); @@ -141,30 +204,41 @@ bool loop = true; while (loop) { KeyCode key; + const char *str; if (!term_read_key(&key)) { - term_add_literal(" UNKNOWN -\r\n"); - term_output_flush(); - continue; - } - switch (key) { - case KEY_PASTE: + str = "UNKNOWN"; + } else if (key == KEY_PASTE) { term_discard_paste(); continue; - case CTRL('D'): - loop = false; - break; + } else if (key == KEY_IGNORE) { + continue; + } else { + if (key == CTRL('D')) { + loop = false; + } + str = keycode_to_string(key); } - const char *str = key_to_string(key); - term_sprintf(" %-12s 0x%-12" PRIX32 "\r\n", str, key); + term_add_literal(" "); + term_add_bytes(str, strlen(str)); + term_add_literal("\r\n"); term_output_flush(); } terminal.put_control_code(terminal.control_codes.keypad_off); terminal.put_control_code(terminal.control_codes.deinit); term_output_flush(); - terminal.cooked(); + term_cooked(); + return EX_OK; } +static const char copyright[] = + "(C) 2017-2021 Craig Barnes\n" + "(C) 2010-2015 Timo Hirvonen\n" + "This program is free software; you can redistribute and/or modify\n" + "it under the terms of the GNU General Public License version 2\n" + ".\n" + "There is NO WARRANTY, to the extent permitted by law."; + static const char usage[] = "Usage: %s [OPTIONS] [[+LINE] FILE]...\n\n" "Options:\n" @@ -187,14 +261,11 @@ const char *tag = NULL; const char *rc = NULL; const char *command = NULL; - const char *lint_syntax = NULL; bool read_rc = true; bool use_showkey = false; bool load_and_save_history = true; int ch; - init_editor_state(); - while ((ch = getopt(argc, argv, optstring)) != -1) { switch (ch) { case 'c': @@ -207,16 +278,14 @@ rc = optarg; break; case 's': - lint_syntax = optarg; - goto loop_break; + return lint_syntax(optarg); case 'R': read_rc = false; break; case 'b': return dump_builtin_config(optarg); case 'B': - list_builtin_configs(); - return 0; + return list_builtin_configs(); case 'H': load_and_save_history = false; break; @@ -225,125 +294,131 @@ goto loop_break; case 'V': printf("dte %s\n", editor.version); - puts("(C) 2017-2019 Craig Barnes"); - puts("(C) 2010-2015 Timo Hirvonen"); - return 0; + puts(copyright); + return EX_OK; case 'h': printf(usage, argv[0]); - return 0; + return EX_OK; case '?': default: - return 1; + return EX_USAGE; } } loop_break: - if (lint_syntax) { - int err; - const Syntax *s = load_syntax_file(lint_syntax, CFG_MUST_EXIST, &err); - if (s) { - const size_t n = s->states.count; - const char *p = (n > 1) ? "s" : ""; - printf("OK: loaded syntax '%s' with %zu state%s\n", s->name, n, p); - } else if (err == EINVAL) { - error_msg("%s: no default syntax found", lint_syntax); - } - return get_nr_errors() ? 1 : 0; - } - - if (!isatty(STDOUT_FILENO)) { - fputs("stdout doesn't refer to a terminal\n", stderr); - return 1; - } + init_editor_state(); Buffer *stdin_buffer = NULL; if (!isatty(STDIN_FILENO)) { Buffer *b = buffer_new(&editor.charset); - if (read_blocks(b, STDIN_FILENO) == 0) { - b->display_filename = xmemdup_literal("(stdin)"); + if (read_blocks(b, STDIN_FILENO)) { + set_display_filename(b, xmemdup_literal("(stdin)")); stdin_buffer = b; + stdin_buffer->temporary = true; } else { + if (errno != EBADF) { + error_msg("Unable to read redirected stdin"); + } free_buffer(b); - error_msg("Unable to read redirected stdin"); } if (!freopen("/dev/tty", "r", stdin)) { - fputs("Cannot reopen input tty\n", stderr); - return 1; + perror("Unable to reopen input tty"); + return EX_IOERR; + } + int fd = fileno(stdin); + if (fd != STDIN_FILENO) { + fprintf(stderr, "freopen() changed stdin fd from 0 to %d\n", fd); + return EX_OSERR; + } + } + + Buffer *stdout_buffer = NULL; + int old_stdout_fd = -1; + if (!isatty(STDOUT_FILENO)) { + old_stdout_fd = fcntl(STDOUT_FILENO, F_DUPFD_CLOEXEC, 3); + if (old_stdout_fd == -1 && errno != EBADF) { + perror("fcntl"); + return EX_OSERR; + } + if (!freopen("/dev/tty", "w", stdout)) { + perror("Unable to open /dev/tty"); + return EX_OSERR; + } + int fd = fileno(stdout); + if (fd != STDOUT_FILENO) { + // This should never happen in a single-threaded program. + // freopen() should call fclose() followed by open() and + // POSIX requires a successful call to open() to return the + // lowest available file descriptor. + fprintf(stderr, "freopen() changed stdout fd from 1 to %d\n", fd); + return EX_OSERR; + } + if (old_stdout_fd == -1) { + // The call to fcntl(3) above failed with EBADF, meaning stdout was + // most likely closed and there's no point opening a buffer for it + } else if (stdin_buffer) { + stdout_buffer = stdin_buffer; + stdout_buffer->stdout_buffer = true; + set_display_filename(stdout_buffer, xmemdup_literal("(stdin|stdout)")); + } else { + stdout_buffer = open_empty_buffer(); + set_display_filename(stdout_buffer, xmemdup_literal("(stdout)")); + stdout_buffer->stdout_buffer = true; + stdout_buffer->temporary = true; } } term_init(); if (use_showkey) { - showkey_loop(); - return 0; + return showkey_loop(); } // Create this early. Needed if lock-files is true. - const char *const editor_dir = editor.user_config_dir; + const char *editor_dir = editor.user_config_dir; if (mkdir(editor_dir, 0755) != 0 && errno != EEXIST) { error_msg("Error creating %s: %s", editor_dir, strerror(errno)); + load_and_save_history = false; + editor.options.lock_files = false; } - terminal.save_title(); - - exec_reset_colors_rc(); - read_config(commands, "rc", CFG_MUST_EXIST | CFG_BUILTIN); - fill_builtin_colors(); - - // NOTE: syntax_changed() uses window. Should possibly create - // window after reading rc. - window = new_window(); - root_frame = new_root_frame(window); + term_save_title(); + exec_builtin_rc(); if (read_rc) { + ConfigFlags flags = CFG_NOFLAGS; if (rc) { - read_config(commands, rc, CFG_MUST_EXIST); + flags |= CFG_MUST_EXIST; } else { - char *filename = editor_file("rc"); - read_config(commands, filename, CFG_NOFLAGS); - free(filename); + rc = editor_file("rc"); } + DEBUG_LOG("loading configuration from %s", rc); + read_config(&commands, rc, flags); } update_all_syntax_colors(); - sort_aliases(); + window = new_window(); + root_frame = new_root_frame(window); set_signal_handlers(); + set_fatal_error_cleanup_handler(term_cleanup); - char *file_history_filename = NULL; - char *command_history_filename = NULL; - char *search_history_filename = NULL; + const char *file_history_filename = NULL; if (load_and_save_history) { file_history_filename = editor_file("file-history"); - command_history_filename = editor_file("command-history"); - search_history_filename = editor_file("search-history"); - load_file_history(file_history_filename); - - history_load ( - &editor.command_history, - command_history_filename, - command_history_size - ); - - history_load ( - &editor.search_history, - search_history_filename, - search_history_size - ); - if (editor.search_history.count) { - search_set_regexp ( - editor.search_history.ptrs[editor.search_history.count - 1] - ); + history_load(&editor.command_history, editor_file("command-history")); + history_load(&editor.search_history, editor_file("search-history")); + if (editor.search_history.last) { + search_set_regexp(editor.search_history.last->text); } } // Initialize terminal but don't update screen yet. Also display // "Press any key to continue" prompt if there were any errors // during reading configuration files. - terminal.raw(); + term_raw(); if (get_nr_errors()) { any_key(); clear_error(); @@ -370,6 +445,9 @@ if (stdin_buffer) { window_add_buffer(window, stdin_buffer); } + if (stdout_buffer && stdout_buffer != stdin_buffer) { + window_add_buffer(window, stdout_buffer); + } const View *empty_buffer = NULL; if (window->views.count == 0) { @@ -377,18 +455,21 @@ } set_view(window->views.ptrs[0]); - - if (command || tag) { - editor.resize(); - } + ui_start(); if (command) { - handle_command(commands, command); + handle_command(&commands, command, false); } if (tag) { - const char *tag_command[] = {"tag", tag, NULL}; - run_command(commands, (char**)tag_command); + String s = string_new(8 + strlen(tag)); + string_append_literal(&s, "tag "); + if (unlikely(tag[0] == '-')) { + string_append_literal(&s, "-- "); + } + string_append_escaped_arg(&s, tag, true); + handle_command(&commands, string_borrow_cstring(&s), false); + string_free(&s); } if ( @@ -403,29 +484,36 @@ remove_view(window->views.ptrs[0]); } - terminal.put_control_code(terminal.control_codes.init); - editor.resize(); + if (command || tag) { + normal_update(); + } main_loop(); - terminal.restore_title(); - editor.ui_end(); - terminal.put_control_code(terminal.control_codes.deinit); + term_restore_title(); + ui_end(); term_output_flush(); // Unlock files and add files to file history remove_frame(root_frame); if (load_and_save_history) { - history_save(&editor.command_history, command_history_filename); - free(command_history_filename); - - history_save(&editor.search_history, search_history_filename); - free(search_history_filename); - + history_save(&editor.command_history); + history_save(&editor.search_history); save_file_history(file_history_filename); - free(file_history_filename); } - return 0; + if (stdout_buffer) { + Block *blk; + block_for_each(blk, &stdout_buffer->blocks) { + if (xwrite(old_stdout_fd, blk->data, blk->size) < 0) { + perror_msg("write"); + return EX_IOERR; + } + } + free_blocks(stdout_buffer); + free(stdout_buffer); + } + + return editor.exit_code; } diff -Nru dte-1.9.1/src/mode.c dte-1.10/src/mode.c --- dte-1.9.1/src/mode.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/mode.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,165 @@ +#include "mode.h" +#include "bind.h" +#include "buffer.h" +#include "change.h" +#include "cmdline.h" +#include "command/macro.h" +#include "commands.h" +#include "completion.h" +#include "edit.h" +#include "editor.h" +#include "history.h" +#include "search.h" +#include "terminal/input.h" +#include "util/debug.h" +#include "util/unicode.h" +#include "view.h" + +static void normal_mode_keypress(KeyCode key) +{ + switch (key) { + case KEY_TAB: + if (view->selection == SELECT_LINES) { + shift_lines(1); + return; + } + break; + case MOD_SHIFT | KEY_TAB: + if (view->selection == SELECT_LINES) { + shift_lines(-1); + return; + } + break; + case KEY_PASTE: { + size_t size; + char *text = term_read_paste(&size); + begin_change(CHANGE_MERGE_NONE); + insert_text(text, size, true); + end_change(); + macro_insert_text_hook(text, size); + free(text); + return; + } + } + + if (u_is_unicode(key)) { + insert_ch(key); + macro_insert_char_hook(key); + } else { + handle_binding(key); + } +} + +static void command_mode_keypress(KeyCode key) +{ + switch (key) { + case KEY_ENTER: + reset_completion(); + set_input_mode(INPUT_NORMAL); + const char *str = string_borrow_cstring(&editor.cmdline.buf); + cmdline_clear(&editor.cmdline); + if (str[0] != ' ') { + // This is done before handle_command() because "command [text]" + // can modify the contents of the command-line + history_add(&editor.command_history, str); + } + handle_command(&commands, str, true); + return; + case KEY_TAB: + complete_command_next(); + return; + case MOD_SHIFT | KEY_TAB: + complete_command_prev(); + return; + } + + switch (cmdline_handle_key(&editor.cmdline, &editor.command_history, key)) { + case CMDLINE_CANCEL: + set_input_mode(INPUT_NORMAL); + // Fallthrough + case CMDLINE_KEY_HANDLED: + reset_completion(); + // Fallthrough + case CMDLINE_UNKNOWN_KEY: + return; + } +} + +static void search_mode_keypress(KeyCode key) +{ + switch (key) { + case MOD_META | KEY_ENTER: + if (editor.cmdline.buf.len == 0) { + return; + } else { + // Escape the regex; to match as plain text + char *original = string_clone_cstring(&editor.cmdline.buf); + size_t len = editor.cmdline.buf.len; + string_clear(&editor.cmdline.buf); + for (size_t i = 0; i < len; i++) { + char ch = original[i]; + if (is_regex_special_char(ch)) { + string_append_byte(&editor.cmdline.buf, '\\'); + } + string_append_byte(&editor.cmdline.buf, ch); + } + free(original); + } + // Fallthrough + case KEY_ENTER: { + const char *args[3] = {NULL, NULL, NULL}; + if (editor.cmdline.buf.len > 0) { + const char *str = string_borrow_cstring(&editor.cmdline.buf); + search_set_regexp(str); + history_add(&editor.search_history, str); + if (unlikely(str[0] == '-')) { + args[0] = "--"; + args[1] = str; + } else { + args[0] = str; + } + } else { + args[0] = "-n"; + } + search_next(); + macro_command_hook("search", (char**)args); + cmdline_clear(&editor.cmdline); + set_input_mode(INPUT_NORMAL); + return; + } + case MOD_META | 'c': + editor.options.case_sensitive_search = (editor.options.case_sensitive_search + 1) % 3; + return; + case MOD_META | 'r': + toggle_search_direction(); + return; + case KEY_TAB: + return; + } + + switch (cmdline_handle_key(&editor.cmdline, &editor.search_history, key)) { + case CMDLINE_CANCEL: + set_input_mode(INPUT_NORMAL); + return; + case CMDLINE_UNKNOWN_KEY: + case CMDLINE_KEY_HANDLED: + return; + } +} + +void handle_input(KeyCode key) +{ + switch (editor.input_mode) { + case INPUT_NORMAL: + normal_mode_keypress(key); + break; + case INPUT_COMMAND: + command_mode_keypress(key); + break; + case INPUT_SEARCH: + search_mode_keypress(key); + break; + default: + BUG("unhandled input mode"); + } +} diff -Nru dte-1.9.1/src/mode-command.c dte-1.10/src/mode-command.c --- dte-1.9.1/src/mode-command.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/mode-command.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -#include "cmdline.h" -#include "command.h" -#include "completion.h" -#include "editor.h" -#include "error.h" -#include "history.h" -#include "mode.h" - -static void command_mode_handle_enter(void) -{ - reset_completion(); - set_input_mode(INPUT_NORMAL); - - const char *str = string_borrow_cstring(&editor.cmdline.buf); - PointerArray array = PTR_ARRAY_INIT; - CommandParseError err = 0; - bool ok = parse_commands(&array, str, &err); - - // This is done before run_commands() because "command [text]" - // can modify the contents of the command-line - history_add(&editor.command_history, str, command_history_size); - cmdline_clear(&editor.cmdline); - - if (ok) { - run_commands(commands, &array); - } else { - error_msg("Parsing command: %s", command_parse_error_to_string(err)); - } - - ptr_array_free(&array); -} - -static void command_mode_keypress(KeyCode key) -{ - switch (key) { - case KEY_ENTER: - command_mode_handle_enter(); - return; - case '\t': - complete_command(); - return; - } - - switch (cmdline_handle_key(&editor.cmdline, &editor.command_history, key)) { - case CMDLINE_KEY_HANDLED: - reset_completion(); - return; - case CMDLINE_CANCEL: - set_input_mode(INPUT_NORMAL); - return; - case CMDLINE_UNKNOWN_KEY: - return; - } -} - -const EditorModeOps command_mode_ops = { - .keypress = command_mode_keypress, - .update = normal_update, -}; diff -Nru dte-1.9.1/src/mode-git-open.c dte-1.10/src/mode-git-open.c --- dte-1.9.1/src/mode-git-open.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/mode-git-open.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,321 +0,0 @@ -#include "cmdline.h" -#include "editor.h" -#include "error.h" -#include "mode.h" -#include "screen.h" -#include "spawn.h" -#include "terminal/output.h" -#include "terminal/terminal.h" -#include "util/ascii.h" -#include "util/ptr-array.h" -#include "util/utf8.h" -#include "util/unicode.h" -#include "util/xmalloc.h" -#include "window.h" - -static struct { - PointerArray files; - char *all_files; - size_t size; - size_t selected; - size_t scroll; -} git_open; - -static void git_open_clear(void) -{ - free(git_open.all_files); - git_open.all_files = NULL; - git_open.size = 0; - git_open.files.count = 0; - git_open.selected = 0; - git_open.scroll = 0; -} - -static char *cdup(void) -{ - static const char *const cmd[] = {"git", "rev-parse", "--show-cdup", NULL}; - FilterData data = FILTER_DATA_INIT; - - if (spawn_filter((char **)cmd, &data)) { - return NULL; - } - - const size_t len = data.out_len; - if (len > 1 && data.out[len - 1] == '\n') { - data.out[len - 1] = '\0'; - return data.out; - } - free(data.out); - return NULL; -} - -static void git_open_load(void) -{ - static const char *cmd[] = {"git", "ls-files", "-z", NULL, NULL}; - FilterData data = FILTER_DATA_INIT; - int status = 0; - char *dir = cdup(); - cmd[3] = dir; - - if ((status = spawn_filter((char **)cmd, &data)) == 0) { - git_open.all_files = data.out; - git_open.size = data.out_len; - } else { - set_input_mode(INPUT_NORMAL); - error_msg("git-open: 'git ls-files' command returned %d", status); - } - free(dir); -} - -static bool contains_upper(const char *str) -{ - size_t i = 0; - while (str[i]) { - if (u_is_upper(u_str_get_char(str, &i))) { - return true; - } - } - return false; -} - -static void split(PointerArray *words, const char *str) -{ - size_t i = 0; - while (str[i]) { - while (ascii_isspace(str[i])) { - i++; - } - if (!str[i]) { - break; - } - const size_t s = i++; - while (str[i] && !ascii_isspace(str[i])) { - i++; - } - ptr_array_add(words, xstrslice(str, s, i)); - } -} - -static bool words_match(const char *name, const PointerArray *words) -{ - for (size_t i = 0, n = words->count; i < n; i++) { - if (!strstr(name, words->ptrs[i])) { - return false; - } - } - return true; -} - -static bool words_match_icase(const char *name, const PointerArray *words) -{ - for (size_t i = 0, n = words->count; i < n; i++) { - if (u_str_index(name, words->ptrs[i]) < 0) { - return false; - } - } - return true; -} - -static const char *selected_file(void) -{ - if (git_open.files.count == 0) { - return NULL; - } - return git_open.files.ptrs[git_open.selected]; -} - -static void git_open_filter(void) -{ - const char *str = string_borrow_cstring(&editor.cmdline.buf); - char *ptr = git_open.all_files; - char *end = git_open.all_files + git_open.size; - bool (*match)(const char*, const PointerArray*) = words_match_icase; - PointerArray words = PTR_ARRAY_INIT; - - // NOTE: words_match_icase() requires str to be lowercase - if (contains_upper(str)) { - match = words_match; - } - split(&words, str); - - git_open.files.count = 0; - while (ptr < end) { - char *zero = memchr(ptr, 0, end - ptr); - if (zero == NULL) { - break; - } - if (match(ptr, &words)) { - ptr_array_add(&git_open.files, ptr); - } - ptr = zero + 1; - } - ptr_array_free(&words); - git_open.selected = 0; - git_open.scroll = 0; -} - -static void up(size_t count) -{ - if (count >= git_open.selected) { - git_open.selected = 0; - } else { - git_open.selected -= count; - } -} - -static void down(size_t count) -{ - if (git_open.files.count > 1) { - git_open.selected += count; - if (git_open.selected >= git_open.files.count) { - git_open.selected = git_open.files.count - 1; - } - } -} - -static void open_selected(void) -{ - const char *sel = selected_file(); - if (sel != NULL) { - window_open_file(window, sel, NULL); - } -} - -void git_open_reload(void) -{ - git_open_clear(); - git_open_load(); - git_open_filter(); -} - -static size_t terminal_page_height(void) -{ - if (terminal.height >= 6) { - return terminal.height - 2; - } else { - return 1; - } -} - -static void git_open_keypress(KeyCode key) -{ - switch (key) { - case KEY_ENTER: - open_selected(); - cmdline_clear(&editor.cmdline); - set_input_mode(INPUT_NORMAL); - break; - case CTRL('O'): - open_selected(); - down(1); - break; - case MOD_META | 'e': - if (git_open.files.count > 1) { - git_open.selected = git_open.files.count - 1; - } - break; - case MOD_META | 't': - git_open.selected = 0; - break; - case KEY_UP: - up(1); - break; - case KEY_DOWN: - down(1); - break; - case KEY_PAGE_UP: - up(terminal_page_height()); - break; - case KEY_PAGE_DOWN: - down(terminal_page_height()); - break; - case '\t': - if (git_open.selected + 1 >= git_open.files.count) { - git_open.selected = 0; - } else { - down(1); - } - break; - default: - switch (cmdline_handle_key(&editor.cmdline, NULL, key)) { - case CMDLINE_UNKNOWN_KEY: - break; - case CMDLINE_KEY_HANDLED: - git_open_filter(); - break; - case CMDLINE_CANCEL: - set_input_mode(INPUT_NORMAL); - break; - } - } - mark_everything_changed(); -} - -static void git_open_update_screen(void) -{ - int x = 0; - int y = 0; - int w = terminal.width; - int h = terminal.height - 1; - int max_y = git_open.scroll + h - 1; - int i = 0; - - if (h >= git_open.files.count) { - git_open.scroll = 0; - } - if (git_open.scroll > git_open.selected) { - git_open.scroll = git_open.selected; - } - if (git_open.selected > max_y) { - git_open.scroll += git_open.selected - max_y; - } - - term_output_reset(x, w, 0); - terminal.move_cursor(0, 0); - editor.cmdline_x = print_command('/'); - term_clear_eol(); - y++; - - for (; i < h; i++) { - int file_idx = git_open.scroll + i; - char *file; - TermColor color; - - if (file_idx >= git_open.files.count) { - break; - } - - file = git_open.files.ptrs[file_idx]; - obuf.x = 0; - terminal.move_cursor(x, y + i); - - color = *builtin_colors[BC_DEFAULT]; - if (file_idx == git_open.selected) { - mask_color(&color, builtin_colors[BC_SELECTION]); - } - terminal.set_color(&color); - term_add_str(file); - term_clear_eol(); - } - set_builtin_color(BC_DEFAULT); - for (; i < h; i++) { - obuf.x = 0; - terminal.move_cursor(x, y + i); - term_clear_eol(); - } -} - -static void git_open_update(void) -{ - term_hide_cursor(); - update_term_title(window->view->buffer); - git_open_update_screen(); - terminal.move_cursor(editor.cmdline_x, 0); - term_show_cursor(); - term_output_flush(); -} - -const EditorModeOps git_open_ops = { - .keypress = git_open_keypress, - .update = git_open_update, -}; diff -Nru dte-1.9.1/src/mode.h dte-1.10/src/mode.h --- dte-1.9.1/src/mode.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/mode.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,16 +3,6 @@ #include "terminal/key.h" -typedef struct { - void (*keypress)(KeyCode key); - void (*update)(void); -} EditorModeOps; - -extern const EditorModeOps normal_mode_ops; -extern const EditorModeOps command_mode_ops; -extern const EditorModeOps search_mode_ops; -extern const EditorModeOps git_open_ops; - -void git_open_reload(void); +void handle_input(KeyCode key); #endif diff -Nru dte-1.9.1/src/mode-normal.c dte-1.10/src/mode-normal.c --- dte-1.9.1/src/mode-normal.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/mode-normal.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -#include "bind.h" -#include "change.h" -#include "edit.h" -#include "editor.h" -#include "mode.h" -#include "terminal/input.h" -#include "util/unicode.h" -#include "view.h" -#include "window.h" - -static void insert_paste(void) -{ - size_t size; - char *text = term_read_paste(&size); - - // Because this is not a command (see run_command()) you have to - // call begin_change() to avoid merging this change into previous - begin_change(CHANGE_MERGE_NONE); - insert_text(text, size); - end_change(); - - free(text); -} - -static void normal_mode_keypress(KeyCode key) -{ - switch (key) { - case '\t': - if (view->selection == SELECT_LINES) { - shift_lines(1); - return; - } - break; - case MOD_SHIFT | '\t': - if (view->selection == SELECT_LINES) { - shift_lines(-1); - return; - } - break; - case KEY_PASTE: - insert_paste(); - return; - } - - if (u_is_unicode(key)) { - insert_ch(key); - } else { - handle_binding(key); - } -} - -const EditorModeOps normal_mode_ops = { - .keypress = normal_mode_keypress, - .update = normal_update, -}; diff -Nru dte-1.9.1/src/mode-search.c dte-1.10/src/mode-search.c --- dte-1.9.1/src/mode-search.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/mode-search.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -#include "cmdline.h" -#include "editor.h" -#include "history.h" -#include "mode.h" -#include "search.h" - -static void search_mode_keypress(KeyCode key) -{ - switch (key) { - case KEY_ENTER: - if (editor.cmdline.buf.len > 0) { - const char *str = string_borrow_cstring(&editor.cmdline.buf); - search_set_regexp(str); - search_next(); - history_add(&editor.search_history, str, search_history_size); - } else { - search_next(); - } - cmdline_clear(&editor.cmdline); - set_input_mode(INPUT_NORMAL); - return; - case MOD_META | 'c': - editor.options.case_sensitive_search = (editor.options.case_sensitive_search + 1) % 3; - return; - case MOD_META | 'r': - search_set_direction(current_search_direction() ^ 1); - return; - case '\t': - return; - } - - switch (cmdline_handle_key(&editor.cmdline, &editor.search_history, key)) { - case CMDLINE_CANCEL: - set_input_mode(INPUT_NORMAL); - return; - case CMDLINE_UNKNOWN_KEY: - case CMDLINE_KEY_HANDLED: - return; - } -} - -const EditorModeOps search_mode_ops = { - .keypress = search_mode_keypress, - .update = normal_update, -}; diff -Nru dte-1.9.1/src/move.c dte-1.10/src/move.c --- dte-1.9.1/src/move.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/move.c 2021-04-03 21:08:53.000000000 +0000 @@ -13,15 +13,15 @@ void move_to_preferred_x(long preferred_x) { - LineRef lr; + StringView line; view->preferred_x = preferred_x; block_iter_bol(&view->cursor); - fill_line_ref(&view->cursor, &lr); + fill_line_ref(&view->cursor, &line); - if (buffer->options.emulate_tab && view->preferred_x < lr.size) { + if (buffer->options.emulate_tab && view->preferred_x < line.length) { const size_t iw = buffer->options.indent_width; const size_t ilevel = view->preferred_x / iw; - for (size_t i = 0; i < lr.size && lr.line[i] == ' '; i++) { + for (size_t i = 0; i < line.length && line.data[i] == ' '; i++) { if (i + 1 == (ilevel + 1) * iw) { // Force cursor to beginning of the indentation level view->cursor.offset += ilevel * iw; @@ -33,10 +33,10 @@ const unsigned int tw = buffer->options.tab_width; unsigned long x = 0; size_t i = 0; - while (x < view->preferred_x && i < lr.size) { - CodePoint u = lr.line[i++]; - if (u < 0x80) { - if (!ascii_iscntrl(u)) { + while (x < view->preferred_x && i < line.length) { + CodePoint u = line.data[i++]; + if (likely(u < 0x80)) { + if (likely(!ascii_iscntrl(u))) { x++; } else if (u == '\t') { x = (x + tw) / tw * tw; @@ -48,7 +48,7 @@ } else { const size_t next = i; i--; - u = u_get_nonascii(lr.line, lr.size, &i); + u = u_get_nonascii(line.data, line.length, &i); x += u_char_width(u); if (x > view->preferred_x) { i = next; @@ -62,7 +62,6 @@ view->cursor.offset += i; // If cursor stopped on a zero-width char, move to the next spacing char. - // TODO: Incorporate this cursor fixup into the logic above. CodePoint u; if (block_iter_get_char(&view->cursor, &u) && u_is_zero_width(u)) { block_iter_next_column(&view->cursor); @@ -105,11 +104,11 @@ void move_bol_smart(void) { - LineRef lr; - const size_t cursor_offset = fetch_this_line(&view->cursor, &lr); + StringView line; + const size_t cursor_offset = fetch_this_line(&view->cursor, &line); size_t indent_bytes = 0; - while (ascii_isblank(*lr.line++)) { + while (ascii_isblank(*line.data++)) { indent_bytes++; } diff -Nru dte-1.9.1/src/move.h dte-1.10/src/move.h --- dte-1.9.1/src/move.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/move.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,6 +2,7 @@ #define MOVE_H #include +#include #include "block-iter.h" #include "view.h" diff -Nru dte-1.9.1/src/msg.c dte-1.10/src/msg.c --- dte-1.9.1/src/msg.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/msg.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,31 +1,21 @@ +#include +#include #include "msg.h" #include "buffer.h" +#include "edit.h" #include "error.h" #include "move.h" #include "search.h" +#include "util/debug.h" #include "util/ptr-array.h" -#include "util/str-util.h" #include "util/xmalloc.h" +#include "view.h" #include "window.h" static PointerArray file_locations = PTR_ARRAY_INIT; static PointerArray msgs = PTR_ARRAY_INIT; static size_t msg_pos; -FileLocation *file_location_create ( - const char *filename, - unsigned long buffer_id, - unsigned long line, - unsigned long column -) { - FileLocation *loc = xnew0(FileLocation, 1); - loc->filename = filename ? xstrdup(filename) : NULL; - loc->buffer_id = buffer_id; - loc->line = line; - loc->column = column; - return loc; -} - void file_location_free(FileLocation *loc) { free(loc->filename); @@ -33,42 +23,36 @@ free(loc); } -static bool file_location_equals(const FileLocation *a, const FileLocation *b) +FileLocation *get_current_file_location(void) { - if (!xstreq(a->filename, b->filename)) { - return false; - } - if (a->buffer_id != b->buffer_id) { - return false; - } - if (!xstreq(a->pattern, b->pattern)) { - return false; - } - if (a->line != b->line) { - return false; - } - if (a->column != b->column) { - return false; - } - return true; + const char *filename = buffer->abs_filename; + FileLocation *loc = xmalloc(sizeof(*loc)); + *loc = (FileLocation) { + .filename = filename ? xstrdup(filename) : NULL, + .buffer_id = buffer->id, + .line = view->cy + 1, + .column = view->cx_char + 1 + }; + return loc; } static bool file_location_go(const FileLocation *loc) { Window *w = window; View *v = window_open_buffer(w, loc->filename, true, NULL); - bool ok = true; - if (!v) { // Failed to open file. Error message should be visible. return false; } + if (w->view != v) { set_view(v); // Force centering view to the cursor because file changed v->force_center = true; } - if (loc->pattern != NULL) { + + bool ok = true; + if (loc->pattern) { bool err = false; search_tag(loc->pattern, &err); ok = !err; @@ -78,6 +62,9 @@ move_to_column(v, loc->column); } } + if (ok) { + unselect(); + } return ok; } @@ -87,21 +74,24 @@ Buffer *b = find_buffer_by_id(loc->buffer_id); View *v; - if (b != NULL) { + if (b) { v = window_get_view(w, b); } else { - if (loc->filename == NULL) { + if (!loc->filename) { // Can't restore closed buffer that had no filename. // Try again. return false; } v = window_open_buffer(w, loc->filename, true, NULL); } - if (v == NULL) { + + if (!v) { // Open failed. Don't try again. return true; } + set_view(v); + unselect(); move_to_line(v, loc->line); move_to_column(v, loc->column); return true; @@ -109,7 +99,12 @@ void push_file_location(FileLocation *loc) { - ptr_array_add(&file_locations, loc); + const size_t max_entries = 256; + if (file_locations.count == max_entries) { + file_location_free(ptr_array_remove_idx(&file_locations, 0)); + } + BUG_ON(file_locations.count >= max_entries); + ptr_array_append(&file_locations, loc); } void pop_file_location(void) @@ -125,50 +120,22 @@ static void free_message(Message *m) { free(m->msg); - if (m->loc != NULL) { + if (m->loc) { file_location_free(m->loc); } free(m); } -static bool message_equals(const Message *a, const Message *b) -{ - if (!streq(a->msg, b->msg)) { - return false; - } - if (a->loc == NULL) { - return b->loc == NULL; - } - if (b->loc == NULL) { - return false; - } - return file_location_equals(a->loc, b->loc); -} - -static bool is_duplicate(const Message *m) -{ - for (size_t i = 0; i < msgs.count; i++) { - if (message_equals(m, msgs.ptrs[i])) { - return true; - } - } - return false; -} - -Message *new_message(const char *msg) +Message *new_message(const char *msg, size_t len) { Message *m = xnew0(Message, 1); - m->msg = xstrdup(msg); + m->msg = xstrcut(msg, len); return m; } void add_message(Message *m) { - if (is_duplicate(m)) { - free_message(m); - } else { - ptr_array_add(&msgs, m); - } + ptr_array_append(&msgs, m); } void activate_current_message(void) @@ -177,7 +144,7 @@ return; } const Message *m = msgs.ptrs[msg_pos]; - if (m->loc != NULL && m->loc->filename != NULL) { + if (m->loc && m->loc->filename) { if (!file_location_go(m->loc)) { // Error message is visible return; @@ -206,6 +173,20 @@ activate_current_message(); } +void activate_current_message_save(void) +{ + const BlockIter save = view->cursor; + FileLocation *loc = get_current_file_location(); + activate_current_message(); + + // Save position if file changed or cursor moved + if (view->cursor.blk != save.blk || view->cursor.offset != save.offset) { + push_file_location(loc); + } else { + file_location_free(loc); + } +} + void clear_messages(void) { ptr_array_free_cb(&msgs, FREE_FUNC(free_message)); diff -Nru dte-1.9.1/src/msg.h dte-1.10/src/msg.h --- dte-1.9.1/src/msg.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/msg.h 2021-04-03 21:08:53.000000000 +0000 @@ -22,22 +22,17 @@ FileLocation *loc; } Message; -FileLocation *file_location_create ( - const char *filename, - unsigned long buffer_id, - unsigned long line, - unsigned long column -); - +FileLocation *get_current_file_location(void) RETURNS_NONNULL; void file_location_free(FileLocation *loc); void push_file_location(FileLocation *loc); void pop_file_location(void); -Message *new_message(const char *msg); +Message *new_message(const char *msg, size_t len) RETURNS_NONNULL; void add_message(Message *m); void activate_current_message(void); void activate_next_message(void); void activate_prev_message(void); +void activate_current_message_save(void); void clear_messages(void); size_t message_count(void) PURE; diff -Nru dte-1.9.1/src/options.c dte-1.10/src/options.c --- dte-1.9.1/src/options.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/options.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,19 +1,25 @@ +#include +#include +#include #include "options.h" +#include "buffer.h" +#include "command/serialize.h" #include "completion.h" -#include "debug.h" #include "editor.h" #include "error.h" #include "file-option.h" #include "filetype.h" #include "regexp.h" #include "screen.h" -#include "terminal/terminal.h" +#include "terminal/output.h" +#include "util/bsearch.h" +#include "util/debug.h" #include "util/hashset.h" +#include "util/numtostr.h" #include "util/str-util.h" #include "util/string-view.h" #include "util/strtonum.h" #include "util/xmalloc.h" -#include "util/xsnprintf.h" #include "view.h" #include "window.h" @@ -21,15 +27,16 @@ OPT_STR, OPT_UINT, OPT_ENUM, + OPT_BOOL, OPT_FLAG, } OptionType; typedef struct { - const struct OptionOps *ops; - const char *name; - size_t offset; + const char name[22]; bool local; bool global; + unsigned int offset; + OptionType type; union { struct { // Optional @@ -40,14 +47,14 @@ unsigned int max; } uint_opt; struct { - const char **values; + const char *const *values; } enum_opt; struct { - const char **values; + const char *const *values; } flag_opt; } u; // Optional - void (*on_change)(void); + void (*on_change)(bool global); } OptionDesc; typedef union { @@ -55,19 +62,13 @@ const char *str_val; // OPT_UINT, OPT_ENUM, OPT_FLAG unsigned int uint_val; + // OPT_BOOL + bool bool_val; } OptionValue; -typedef struct OptionOps { - OptionValue (*get)(const OptionDesc *desc, void *ptr); - void (*set)(const OptionDesc *desc, void *ptr, OptionValue value); - bool (*parse)(const OptionDesc *desc, const char *str, OptionValue *value); - const char *(*string)(const OptionDesc *desc, OptionValue value); - bool (*equals)(const OptionDesc *desc, void *ptr, OptionValue value); -} OptionOps; - #define STR_OPT(_name, OLG, _validate, _on_change) { \ - .ops = &option_ops[OPT_STR], \ .name = _name, \ + .type = OPT_STR, \ OLG \ .u = { .str_opt = { \ .validate = _validate, \ @@ -76,8 +77,8 @@ } #define UINT_OPT(_name, OLG, _min, _max, _on_change) { \ - .ops = &option_ops[OPT_UINT], \ .name = _name, \ + .type = OPT_UINT, \ OLG \ .u = { .uint_opt = { \ .min = _min, \ @@ -87,8 +88,8 @@ } #define ENUM_OPT(_name, OLG, _values, _on_change) { \ - .ops = &option_ops[OPT_ENUM], \ .name = _name, \ + .type = OPT_ENUM, \ OLG \ .u = { .enum_opt = { \ .values = _values, \ @@ -97,8 +98,8 @@ } #define FLAG_OPT(_name, OLG, _values, _on_change) { \ - .ops = &option_ops[OPT_FLAG], \ .name = _name, \ + .type = OPT_FLAG, \ OLG \ .u = { .flag_opt = { \ .values = _values, \ @@ -106,10 +107,9 @@ .on_change = _on_change, \ } -// Can't reuse ENUM_OPT() because of weird macro expansion rules #define BOOL_OPT(_name, OLG, _on_change) { \ - .ops = &option_ops[OPT_ENUM], \ .name = _name, \ + .type = OPT_BOOL, \ OLG \ .u = { .enum_opt = { \ .values = bool_enum, \ @@ -126,46 +126,60 @@ #define G(member) OLG(offsetof(GlobalOptions, member), false, true) #define C(member) OLG(offsetof(CommonOptions, member), true, true) -static void filetype_changed(void) +static void filetype_changed(bool global) { - Buffer *b = window->view->buffer; - set_file_options(b); - buffer_update_syntax(b); + BUG_ON(!buffer); + BUG_ON(global); + set_file_options(buffer); + buffer_update_syntax(buffer); } -static void set_window_title_changed(void) +static void set_window_title_changed(bool global) { + BUG_ON(!global); if (editor.options.set_window_title) { if (editor.status == EDITOR_RUNNING) { - update_term_title(window->view->buffer); + update_term_title(buffer); } } else { - terminal.restore_title(); - terminal.save_title(); + term_restore_title(); + term_save_title(); + } +} + +static void syntax_changed(bool global) +{ + if (buffer && !global) { + buffer_update_syntax(buffer); } } -static void syntax_changed(void) +static void redraw_buffer(bool global) { - if (window->view != NULL) { - buffer_update_syntax(window->view->buffer); + if (buffer && !global) { + mark_all_lines_changed(buffer); } } +static void redraw_screen(bool UNUSED_ARG(global)) +{ + mark_everything_changed(); +} + static bool validate_statusline_format(const char *value) { - static const char chars[] = "fmryYxXpEMnstu%"; + static const StringView chars = STRING_VIEW("bfmryYxXpEMNSnstu%"); size_t i = 0; while (value[i]) { char ch = value[i++]; if (ch == '%') { ch = value[i++]; - if (!ch) { - error_msg("Format character expected after '%%'."); + if (ch == '\0') { + error_msg("Format character expected after '%%'"); return false; } - if (!strchr(chars, ch)) { - error_msg("Invalid format character '%c'.", ch); + if (!strview_memchr(&chars, ch)) { + error_msg("Invalid format character '%c'", ch); return false; } } @@ -190,114 +204,135 @@ static OptionValue str_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) { OptionValue v; - v.str_val = *(char**)ptr; + const char **strp = ptr; + v.str_val = *strp; return v; } -static void str_set ( - const OptionDesc* UNUSED_ARG(desc), - void *ptr, - OptionValue value -) { +static void str_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) +{ const char **strp = ptr; - *strp = str_intern(value.str_val); + *strp = str_intern(v.str_val); } -static bool str_parse ( - const OptionDesc *desc, - const char *str, - OptionValue *value -) { - if (desc->u.str_opt.validate && !desc->u.str_opt.validate(str)) { - value->str_val = NULL; +static bool str_parse(const OptionDesc *d, const char *str, OptionValue *v) +{ + if (d->u.str_opt.validate && !d->u.str_opt.validate(str)) { + v->str_val = NULL; return false; } - value->str_val = str; + v->str_val = str; return true; } -static const char *str_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value) +static const char *str_string(const OptionDesc* UNUSED_ARG(d), OptionValue v) { - const char *s = value.str_val; + const char *s = v.str_val; return s ? s : ""; } -static bool str_equals ( - const OptionDesc* UNUSED_ARG(desc), - void *ptr, - OptionValue value -) { - return xstreq(*(char**)ptr, value.str_val); +static bool str_equals(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) +{ + const char **strp = ptr; + return xstreq(*strp, v.str_val); } static OptionValue uint_get(const OptionDesc* UNUSED_ARG(desc), void *ptr) { OptionValue v; - v.uint_val = *(unsigned int*)ptr; + unsigned int *valp = ptr; + v.uint_val = *valp; return v; } -static void uint_set ( - const OptionDesc* UNUSED_ARG(desc), - void *ptr, - OptionValue value -) { - *(unsigned int*)ptr = value.uint_val; +static void uint_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) +{ + unsigned int *valp = ptr; + *valp = v.uint_val; } -static bool uint_parse ( - const OptionDesc *desc, - const char *str, - OptionValue *value -) { +static bool uint_parse(const OptionDesc *d, const char *str, OptionValue *v) +{ unsigned int val; if (!str_to_uint(str, &val)) { - error_msg("Integer value for %s expected.", desc->name); + error_msg("Integer value for %s expected", d->name); return false; } - if (val < desc->u.uint_opt.min || val > desc->u.uint_opt.max) { - error_msg ( - "Value for %s must be in %u-%u range.", - desc->name, - desc->u.uint_opt.min, - desc->u.uint_opt.max - ); + const unsigned int min = d->u.uint_opt.min; + const unsigned int max = d->u.uint_opt.max; + if (val < min || val > max) { + error_msg("Value for %s must be in %u-%u range", d->name, min, max); return false; } - value->uint_val = val; + v->uint_val = val; return true; } static const char *uint_string(const OptionDesc* UNUSED_ARG(desc), OptionValue value) { - static char buf[64]; - xsnprintf(buf, sizeof buf, "%u", value.uint_val); - return buf; + return uint_to_str(value.uint_val); } static bool uint_equals(const OptionDesc* UNUSED_ARG(desc), void *ptr, OptionValue value) { - return *(unsigned int*)ptr == value.uint_val; + unsigned int *valp = ptr; + return *valp == value.uint_val; } -static bool enum_parse ( - const OptionDesc *desc, - const char *str, - OptionValue *value -) { +static OptionValue bool_get(const OptionDesc* UNUSED_ARG(d), void *ptr) +{ + OptionValue v; + bool *valp = ptr; + v.bool_val = *valp; + return v; +} + +static void bool_set(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) +{ + bool *valp = ptr; + *valp = v.bool_val; +} + +static bool bool_parse(const OptionDesc *d, const char *str, OptionValue *v) +{ + if (streq(str, "true")) { + v->bool_val = true; + return true; + } else if (streq(str, "false")) { + v->bool_val = false; + return true; + } + error_msg("Invalid value for %s", d->name); + return false; +} + +static const char *bool_string(const OptionDesc* UNUSED_ARG(d), OptionValue v) +{ + return v.bool_val ? "true" : "false"; +} + +static bool bool_equals(const OptionDesc* UNUSED_ARG(d), void *ptr, OptionValue v) +{ + bool *valp = ptr; + return *valp == v.bool_val; +} + +static bool enum_parse(const OptionDesc *d, const char *str, OptionValue *v) +{ + const char *const *values = d->u.enum_opt.values; unsigned int i; - for (i = 0; desc->u.enum_opt.values[i]; i++) { - if (streq(desc->u.enum_opt.values[i], str)) { - value->uint_val = i; + for (i = 0; values[i]; i++) { + if (streq(values[i], str)) { + v->uint_val = i; return true; } } unsigned int val; if (!str_to_uint(str, &val) || val >= i) { - error_msg("Invalid value for %s.", desc->name); + error_msg("Invalid value for %s", d->name); return false; } - value->uint_val = val; + v->uint_val = val; return true; } @@ -306,51 +341,34 @@ return desc->u.enum_opt.values[value.uint_val]; } -static bool flag_parse ( - const OptionDesc *desc, - const char *str, - OptionValue *value -) { +static bool flag_parse(const OptionDesc *d, const char *str, OptionValue *v) +{ // "0" is allowed for compatibility and is the same as "" if (str[0] == '0' && str[1] == '\0') { - value->uint_val = 0; + v->uint_val = 0; return true; } - const char **values = desc->u.flag_opt.values; - const char *ptr = str; + const char *const *values = d->u.flag_opt.values; unsigned int flags = 0; - while (*ptr) { - const char *end = strchr(ptr, ','); - size_t len; - if (end) { - len = end - ptr; - end++; - } else { - len = strlen(ptr); - end = ptr + len; - } - const StringView flag = string_view(ptr, len); - ptr = end; + for (size_t pos = 0, len = strlen(str); pos < len; ) { + const StringView flag = get_delim(str, &pos, len, ','); size_t i; for (i = 0; values[i]; i++) { - if (string_view_equal_cstr(&flag, values[i])) { + if (strview_equal_cstring(&flag, values[i])) { flags |= 1u << i; break; } } - if (!values[i]) { - error_msg ( - "Invalid flag '%.*s' for %s.", - (int)flag.length, - flag.data, - desc->name - ); + if (unlikely(!values[i])) { + int flen = (int)flag.length; + error_msg("Invalid flag '%.*s' for %s", flen, flag.data, d->name); return false; } } - value->uint_val = flags; + + v->uint_val = flags; return true; } @@ -365,9 +383,9 @@ } char *ptr = buf; - const char **values = desc->u.flag_opt.values; + const char *const *values = desc->u.flag_opt.values; for (size_t i = 0; values[i]; i++) { - if (flags & (1 << i)) { + if (flags & (1u << i)) { size_t len = strlen(values[i]); memcpy(ptr, values[i], len); ptr += len; @@ -379,77 +397,72 @@ return buf; } -static const OptionOps option_ops[] = { +static const struct { + OptionValue (*get)(const OptionDesc *desc, void *ptr); + void (*set)(const OptionDesc *desc, void *ptr, OptionValue value); + bool (*parse)(const OptionDesc *desc, const char *str, OptionValue *value); + const char *(*string)(const OptionDesc *desc, OptionValue value); + bool (*equals)(const OptionDesc *desc, void *ptr, OptionValue value); +} option_ops[] = { [OPT_STR] = {str_get, str_set, str_parse, str_string, str_equals}, [OPT_UINT] = {uint_get, uint_set, uint_parse, uint_string, uint_equals}, [OPT_ENUM] = {uint_get, uint_set, enum_parse, enum_string, uint_equals}, + [OPT_BOOL] = {bool_get, bool_set, bool_parse, bool_string, bool_equals}, [OPT_FLAG] = {uint_get, uint_set, flag_parse, flag_string, uint_equals}, }; -static const char *bool_enum[] = {"false", "true", NULL}; -static const char *newline_enum[] = {"unix", "dos", NULL}; - -static const char *case_sensitive_search_enum[] = { - "false", - "true", - "auto", - NULL -}; +static const char *const bool_enum[] = {"false", "true", NULL}; +static const char *const newline_enum[] = {"unix", "dos", NULL}; +static const char *const tristate_enum[] = {"false", "true", "auto", NULL}; -static const char *detect_indent_values[] = { +static const char *const detect_indent_values[] = { "1", "2", "3", "4", "5", "6", "7", "8", NULL }; -static const char *tab_bar_enum[] = { - "hidden", - "horizontal", - "vertical", - "auto", - NULL -}; - -static const char *ws_error_values[] = { - "trailing", +static const char *const ws_error_values[] = { "space-indent", "space-align", "tab-indent", "tab-after-indent", "special", "auto-indent", + "trailing", + "all-trailing", NULL }; static const OptionDesc option_desc[] = { BOOL_OPT("auto-indent", C(auto_indent), NULL), BOOL_OPT("brace-indent", L(brace_indent), NULL), - ENUM_OPT("case-sensitive-search", G(case_sensitive_search), case_sensitive_search_enum, NULL), + ENUM_OPT("case-sensitive-search", G(case_sensitive_search), tristate_enum, NULL), FLAG_OPT("detect-indent", C(detect_indent), detect_indent_values, NULL), - BOOL_OPT("display-invisible", G(display_invisible), NULL), - BOOL_OPT("display-special", G(display_special), NULL), + BOOL_OPT("display-invisible", G(display_invisible), redraw_screen), + BOOL_OPT("display-special", G(display_special), redraw_screen), BOOL_OPT("editorconfig", C(editorconfig), NULL), BOOL_OPT("emulate-tab", C(emulate_tab), NULL), UINT_OPT("esc-timeout", G(esc_timeout), 0, 2000, NULL), - BOOL_OPT("expand-tab", C(expand_tab), NULL), + BOOL_OPT("expand-tab", C(expand_tab), redraw_buffer), BOOL_OPT("file-history", C(file_history), NULL), UINT_OPT("filesize-limit", G(filesize_limit), 0, 16000, NULL), STR_OPT("filetype", L(filetype), validate_filetype, filetype_changed), - UINT_OPT("indent-width", C(indent_width), 1, 8, NULL), + BOOL_OPT("fsync", C(fsync), NULL), STR_OPT("indent-regex", L(indent_regex), validate_regex, NULL), + UINT_OPT("indent-width", C(indent_width), 1, 8, NULL), BOOL_OPT("lock-files", G(lock_files), NULL), - ENUM_OPT("newline", G(newline), newline_enum, NULL), - UINT_OPT("scroll-margin", G(scroll_margin), 0, 100, NULL), + ENUM_OPT("newline", G(crlf_newlines), newline_enum, NULL), + UINT_OPT("scroll-margin", G(scroll_margin), 0, 100, redraw_screen), + BOOL_OPT("select-cursor-char", G(select_cursor_char), redraw_screen), BOOL_OPT("set-window-title", G(set_window_title), set_window_title_changed), - BOOL_OPT("show-line-numbers", G(show_line_numbers), NULL), + BOOL_OPT("show-line-numbers", G(show_line_numbers), redraw_screen), STR_OPT("statusline-left", G(statusline_left), validate_statusline_format, NULL), STR_OPT("statusline-right", G(statusline_right), validate_statusline_format, NULL), BOOL_OPT("syntax", C(syntax), syntax_changed), - ENUM_OPT("tab-bar", G(tab_bar), tab_bar_enum, NULL), - UINT_OPT("tab-bar-max-components", G(tab_bar_max_components), 0, 10, NULL), - UINT_OPT("tab-bar-width", G(tab_bar_width), TAB_BAR_MIN_WIDTH, 100, NULL), - UINT_OPT("tab-width", C(tab_width), 1, 8, NULL), + BOOL_OPT("tab-bar", G(tab_bar), redraw_screen), + UINT_OPT("tab-width", C(tab_width), 1, 8, redraw_buffer), UINT_OPT("text-width", C(text_width), 1, 1000, NULL), - FLAG_OPT("ws-error", C(ws_error), ws_error_values, NULL), + BOOL_OPT("utf8-bom", G(utf8_bom), NULL), + FLAG_OPT("ws-error", C(ws_error), ws_error_values, redraw_buffer), }; static char *local_ptr(const OptionDesc *desc, const LocalOptions *opt) @@ -462,36 +475,82 @@ return (char*)&editor.options + desc->offset; } -static bool desc_is(const OptionDesc *desc, OptionType type) +UNITTEST { + static const size_t alignments[] = { + [OPT_STR] = alignof(const char*), + [OPT_UINT] = alignof(unsigned int), + [OPT_ENUM] = alignof(unsigned int), + [OPT_BOOL] = alignof(bool), + [OPT_FLAG] = alignof(unsigned int), + }; + + // Check offset alignments + for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { + const OptionDesc *desc = &option_desc[i]; + size_t alignment = alignments[desc->type]; + if (desc->global) { + uintptr_t ptr_val = (uintptr_t)global_ptr(desc); + BUG_ON(ptr_val % alignment != 0); + } + if (desc->local) { + const UNUSED Buffer b; + uintptr_t ptr_val = (uintptr_t)local_ptr(desc, &b.options); + BUG_ON(ptr_val % alignment != 0); + } + } + + // Ensure option_desc[] is properly sorted + CHECK_BSEARCH_ARRAY(option_desc, name, strcmp); + + // Validate default statusline formats + BUG_ON(!validate_statusline_format(editor.options.statusline_left)); + BUG_ON(!validate_statusline_format(editor.options.statusline_right)); +} + +static OptionValue desc_get(const OptionDesc *desc, void *ptr) { - return desc->ops == &option_ops[type]; + return option_ops[desc->type].get(desc, ptr); } -static void desc_set(const OptionDesc *desc, void *ptr, OptionValue value) +static void desc_set(const OptionDesc *desc, void *ptr, bool global, OptionValue value) { - desc->ops->set(desc, ptr, value); + option_ops[desc->type].set(desc, ptr, value); if (desc->on_change) { - desc->on_change(); - } else { - mark_everything_changed(); + desc->on_change(global); } } +static bool desc_parse(const OptionDesc *desc, const char *str, OptionValue *value) +{ + return option_ops[desc->type].parse(desc, str, value); +} + +static const char *desc_string(const OptionDesc *desc, OptionValue value) +{ + return option_ops[desc->type].string(desc, value); +} + +static bool desc_equals(const OptionDesc *desc, void *ptr, OptionValue value) +{ + return option_ops[desc->type].equals(desc, ptr, value); +} + +static int option_cmp(const void *key, const void *elem) +{ + const char *name = key; + const OptionDesc *desc = elem; + return strcmp(name, desc->name); +} + static const OptionDesc *find_option(const char *name) { - for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { - const OptionDesc *desc = &option_desc[i]; - if (streq(name, desc->name)) { - return desc; - } - } - return NULL; + return BSEARCH(name, option_desc, option_cmp); } static const OptionDesc *must_find_option(const char *name) { const OptionDesc *desc = find_option(name); - if (desc == NULL) { + if (!desc) { error_msg("No such option %s", name); } return desc; @@ -523,7 +582,7 @@ } OptionValue val; - if (!desc->ops->parse(desc, value, &val)) { + if (!desc_parse(desc, value, &val)) { return; } if (!local && !global) { @@ -537,10 +596,10 @@ } if (local) { - desc_set(desc, local_ptr(desc, &buffer->options), val); + desc_set(desc, local_ptr(desc, &buffer->options), false, val); } if (global) { - desc_set(desc, global_ptr(desc), val); + desc_set(desc, global_ptr(desc), true, val); } } @@ -559,8 +618,8 @@ if (!desc) { return; } - if (!desc_is(desc, OPT_ENUM) || desc->u.enum_opt.values != bool_enum) { - error_msg("Option %s is not boolean.", desc->name); + if (desc->type != OPT_BOOL) { + error_msg("Option %s is not boolean", desc->name); return; } do_set_option(desc, "true", local, global); @@ -580,38 +639,32 @@ return desc; } -static unsigned int toggle(unsigned int value, const char **values) -{ - if (!values[++value]) { - value = 0; - } - return value; -} - void toggle_option(const char *name, bool global, bool verbose) { const OptionDesc *desc = find_toggle_option(name, &global); if (!desc) { return; } - if (!desc_is(desc, OPT_ENUM)) { - error_msg("Option %s is not toggleable.", name); - return; - } - char *ptr; - if (global) { - ptr = global_ptr(desc); + char *ptr = global ? global_ptr(desc) : local_ptr(desc, &buffer->options); + OptionValue value = desc_get(desc, ptr); + OptionType type = desc->type; + if (type == OPT_ENUM) { + if (desc->u.enum_opt.values[value.uint_val + 1]) { + value.uint_val++; + } else { + value.uint_val = 0; + } + } else if (type == OPT_BOOL) { + value.bool_val = !value.bool_val; } else { - ptr = local_ptr(desc, &buffer->options); + error_msg("Toggling %s requires arguments", name); + return; } - OptionValue value; - value.uint_val = toggle(*(unsigned int *)ptr, desc->u.enum_opt.values); - desc_set(desc, ptr, value); - + desc_set(desc, ptr, global, value); if (verbose) { - const char *str = desc->ops->string(desc, value); + const char *str = desc_string(desc, value); info_msg("%s = %s", desc->name, str); } } @@ -635,8 +688,8 @@ OptionValue *parsed_values = xnew(OptionValue, count); for (size_t i = 0; i < count; i++) { - if (desc->ops->parse(desc, values[i], &parsed_values[i])) { - if (desc->ops->equals(desc, ptr, parsed_values[i])) { + if (desc_parse(desc, values[i], &parsed_values[i])) { + if (desc_equals(desc, ptr, parsed_values[i])) { current = i + 1; } } else { @@ -646,9 +699,9 @@ if (!error) { size_t i = current % count; - desc_set(desc, ptr, parsed_values[i]); + desc_set(desc, ptr, global, parsed_values[i]); if (verbose) { - const char *str = desc->ops->string(desc, parsed_values[i]); + const char *str = desc_string(desc, parsed_values[i]); info_msg("%s = %s", desc->name, str); } } @@ -658,82 +711,106 @@ bool validate_local_options(char **strs) { - bool valid = true; + size_t invalid = 0; for (size_t i = 0; strs[i]; i += 2) { const char *name = strs[i]; const char *value = strs[i + 1]; const OptionDesc *desc = must_find_option(name); - if (desc == NULL) { - valid = false; - } else if (!desc->local) { - error_msg("Option %s is not local", name); - valid = false; - } else if (streq(name, "filetype")) { + if (unlikely(!desc)) { + invalid++; + } else if (unlikely(!desc->local)) { + error_msg("%s is not local", name); + invalid++; + } else if (unlikely(desc->on_change == filetype_changed)) { error_msg("filetype cannot be set via option command"); - valid = false; + invalid++; } else { OptionValue val; - if (!desc->ops->parse(desc, value, &val)) { - valid = false; + if (unlikely(!desc_parse(desc, value, &val))) { + invalid++; } } } - return valid; + return !invalid; } -void collect_options(const char *prefix) +void collect_options(const char *prefix, bool local, bool global) { for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { const OptionDesc *desc = &option_desc[i]; + if ((local && !desc->local) || (global && !desc->global)) { + continue; + } if (str_has_prefix(desc->name, prefix)) { add_completion(xstrdup(desc->name)); } } } -void collect_toggleable_options(const char *prefix) +// Collect options that can be set via the "option" command +void collect_auto_options(const char *prefix) { for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { const OptionDesc *desc = &option_desc[i]; - if (desc_is(desc, OPT_ENUM) && str_has_prefix(desc->name, prefix)) { + if (!desc->local || desc->on_change == filetype_changed) { + continue; + } + if (str_has_prefix(desc->name, prefix)) { add_completion(xstrdup(desc->name)); } } } -void collect_option_values(const char *name, const char *prefix) +void collect_toggleable_options(const char *prefix, bool global) { - const OptionDesc *desc = find_option(name); + for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { + const OptionDesc *desc = &option_desc[i]; + if (global && !desc->global) { + continue; + } + OptionType type = desc->type; + bool toggleable = (type == OPT_ENUM || type == OPT_BOOL); + if (toggleable && str_has_prefix(desc->name, prefix)) { + add_completion(xstrdup(desc->name)); + } + } +} + +void collect_option_values(const char *option, const char *prefix) +{ + const OptionDesc *desc = find_option(option); if (!desc) { return; } - if (!*prefix) { - // Complete value - char *ptr; - if (desc->local) { - ptr = local_ptr(desc, &buffer->options); - } else { - ptr = global_ptr(desc); - } - OptionValue value = desc->ops->get(desc, ptr); - add_completion(xstrdup(desc->ops->string(desc, value))); - } else if (desc_is(desc, OPT_ENUM)) { - // Complete possible values - for (size_t i = 0; desc->u.enum_opt.values[i]; i++) { - if (str_has_prefix(desc->u.enum_opt.values[i], prefix)) { - add_completion(xstrdup(desc->u.enum_opt.values[i])); + if (prefix[0] == '\0') { + bool local = desc->local; + char *ptr = local ? local_ptr(desc, &buffer->options) : global_ptr(desc); + OptionValue value = desc_get(desc, ptr); + add_completion(xstrdup(desc_string(desc, value))); + return; + } + + const char *const *values; + const char *comma; + size_t prefix_len; + + switch (desc->type) { + case OPT_ENUM: + case OPT_BOOL: + values = desc->u.enum_opt.values; + for (size_t i = 0; values[i]; i++) { + if (str_has_prefix(values[i], prefix)) { + add_completion(xstrdup(values[i])); } } - } else if (desc_is(desc, OPT_FLAG)) { - // Complete possible values - const char *comma = strrchr(prefix, ','); - size_t prefix_len = 0; - if (comma) { - prefix_len = ++comma - prefix; - } - for (size_t i = 0; desc->u.flag_opt.values[i]; i++) { - const char *str = desc->u.flag_opt.values[i]; + break; + case OPT_FLAG: + values = desc->u.flag_opt.values; + comma = strrchr(prefix, ','); + prefix_len = comma ? ++comma - prefix : 0; + for (size_t i = 0; values[i]; i++) { + const char *str = values[i]; if (str_has_prefix(str, prefix + prefix_len)) { size_t str_len = strlen(str); char *completion = xmalloc(prefix_len + str_len + 1); @@ -742,5 +819,63 @@ add_completion(completion); } } + break; + case OPT_STR: + case OPT_UINT: + break; + default: + BUG("unhandled option type"); + } +} + +static void append_option(String *s, const OptionDesc *desc, void *ptr) +{ + const OptionValue value = desc_get(desc, ptr); + const char *value_str = desc_string(desc, value); + if (unlikely(value_str[0] == '-')) { + string_append_literal(s, "-- "); + } + string_append_cstring(s, desc->name); + string_append_byte(s, ' '); + string_append_escaped_arg(s, value_str, true); + string_append_byte(s, '\n'); +} + +String dump_options(void) +{ + String buf = string_new(4096); + for (size_t i = 0; i < ARRAY_COUNT(option_desc); i++) { + const OptionDesc *desc = &option_desc[i]; + void *local = desc->local ? local_ptr(desc, &buffer->options) : NULL; + void *global = desc->global ? global_ptr(desc) : NULL; + if (local && global) { + const OptionValue global_value = desc_get(desc, global); + if (desc_equals(desc, local, global_value)) { + string_append_literal(&buf, "set "); + append_option(&buf, desc, local); + } else { + string_append_literal(&buf, "set -g "); + append_option(&buf, desc, global); + string_append_literal(&buf, "set -l "); + append_option(&buf, desc, local); + } + } else { + string_append_literal(&buf, "set "); + append_option(&buf, desc, local ? local : global); + } + } + string_append_literal(&buf, "\n\n"); + dump_file_options(&buf); + return buf; +} + +const char *get_option_value_string(const char *name) +{ + const OptionDesc *desc = find_option(name); + if (!desc) { + return NULL; } + bool local = desc->local; + char *ptr = local ? local_ptr(desc, &buffer->options) : global_ptr(desc); + return desc_string(desc, desc_get(desc, ptr)); } diff -Nru dte-1.9.1/src/options.h dte-1.10/src/options.h --- dte-1.9.1/src/options.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/options.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,31 +2,37 @@ #define OPTIONS_H #include -#include "encoding/encoder.h" +#include +#include "util/string.h" enum { - // Trailing whitespace - WSE_TRAILING = 1 << 0, - // Spaces in indentation. // Does not include less than tab-width spaces at end of indentation. - WSE_SPACE_INDENT = 1 << 1, + WSE_SPACE_INDENT = 1 << 0, // Less than tab-width spaces at end of indentation - WSE_SPACE_ALIGN = 1 << 2, + WSE_SPACE_ALIGN = 1 << 1, // Tab in indentation - WSE_TAB_INDENT = 1 << 3, + WSE_TAB_INDENT = 1 << 2, // Tab anywhere but in indentation - WSE_TAB_AFTER_INDENT = 1 << 4, + WSE_TAB_AFTER_INDENT = 1 << 3, // Special whitespace characters - WSE_SPECIAL = 1 << 5, + WSE_SPECIAL = 1 << 4, // expand-tab = false: WSE_SPACE_INDENT // expand-tab = true: WSE_TAB_AFTER_INDENT | WSE_TAB_INDENT - WSE_AUTO_INDENT = 1 << 6, + WSE_AUTO_INDENT = 1 << 5, + + // Trailing whitespace + WSE_TRAILING = 1 << 6, + + // Like WSE_TRAILING, but also includes runs of whitespace adjacent + // to the cursor (where the presence of trailing whitespace is usually + // obvious and highlighting can be annoying) + WSE_ALL_TRAILING = 1 << 7, }; typedef enum { @@ -35,25 +41,19 @@ CSS_AUTO, } SearchCaseSensitivity; -typedef enum { - TAB_BAR_HIDDEN, - TAB_BAR_HORIZONTAL, - TAB_BAR_VERTICAL, - TAB_BAR_AUTO, -} TabBarMode; - #define COMMON_OPTIONS \ - unsigned int auto_indent; \ unsigned int detect_indent; \ - unsigned int editorconfig; \ - unsigned int emulate_tab; \ - unsigned int expand_tab; \ - unsigned int file_history; \ unsigned int indent_width; \ - unsigned int syntax; \ unsigned int tab_width; \ unsigned int text_width; \ - unsigned int ws_error + unsigned int ws_error; \ + bool auto_indent; \ + bool editorconfig; \ + bool emulate_tab; \ + bool expand_tab; \ + bool file_history; \ + bool fsync; \ + bool syntax typedef struct { COMMON_OPTIONS; @@ -62,7 +62,7 @@ typedef struct { COMMON_OPTIONS; // Only local - unsigned int brace_indent; + bool brace_indent; const char *filetype; const char *indent_regex; } LocalOptions; @@ -70,34 +70,35 @@ typedef struct { COMMON_OPTIONS; // Only global - unsigned int display_invisible; - unsigned int display_special; + bool display_invisible; + bool display_special; + bool lock_files; + bool select_cursor_char; + bool set_window_title; + bool show_line_numbers; + bool tab_bar; + bool utf8_bom; // Default value for new files unsigned int esc_timeout; unsigned int filesize_limit; - unsigned int lock_files; unsigned int scroll_margin; - unsigned int set_window_title; - unsigned int show_line_numbers; - unsigned int tab_bar_max_components; - unsigned int tab_bar_width; - LineEndingType newline; // Default value for new files - SearchCaseSensitivity case_sensitive_search; - TabBarMode tab_bar; + unsigned int crlf_newlines; // Default value for new files + unsigned int case_sensitive_search; const char *statusline_left; const char *statusline_right; } GlobalOptions; #undef COMMON_OPTIONS -#define TAB_BAR_MIN_WIDTH 12 - void set_option(const char *name, const char *value, bool local, bool global); void set_bool_option(const char *name, bool local, bool global); void toggle_option(const char *name, bool global, bool verbose); void toggle_option_values(const char *name, bool global, bool verbose, char **values, size_t count); bool validate_local_options(char **strs); -void collect_options(const char *prefix); -void collect_toggleable_options(const char *prefix); -void collect_option_values(const char *name, const char *prefix); +void collect_options(const char *prefix, bool local, bool global); +void collect_auto_options(const char *prefix); +void collect_toggleable_options(const char *prefix, bool global); +void collect_option_values(const char *option, const char *prefix); +String dump_options(void); +const char *get_option_value_string(const char *name); #endif diff -Nru dte-1.9.1/src/parse-args.c dte-1.10/src/parse-args.c --- dte-1.9.1/src/parse-args.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/parse-args.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,122 +0,0 @@ -#include -#include "parse-args.h" -#include "debug.h" -#include "error.h" -#include "util/str-util.h" - -/* - * Flags and first "--" are removed. - * Flag arguments are moved to beginning. - * Other arguments come right after flag arguments. - * - * a->args field should be set before calling. - * If parsing succeeds, the other field are set and true is returned. - */ -bool parse_args(const Command *cmd, CommandArgs *a) -{ - char **args = a->args; - BUG_ON(!args); - - size_t argc = 0; - while (args[argc]) { - argc++; - } - - const char *flag_desc = cmd->flags; - size_t nr_flags = 0; - size_t nr_flag_args = 0; - bool flags_after_arg = true; - - if (*flag_desc == '-') { - flag_desc++; - flags_after_arg = false; - } - - size_t i = 0; - while (args[i]) { - char *arg = args[i]; - - if (streq(arg, "--")) { - // Move the NULL too - memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); - free(arg); - argc--; - break; - } - if (arg[0] != '-' || !arg[1]) { - if (!flags_after_arg) { - break; - } - i++; - continue; - } - - for (size_t j = 1; arg[j]; j++) { - char flag = arg[j]; - char *flag_arg; - char *flagp = strchr(flag_desc, flag); - - if (!flagp || flag == '=') { - error_msg("Invalid option -%c", flag); - return false; - } - a->flags[nr_flags++] = flag; - if (nr_flags == ARRAY_COUNT(a->flags)) { - error_msg("Too many options given."); - return false; - } - if (flagp[1] != '=') { - continue; - } - - if (j > 1 || arg[j + 1]) { - error_msg ( - "Flag -%c must be given separately because it" - " requires an argument.", - flag - ); - return false; - } - flag_arg = args[i + 1]; - if (!flag_arg) { - error_msg("Option -%c requires on argument.", flag); - return false; - } - - // Move flag argument before any other arguments - if (i != nr_flag_args) { - // farg1 arg1 arg2 -f farg2 arg3 - // farg1 farg2 arg1 arg2 arg3 - size_t count = i - nr_flag_args; - memmove ( - args + nr_flag_args + 1, - args + nr_flag_args, - count * sizeof(*args) - ); - } - args[nr_flag_args++] = flag_arg; - i++; - } - - memmove(args + i, args + i + 1, (argc - i) * sizeof(*args)); - free(arg); - argc--; - } - - // Don't count arguments to flags as arguments to command - argc -= nr_flag_args; - - if (argc < cmd->min_args) { - error_msg("Not enough arguments"); - return false; - } - if (argc > cmd->max_args) { - error_msg("Too many arguments"); - return false; - } - a->flags[nr_flags] = '\0'; - - a->nr_args = argc; - a->nr_flags = nr_flags; - return true; -} diff -Nru dte-1.9.1/src/parse-args.h dte-1.10/src/parse-args.h --- dte-1.9.1/src/parse-args.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/parse-args.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#ifndef PARSE_ARGS_H -#define PARSE_ARGS_H - -#include -#include "command.h" -#include "util/macros.h" - -bool parse_args(const Command *cmd, CommandArgs *a) NONNULL_ARGS; - -#endif diff -Nru dte-1.9.1/src/README.md dte-1.10/src/README.md --- dte-1.9.1/src/README.md 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/README.md 2021-04-03 21:08:53.000000000 +0000 @@ -8,11 +8,11 @@ The main editor code is in the base directory and various other (somewhat reusable) parts are in sub-directories: +* `command/` - command language parsing and execution * `editorconfig/` - [EditorConfig] implementation -* `encoding/` - charset encoding/decoding/conversion * `filetype/` - filetype detection * `syntax/` - syntax highlighting -* `terminal/` - terminal control and response parsing +* `terminal/` - terminal input/output handling * `util/` - data structures, string utilities, etc. diff -Nru dte-1.9.1/src/regexp.c dte-1.10/src/regexp.c --- dte-1.9.1/src/regexp.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/regexp.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,30 +1,21 @@ -#include +#include #include "regexp.h" -#include "debug.h" #include "error.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/str-util.h" #include "util/xmalloc.h" +#include "util/xsnprintf.h" -bool regexp_match_nosub(const char *pattern, const char *buf, size_t size) +RegexpWordBoundaryTokens regexp_word_boundary; + +bool regexp_match_nosub(const char *pattern, const StringView *buf) { regex_t re; bool compiled = regexp_compile(&re, pattern, REG_NEWLINE | REG_NOSUB); BUG_ON(!compiled); regmatch_t m; - bool ret = regexp_exec(&re, buf, size, 1, &m, 0); - regfree(&re); - return ret; -} - -bool regexp_match ( - const char *pattern, - const char *buf, - size_t size, - PointerArray *m -) { - regex_t re; - bool compiled = regexp_compile(&re, pattern, REG_NEWLINE); - BUG_ON(!compiled); - bool ret = regexp_exec_sub(&re, buf, size, m, 0); + bool ret = regexp_exec(&re, buf->data, buf->length, 1, &m, 0); regfree(&re); return ret; } @@ -32,7 +23,6 @@ bool regexp_compile_internal(regex_t *re, const char *pattern, int flags) { int err = regcomp(re, pattern, flags); - if (err) { char msg[1024]; regerror(err, re, msg, sizeof(msg)); @@ -51,9 +41,8 @@ int flags ) { BUG_ON(!nr_m); -// Clang's address sanitizer seemingly doesn't take REG_STARTEND into -// account when checking for buffer overflow. -#if defined(REG_STARTEND) && !defined(CLANG_ASAN_ENABLED) +// ASan/MSan don't seem to take REG_STARTEND into account +#if defined(REG_STARTEND) && !defined(ASAN_ENABLED) && !defined(MSAN_ENABLED) m[0].rm_so = 0; m[0].rm_eo = size; return !regexec(re, buf, nr_m, m, flags | REG_STARTEND); @@ -66,23 +55,35 @@ #endif } -bool regexp_exec_sub ( - const regex_t *re, - const char *buf, - size_t size, - PointerArray *matches, - int flags -) { - regmatch_t m[16]; - bool ret = regexp_exec(re, buf, size, ARRAY_COUNT(m), m, flags); - if (!ret) { - return false; - } - for (size_t i = 0; i < ARRAY_COUNT(m); i++) { - if (m[i].rm_so == -1) { +void regexp_init_word_boundary_tokens(void) +{ + const char text[] = "SSfooEE SSfoo fooEE foo SSfooEE"; + const regoff_t match_start = 20, match_end = 23; + const RegexpWordBoundaryTokens pairs[] = { + {"\\<", "\\>"}, + {"[[:<:]]", "[[:>:]]"}, + {"\\b", "\\b"}, + }; + + BUG_ON(ARRAY_COUNT(text) <= match_end); + BUG_ON(!mem_equal(text + match_start - 1, " foo ", 5)); + + for (size_t i = 0; i < ARRAY_COUNT(pairs); i++) { + const char *start = pairs[i].start; + const char *end = pairs[i].end; + char patt[32]; + xsnprintf(patt, sizeof(patt), "%s(foo)%s", start, end); + regex_t re; + if (regcomp(&re, patt, REG_EXTENDED) != 0) { + continue; + } + regmatch_t m[2]; + bool match = !regexec(&re, text, ARRAY_COUNT(m), m, 0); + regfree(&re); + if (match && m[0].rm_so == match_start && m[0].rm_eo == match_end) { + DEBUG_LOG("regexp word boundary tokens detected: %s %s", start, end); + regexp_word_boundary = pairs[i]; break; } - ptr_array_add(matches, xstrslice(buf, m[i].rm_so, m[i].rm_eo)); } - return true; } diff -Nru dte-1.9.1/src/regexp.h dte-1.10/src/regexp.h --- dte-1.9.1/src/regexp.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/regexp.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,14 +3,17 @@ #include #include -#include "util/ptr-array.h" +#include +#include "util/string-view.h" -bool regexp_match_nosub(const char *pattern, const char *buf, size_t size); -bool regexp_match(const char *pattern, const char *buf, size_t size, PointerArray *m); +typedef struct { + char start[8]; + char end[8]; +} RegexpWordBoundaryTokens; + +extern RegexpWordBoundaryTokens regexp_word_boundary; bool regexp_compile_internal(regex_t *re, const char *pattern, int flags); -bool regexp_exec(const regex_t *re, const char *buf, size_t size, size_t nr_m, regmatch_t *m, int flags); -bool regexp_exec_sub(const regex_t *re, const char *buf, size_t size, PointerArray *matches, int flags); static inline bool regexp_compile(regex_t *re, const char *pattern, int flags) { @@ -32,4 +35,8 @@ return regexp_compile_internal(re, pattern, flags); } +bool regexp_match_nosub(const char *pattern, const StringView *buf); +bool regexp_exec(const regex_t *re, const char *buf, size_t size, size_t nr_m, regmatch_t *m, int flags); +void regexp_init_word_boundary_tokens(void); + #endif diff -Nru dte-1.9.1/src/screen.c dte-1.10/src/screen.c --- dte-1.9.1/src/screen.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,54 +1,50 @@ -#include +#include #include "screen.h" -#include "cmdline.h" #include "editor.h" #include "frame.h" -#include "search.h" -#include "terminal/input.h" -#include "terminal/no-op.h" #include "terminal/output.h" #include "terminal/terminal.h" -#include "util/path.h" -#include "util/utf8.h" -#include "util/xsnprintf.h" -#include "view.h" +#include "terminal/winsize.h" void set_color(const TermColor *color) { TermColor tmp = *color; - // NOTE: -2 (keep) is treated as -1 (default) if (tmp.fg < 0) { - tmp.fg = builtin_colors[BC_DEFAULT]->fg; + tmp.fg = builtin_colors[BC_DEFAULT].fg; } if (tmp.bg < 0) { - tmp.bg = builtin_colors[BC_DEFAULT]->bg; + tmp.bg = builtin_colors[BC_DEFAULT].bg; + } + if (same_color(&tmp, &obuf.color)) { + return; } terminal.set_color(&tmp); + obuf.color = tmp; } -void set_builtin_color(enum builtin_color c) +void set_builtin_color(BuiltinColorEnum c) { - set_color(builtin_colors[c]); + set_color(&builtin_colors[c]); } void update_term_title(const Buffer *b) { - if (!editor.options.set_window_title || terminal.set_title == no_op_s) { + if ( + !editor.options.set_window_title + || terminal.control_codes.set_title_begin.length == 0 + ) { return; } // FIXME: title must not contain control characters - char title[1024]; - snprintf ( - title, - sizeof title, - "%s %c dte", - buffer_filename(b), - buffer_modified(b) ? '+' : '-' - ); - - terminal.set_title(title); + const char *filename = buffer_filename(b); + terminal.put_control_code(terminal.control_codes.set_title_begin); + term_add_bytes(filename, strlen(filename)); + term_add_byte(' '); + term_add_byte(buffer_modified(b) ? '+' : '-'); + term_add_bytes(STRN(" dte")); + terminal.put_control_code(terminal.control_codes.set_title_end); } void mask_color(TermColor *color, const TermColor *over) @@ -69,9 +65,8 @@ if (win->x + win->w == terminal.width) { return; } - - for (int y = 0; y < win->h; y++) { - terminal.move_cursor(win->x + win->w, win->y + y); + for (int y = 0, h = win->h; y < h; y++) { + term_move_cursor(win->x + win->w, win->y + y); term_add_byte('|'); } } @@ -86,10 +81,9 @@ { const View *v = win->view; size_t lines = v->buffer->nl; - int x = win->x + vertical_tabbar_width(win); + int x = win->x; calculate_line_numbers(win); - long first = v->vy + 1; long last = v->vy + win->edit_h; if (last > lines) { @@ -107,20 +101,24 @@ win->line_numbers.first = first; win->line_numbers.last = last; + char buf[DECIMAL_STR_MAX(unsigned long) + 1]; + size_t width = win->line_numbers.width; + BUG_ON(width > sizeof(buf)); + BUG_ON(width < LINE_NUMBERS_MIN_WIDTH); term_output_reset(win->x, win->w, 0); set_builtin_color(BC_LINENUMBER); - for (int i = 0; i < win->edit_h; i++) { - long line = v->vy + i + 1; - int w = win->line_numbers.width - 1; - char buf[32]; - - if (line > lines) { - xsnprintf(buf, sizeof(buf), "%*s ", w, ""); - } else { - xsnprintf(buf, sizeof(buf), "%*ld ", w, line); + + for (int y = 0, h = win->edit_h, edit_y = win->edit_y; y < h; y++) { + unsigned long line = v->vy + y + 1; + memset(buf, ' ', width); + if (line <= lines) { + size_t i = width - 2; + do { + buf[i--] = (line % 10) + '0'; + } while (line /= 10); } - terminal.move_cursor(x, win->edit_y + i); - term_add_bytes(buf, win->line_numbers.width); + term_move_cursor(x, edit_y + y); + term_add_bytes(buf, width); } } diff -Nru dte-1.9.1/src/screen-cmdline.c dte-1.10/src/screen-cmdline.c --- dte-1.9.1/src/screen-cmdline.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen-cmdline.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,21 +1,20 @@ #include "screen.h" -#include "debug.h" #include "editor.h" #include "error.h" #include "search.h" #include "terminal/output.h" #include "terminal/terminal.h" +#include "util/debug.h" #include "util/utf8.h" static void print_message(const char *msg, bool is_error) { - enum builtin_color c = BC_COMMANDLINE; + BuiltinColorEnum c = BC_COMMANDLINE; if (msg[0]) { c = is_error ? BC_ERRORMSG : BC_INFOMSG; } set_builtin_color(c); - size_t i = 0; - while (msg[i]) { + for (size_t i = 0; msg[i];) { CodePoint u = u_get_char(msg, i + 4, &i); if (!term_put_char(u)) { break; @@ -26,12 +25,12 @@ void show_message(const char *msg, bool is_error) { term_output_reset(0, terminal.width, 0); - terminal.move_cursor(0, terminal.height - 1); + term_move_cursor(0, terminal.height - 1); print_message(msg, is_error); term_clear_eol(); } -size_t print_command(char prefix) +static size_t print_command(char prefix) { CodePoint u; @@ -70,7 +69,7 @@ { char prefix = ':'; term_output_reset(0, terminal.width, 0); - terminal.move_cursor(0, terminal.height - 1); + term_move_cursor(0, terminal.height - 1); switch (editor.input_mode) { case INPUT_NORMAL: { bool msg_is_error; @@ -79,13 +78,13 @@ break; } case INPUT_SEARCH: - prefix = current_search_direction() == SEARCH_FWD ? '/' : '?'; + prefix = get_search_direction() == SEARCH_FWD ? '/' : '?'; // fallthrough case INPUT_COMMAND: editor.cmdline_x = print_command(prefix); break; - case INPUT_GIT_OPEN: - break; + default: + BUG("unhandled input mode"); } term_clear_eol(); } diff -Nru dte-1.9.1/src/screen.h dte-1.10/src/screen.h --- dte-1.9.1/src/screen.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,9 +2,8 @@ #define SCREEN_H #include -#include #include "buffer.h" -#include "terminal/color.h" +#include "syntax/color.h" #include "view.h" #include "window.h" @@ -15,12 +14,11 @@ void update_line_numbers(Window *win, bool force); void update_screen_size(void); void set_color(const TermColor *color); -void set_builtin_color(enum builtin_color c); +void set_builtin_color(BuiltinColorEnum c); void mask_color(TermColor *color, const TermColor *over); // screen-cmdline.c void update_command_line(void); -size_t print_command(char prefix); void show_message(const char *msg, bool is_error); // screen-tabbar.c diff -Nru dte-1.9.1/src/screen-status.c dte-1.10/src/screen-status.c --- dte-1.9.1/src/screen-status.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen-status.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,7 +2,7 @@ #include "editor.h" #include "selection.h" #include "terminal/output.h" -#include "terminal/terminal.h" +#include "util/debug.h" #include "util/utf8.h" #include "util/xsnprintf.h" @@ -10,11 +10,13 @@ char *buf; size_t size; size_t pos; - bool separator; + size_t separator; const Window *win; const char *misc_status; } Formatter; +#define add_status_literal(f, s) add_status_bytes(f, s, STRLEN(s)) + static void add_ch(Formatter *f, char ch) { f->buf[f->pos++] = ch; @@ -22,15 +24,16 @@ static void add_separator(Formatter *f) { - if (f->separator && f->pos < f->size) { + while (f->separator && f->pos < f->size) { add_ch(f, ' '); + f->separator--; } - f->separator = false; } static void add_status_str(Formatter *f, const char *str) { - if (!*str) { + BUG_ON(!str); + if (!str[0]) { return; } add_separator(f); @@ -40,15 +43,30 @@ } } +static void add_status_bytes(Formatter *f, const char *str, size_t len) +{ + if (len == 0) { + return; + } + add_separator(f); + if (f->pos >= f->size) { + return; + } + const size_t avail = f->size - f->pos; + len = MIN(len, avail); + memcpy(f->buf + f->pos, str, len); + f->pos += len; +} + PRINTF(2) static void add_status_format(Formatter *f, const char *format, ...) { char buf[1024]; va_list ap; va_start(ap, format); - xvsnprintf(buf, sizeof(buf), format, ap); + size_t len = xvsnprintf(buf, sizeof(buf), format, ap); va_end(ap); - add_status_str(f, buf); + add_status_bytes(f, buf, len); } static void add_status_pos(Formatter *f) @@ -56,17 +74,16 @@ size_t lines = f->win->view->buffer->nl; int h = f->win->edit_h; long pos = f->win->view->vy; - if (lines <= h) { if (pos) { - add_status_str(f, "Bot"); + add_status_literal(f, "Bot"); } else { - add_status_str(f, "All"); + add_status_literal(f, "All"); } } else if (pos == 0) { - add_status_str(f, "Top"); + add_status_literal(f, "Top"); } else if (pos + h - 1 >= lines) { - add_status_str(f, "Bot"); + add_status_literal(f, "Bot"); } else { const long d = lines - (h - 1); add_status_format(f, "%2ld%%", (pos * 100 + d / 2) / d); @@ -80,7 +97,7 @@ f->buf = buf; f->size = size - 5; // Max length of char and terminating NUL f->pos = 0; - f->separator = false; + f->separator = 0; CodePoint u; bool got_char = block_iter_get_char(&v->cursor, &u) > 0; @@ -93,17 +110,25 @@ } ch = *format++; switch (ch) { + case 'b': + if (v->buffer->bom) { + add_status_literal(f, "BOM"); + } + break; case 'f': add_status_str(f, buffer_filename(v->buffer)); break; case 'm': if (buffer_modified(v->buffer)) { - add_status_str(f, "*"); + add_separator(f); + add_ch(f, '*'); } break; case 'r': if (v->buffer->readonly) { - add_status_str(f, "RO"); + add_status_literal(f, "RO"); + } else if (v->buffer->temporary) { + add_status_literal(f, "TMP"); } break; case 'y': @@ -127,24 +152,28 @@ case 'E': add_status_str(f, v->buffer->encoding.name); break; - case 'M': { - if (f->misc_status != NULL) { + case 'M': + if (f->misc_status) { add_status_str(f, f->misc_status); } break; - } + case 'N': + if (v->buffer->crlf_newlines) { + add_status_literal(f, "CRLF"); + } + break; case 'n': - switch (v->buffer->newline) { - case NEWLINE_UNIX: - add_status_str(f, "LF"); - break; - case NEWLINE_DOS: - add_status_str(f, "CRLF"); - break; + if (v->buffer->crlf_newlines) { + add_status_literal(f, "CRLF"); + } else { + add_status_literal(f, "LF"); } break; + case 'S': + f->separator = 3; + break; case 's': - f->separator = true; + f->separator = 1; break; case 't': add_status_str(f, v->buffer->options.filetype); @@ -154,17 +183,19 @@ if (u_is_unicode(u)) { add_status_format(f, "U+%04X", u); } else { - add_status_str(f, "Invalid"); + add_status_literal(f, "Invalid"); } } break; case '%': add_separator(f); - add_ch(f, ch); + add_ch(f, '%'); break; case '\0': f->buf[f->pos] = '\0'; return; + default: + BUG("should be unreachable, due to validate_statusline_format()"); } } f->buf[f->pos] = '\0'; @@ -181,6 +212,7 @@ case CSS_AUTO: return "[case-sensitive = auto]"; } + BUG("unhandled case sensitivity type"); return NULL; } @@ -188,7 +220,7 @@ return NULL; } - static char buf[32]; + static char buf[sizeof("[n chars]") + DECIMAL_STR_MAX(size_t)]; SelectionInfo si; init_selection(win->view, &si); @@ -200,9 +232,11 @@ xsnprintf(buf, sizeof(buf), "[%zu lines]", get_nr_selected_lines(&si)); return buf; case SELECT_NONE: + // Already handled above break; } + BUG("unhandled selection type"); return NULL; } @@ -218,7 +252,7 @@ sf_format(&f, rbuf, sizeof(rbuf), editor.options.statusline_right); term_output_reset(win->x, win->w, 0); - terminal.move_cursor(win->x, win->y + win->h - 1); + term_move_cursor(win->x, win->y + win->h - 1); set_builtin_color(BC_STATUSLINE); size_t lw = u_str_width(lbuf); size_t rw = u_str_width(rbuf); @@ -231,7 +265,7 @@ // Both would fit separately, draw overlapping term_add_str(lbuf); obuf.x = win->w - rw; - terminal.move_cursor(win->x + win->w - rw, win->y + win->h - 1); + term_move_cursor(win->x + win->w - rw, win->y + win->h - 1); term_add_str(rbuf); } else if (lw <= win->w) { // Left fits diff -Nru dte-1.9.1/src/screen-tabbar.c dte-1.10/src/screen-tabbar.c --- dte-1.9.1/src/screen-tabbar.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen-tabbar.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,20 +1,19 @@ #include "screen.h" -#include "debug.h" #include "editor.h" #include "terminal/output.h" -#include "terminal/terminal.h" +#include "util/debug.h" +#include "util/numtostr.h" #include "util/strtonum.h" #include "util/utf8.h" -#include "util/xsnprintf.h" -static int tab_title_width(int number, const char *filename) +static size_t tab_title_width(size_t tab_number, const char *filename) { - return 3 + number_width(number) + u_str_width(filename); + return 3 + size_str_width(tab_number) + u_str_width(filename); } -static void update_tab_title_width(View *v, int tab_number) +static void update_tab_title_width(View *v, size_t tab_number) { - int w = tab_title_width(tab_number, buffer_filename(v->buffer)); + size_t w = tab_title_width(tab_number, buffer_filename(v->buffer)); v->tt_width = w; v->tt_truncated_width = w; } @@ -129,182 +128,61 @@ win->first_tab_idx = 0; } -static void print_horizontal_tab_title(const View *v, size_t idx) +static void print_tab_title(const View *v, size_t idx) { - int skip = v->tt_width - v->tt_truncated_width; const char *filename = buffer_filename(v->buffer); + int skip = v->tt_width - v->tt_truncated_width; if (skip > 0) { filename += u_skip_chars(filename, &skip); } - char buf[16]; - xsnprintf ( - buf, - sizeof(buf), - "%c%zu%c", - obuf.x == 0 && idx > 0 ? '<' : ' ', - idx + 1, - buffer_modified(v->buffer) ? '+' : ':' - ); - - if (v == v->window->view) { - set_builtin_color(BC_ACTIVETAB); - } else { - set_builtin_color(BC_INACTIVETAB); - } - - term_add_str(buf); + const char *tab_number = uint_to_str((unsigned int)idx + 1); + bool is_active_tab = (v == v->window->view); + bool is_modified = buffer_modified(v->buffer); + bool left_overflow = (obuf.x == 0 && idx > 0); + + set_builtin_color(is_active_tab ? BC_ACTIVETAB : BC_INACTIVETAB); + term_put_char(left_overflow ? '<' : ' '); + term_add_str(tab_number); + term_put_char(is_modified ? '+' : ':'); term_add_str(filename); - if (obuf.x == obuf.width - 1 && idx < v->window->views.count - 1) { - term_put_char('>'); - } else { - term_put_char(' '); - } + size_t ntabs = v->window->views.count; + bool right_overflow = (obuf.x == (obuf.width - 1) && idx < (ntabs - 1)); + term_put_char(right_overflow ? '>' : ' '); } -static void print_horizontal_tabbar(Window *win) +void print_tabbar(Window *win) { - term_output_reset(win->x, win->w, 0); - terminal.move_cursor(win->x, win->y); + if (!editor.options.tab_bar) { + return; + } + term_output_reset(win->x, win->w, 0); + term_move_cursor(win->x, win->y); calculate_tabbar(win); - size_t i; - for (i = win->first_tab_idx; i < win->views.count; i++) { + + size_t i = win->first_tab_idx; + size_t n = win->views.count; + for (; i < n; i++) { const View *v = win->views.ptrs[i]; if (obuf.x + v->tt_truncated_width > win->w) { break; } - print_horizontal_tab_title(v, i); + print_tab_title(v, i); } - set_builtin_color(BC_TABBAR); - if (i != win->views.count) { - while (obuf.x < obuf.width - 1) { - term_put_char(' '); - } - if (obuf.x == obuf.width - 1) { - term_put_char('>'); - } - } else { - term_clear_eol(); - } -} -static void print_vertical_tab_title(const View *v, int idx, int width) -{ - const char *orig_filename = buffer_filename(v->buffer); - const char *filename = orig_filename; - unsigned int max = editor.options.tab_bar_max_components; - char buf[16]; - - xsnprintf ( - buf, - sizeof(buf), - "%2d%s", - idx + 1, - buffer_modified(v->buffer) ? "+" : " " - ); - if (max) { - int count = 1; - for (size_t i = 0; filename[i]; i++) { - if (filename[i] == '/') { - count++; - } - } - // Ignore first slash because it does not separate components - if (filename[0] == '/') { - count--; - } - - if (count > max) { - // Skip possible first slash - size_t i; - for (i = 1; ; i++) { - if (filename[i] == '/' && --count == max) { - i++; - break; - } - } - filename += i; - } - } else { - int skip = strlen(buf) + u_str_width(filename) - width + 1; - if (skip > 0) { - filename += u_skip_chars(filename, &skip); - } - } - if (filename != orig_filename) { - // filename was shortened. Add "<<" symbol. - size_t i = strlen(buf); - u_set_char(buf, &i, 0xab); - buf[i] = '\0'; - } - - if (v == v->window->view) { - set_builtin_color(BC_ACTIVETAB); - } else { - set_builtin_color(BC_INACTIVETAB); - } - term_add_str(buf); - term_add_str(filename); - term_clear_eol(); -} - -static void print_vertical_tabbar(Window *win) -{ - int width = vertical_tabbar_width(win); - int h = win->edit_h; - size_t cur_idx = 0; - - for (size_t i = 0; i < win->views.count; i++) { - if (win->view == win->views.ptrs[i]) { - cur_idx = i; - break; - } - } - if (win->views.count <= h) { - // All tabs fit - win->first_tab_idx = 0; - } else { - size_t max_y = win->first_tab_idx + h - 1; - if (win->first_tab_idx > cur_idx) { - win->first_tab_idx = cur_idx; - } - if (cur_idx > max_y) { - win->first_tab_idx += cur_idx - max_y; - } - } - - term_output_reset(win->x, width, 0); - int n = h; - if (n + win->first_tab_idx > win->views.count) { - n = win->views.count - win->first_tab_idx; - } - size_t i; - for (i = 0; i < n; i++) { - size_t idx = win->first_tab_idx + i; - obuf.x = 0; - terminal.move_cursor(win->x, win->y + i); - print_vertical_tab_title(win->views.ptrs[idx], idx, width); - } set_builtin_color(BC_TABBAR); - for (; i < h; i++) { - obuf.x = 0; - terminal.move_cursor(win->x, win->y + i); + + if (i == n) { term_clear_eol(); + return; } -} -void print_tabbar(Window *win) -{ - switch (tabbar_visibility(win)) { - case TAB_BAR_HORIZONTAL: - print_horizontal_tabbar(win); - break; - case TAB_BAR_VERTICAL: - print_vertical_tabbar(win); - break; - default: - break; + while (obuf.x < obuf.width - 1) { + term_put_char(' '); + } + if (obuf.x == obuf.width - 1) { + term_put_char('>'); } } diff -Nru dte-1.9.1/src/screen-view.c dte-1.10/src/screen-view.c --- dte-1.9.1/src/screen-view.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/screen-view.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,11 +1,12 @@ #include "screen.h" -#include "debug.h" #include "editor.h" #include "selection.h" #include "syntax/highlight.h" #include "terminal/output.h" #include "terminal/terminal.h" #include "util/ascii.h" +#include "util/debug.h" +#include "util/str-util.h" #include "util/utf8.h" typedef struct { @@ -20,12 +21,12 @@ size_t pos; size_t indent_size; size_t trailing_ws_offset; - HlColor **colors; + TermColor **colors; } LineInfo; static bool is_default_bg_color(int32_t color) { - return color == builtin_colors[BC_DEFAULT]->bg || color < 0; + return color == builtin_colors[BC_DEFAULT].bg || color < 0; } // Like mask_color() but can change bg color only if it has not been changed yet @@ -47,27 +48,23 @@ TermColor *color ) { if (info->offset >= info->sel_so && info->offset < info->sel_eo) { - mask_color(color, builtin_colors[BC_SELECTION]); + mask_color(color, &builtin_colors[BC_SELECTION]); } else if (info->line_nr == info->view->cy) { - mask_color2(color, builtin_colors[BC_CURRENTLINE]); + mask_color2(color, &builtin_colors[BC_CURRENTLINE]); } } static bool is_non_text(CodePoint u) { - if (u < 0x20) { - return u != '\t' || editor.options.display_special; - } - if (u == 0x7f) { - return true; + if (u == '\t') { + return editor.options.display_special; } - return u_is_unprintable(u); + return u < 0x20 || u == 0x7F || u_is_unprintable(u); } -static int get_ws_error_option(const Buffer *b) +static unsigned int get_ws_error_option(const Buffer *b) { - int flags = b->options.ws_error; - + unsigned int flags = b->options.ws_error; if (flags & WSE_AUTO_INDENT) { if (b->options.expand_tab) { flags |= WSE_TAB_AFTER_INDENT | WSE_TAB_INDENT; @@ -81,60 +78,55 @@ static bool whitespace_error(const LineInfo *info, CodePoint u, size_t i) { const View *v = info->view; - int flags = get_ws_error_option(v->buffer); - - if (i >= info->trailing_ws_offset && flags & WSE_TRAILING) { + const unsigned int flags = get_ws_error_option(v->buffer); + const unsigned int trailing = flags & (WSE_TRAILING | WSE_ALL_TRAILING); + if (i >= info->trailing_ws_offset && trailing) { // Trailing whitespace - if (info->line_nr != v->cy || v->cx < info->trailing_ws_offset) { + if ( + // Cursor is not on this line + info->line_nr != v->cy + // or is positioned before any trailing whitespace + || v->cx < info->trailing_ws_offset + // or user explicitly wants trailing space under cursor highlighted + || flags & WSE_ALL_TRAILING + ) { return true; } - // Cursor is on this line and on the whitespace or at eol. It would - // be annoying if the line you are editing displays trailing - // whitespace as an error. } + bool in_indent = (i < info->indent_size); if (u == '\t') { - if (i < info->indent_size) { - // In indentation - if (flags & WSE_TAB_INDENT) { - return true; - } - } else { - if (flags & WSE_TAB_AFTER_INDENT) { - return true; - } - } - } else if (i < info->indent_size) { - // Space in indentation - const char *line = info->line; - int count = 0, pos = i; - - while (pos > 0 && line[pos - 1] == ' ') { - pos--; - } - while (pos < info->size && line[pos] == ' ') { - pos++; - count++; - } - - if (count >= v->buffer->options.tab_width) { - // Spaces used instead of tab - if (flags & WSE_SPACE_INDENT) { - return true; - } - } else if (pos < info->size && line[pos] == '\t') { - // Space before tab - if (flags & WSE_SPACE_INDENT) { - return true; - } - } else { - // Less than tab width spaces at end of indentation - if (flags & WSE_SPACE_ALIGN) { - return true; - } - } + unsigned int mask = in_indent ? WSE_TAB_INDENT : WSE_TAB_AFTER_INDENT; + return (flags & mask) != 0; } - return false; + if (!in_indent) { + // All checks below here only apply to indentation + return false; + } + + const char *line = info->line; + size_t pos = i; + size_t count = 0; + while (pos > 0 && line[pos - 1] == ' ') { + pos--; + } + while (pos < info->size && line[pos] == ' ') { + pos++; + count++; + } + + unsigned int mask; + if (count >= v->buffer->options.tab_width) { + // Spaces used instead of tab + mask = WSE_SPACE_INDENT; + } else if (pos < info->size && line[pos] == '\t') { + // Space before tab + mask = WSE_SPACE_INDENT; + } else { + // Less than tab width spaces at end of indentation + mask = WSE_SPACE_ALIGN; + } + return (flags & mask) != 0; } static CodePoint screen_next_char(LineInfo *info) @@ -144,7 +136,7 @@ TermColor color; bool ws_error = false; - if (u < 0x80) { + if (likely(u < 0x80)) { info->pos++; count = 1; if (u == '\t' || u == ' ') { @@ -163,15 +155,15 @@ } if (info->colors && info->colors[pos]) { - color = info->colors[pos]->color; + color = *info->colors[pos]; } else { - color = *builtin_colors[BC_DEFAULT]; + color = builtin_colors[BC_DEFAULT]; } if (is_non_text(u)) { - mask_color(&color, builtin_colors[BC_NONTEXT]); + mask_color(&color, &builtin_colors[BC_NONTEXT]); } if (ws_error) { - mask_color(&color, builtin_colors[BC_WSERROR]); + mask_color(&color, &builtin_colors[BC_WSERROR]); } mask_selection_and_current_line(info, &color); set_color(&color); @@ -184,8 +176,8 @@ { CodePoint u = info->line[info->pos++]; info->offset++; - if (u < 0x80) { - if (!ascii_iscntrl(u)) { + if (likely(u < 0x80)) { + if (likely(!ascii_iscntrl(u))) { obuf.x++; } else if (u == '\t' && obuf.tab != TAB_CONTROL) { obuf.x += (obuf.x + obuf.tab_width) / obuf.tab_width * obuf.tab_width - obuf.x; @@ -205,9 +197,9 @@ static bool is_notice(const char *word, size_t len) { switch (len) { - case 3: return !memcmp(word, "XXX", 3); - case 4: return !memcmp(word, "TODO", 4); - case 5: return !memcmp(word, "FIXME", 5); + case 3: return mem_equal(word, "XXX", 3); + case 4: return mem_equal(word, "TODO", 4); + case 5: return mem_equal(word, "FIXME", 5); } return false; } @@ -215,10 +207,10 @@ // Highlight certain words inside comments static void hl_words(const LineInfo *info) { - HlColor *cc = find_color("comment"); - HlColor *nc = find_color("notice"); + TermColor *cc = find_color("comment"); + TermColor *nc = find_color("notice"); - if (info->colors == NULL || cc == NULL || nc == NULL) { + if (!info->colors || !cc || !nc) { return; } @@ -290,14 +282,14 @@ static void line_info_set_line ( LineInfo *info, - const LineRef *lr, - HlColor **colors + const StringView *line, + TermColor **colors ) { - BUG_ON(lr->size == 0); - BUG_ON(lr->line[lr->size - 1] != '\n'); + BUG_ON(line->length == 0); + BUG_ON(line->data[line->length - 1] != '\n'); - info->line = lr->line; - info->size = lr->size - 1; + info->line = line->data; + info->size = line->length - 1; info->pos = 0; info->colors = colors; @@ -312,7 +304,8 @@ info->indent_size = i; } - info->trailing_ws_offset = INT_MAX; + static_assert_compatible_types(info->trailing_ws_offset, size_t); + info->trailing_ws_offset = SIZE_MAX; for (ssize_t i = info->size - 1; i >= 0; i--) { char ch = info->line[i]; if (ch != '\t' && ch != ' ') { @@ -349,14 +342,14 @@ TermColor color; if (editor.options.display_special && obuf.x >= obuf.scroll_x) { // Syntax highlighter highlights \n but use default color anyway - color = *builtin_colors[BC_DEFAULT]; - mask_color(&color, builtin_colors[BC_NONTEXT]); + color = builtin_colors[BC_DEFAULT]; + mask_color(&color, &builtin_colors[BC_NONTEXT]); mask_selection_and_current_line(info, &color); set_color(&color); term_put_char('$'); } - color = *builtin_colors[BC_DEFAULT]; + color = builtin_colors[BC_DEFAULT]; mask_selection_and_current_line(info, &color); set_color(&color); info->offset++; @@ -394,13 +387,13 @@ long i; for (i = y1; got_line && i < y2; i++) { obuf.x = 0; - terminal.move_cursor(edit_x, edit_y + i); + term_move_cursor(edit_x, edit_y + i); - LineRef lr; - fill_line_nl_ref(&bi, &lr); + StringView line; + fill_line_nl_ref(&bi, &line); bool next_changed; - HlColor **colors = hl_line(v->buffer, &lr, info.line_nr, &next_changed); - line_info_set_line(&info, &lr, colors); + TermColor **colors = hl_line(v->buffer, &line, info.line_nr, &next_changed); + line_info_set_line(&info, &line, colors); print_line(&info); got_line = !!block_iter_next_line(&bi); @@ -416,13 +409,13 @@ if (i < y2 && info.line_nr == v->cy) { // Dummy empty line is shown only if cursor is on it - TermColor color = *builtin_colors[BC_DEFAULT]; + TermColor color = builtin_colors[BC_DEFAULT]; obuf.x = 0; - mask_color2(&color, builtin_colors[BC_CURRENTLINE]); + mask_color2(&color, &builtin_colors[BC_CURRENTLINE]); set_color(&color); - terminal.move_cursor(edit_x, edit_y + i++); + term_move_cursor(edit_x, edit_y + i++); term_clear_eol(); } @@ -431,7 +424,7 @@ } for (; i < y2; i++) { obuf.x = 0; - terminal.move_cursor(edit_x, edit_y + i); + term_move_cursor(edit_x, edit_y + i); term_put_char('~'); term_clear_eol(); } diff -Nru dte-1.9.1/src/search.c dte-1.10/src/search.c --- dte-1.9.1/src/search.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/search.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,6 @@ #include "search.h" #include "buffer.h" #include "change.h" -#include "edit.h" #include "editor.h" #include "error.h" #include "regexp.h" @@ -11,7 +10,12 @@ #include "util/xmalloc.h" #include "view.h" -#define MAX_SUBSTRINGS 32 +static struct { + regex_t regex; + char *pattern; + SearchDirection direction; + int re_flags; // If zero, regex hasn't been compiled +} current_search; static bool do_search_fwd(regex_t *regex, BlockIter *bi, bool skip) { @@ -19,25 +23,25 @@ do { regmatch_t match; - LineRef lr; + StringView line; if (block_iter_is_eof(bi)) { return false; } - fill_line_ref(bi, &lr); + fill_line_ref(bi, &line); - // NOTE: If this is the first iteration then lr.line contains + // NOTE: If this is the first iteration then line.data contains // partial line (text starting from the cursor position) and // if match.rm_so is 0 then match is at beginning of the text // which is same as the cursor position. - if (regexp_exec(regex, lr.line, lr.size, 1, &match, flags)) { + if (regexp_exec(regex, line.data, line.length, 1, &match, flags)) { if (skip && match.rm_so == 0) { // Ignore match at current cursor position regoff_t count = match.rm_eo; if (count == 0) { // It is safe to skip one byte because every line - // has one extra byte (newline) that is not in lr.line + // has one extra byte (newline) that is not in line.data count = 1; } block_iter_skip_bytes(bi, (size_t)count); @@ -64,15 +68,15 @@ do { regmatch_t match; - LineRef lr; + StringView line; int flags = 0; regoff_t offset = -1; regoff_t pos = 0; - fill_line_ref(bi, &lr); + fill_line_ref(bi, &line); while ( - pos <= lr.size - && regexp_exec(regex, lr.line + pos, lr.size - pos, 1, &match, flags) + pos <= line.length + && regexp_exec(regex, line.data + pos, line.length - pos, 1, &match, flags) ) { flags = REG_NOTBOL; if (cx >= 0) { @@ -111,13 +115,15 @@ bool search_tag(const char *pattern, bool *err) { - BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks); regex_t regex; bool found = false; - if (!regexp_compile_basic(®ex, pattern, REG_NEWLINE)) { *err = true; - } else if (do_search_fwd(®ex, &bi, false)) { + return found; + } + + BlockIter bi = BLOCK_ITER_INIT(&buffer->blocks); + if (do_search_fwd(®ex, &bi, false)) { view->center_on_scroll = true; found = true; } else { @@ -126,29 +132,26 @@ error_msg("Tag not found."); *err = true; } + regfree(®ex); return found; } -static struct { - regex_t regex; - char *pattern; - SearchDirection direction; - - // If zero then regex hasn't been compiled - int re_flags; -} current_search; - -void search_set_direction(SearchDirection dir) +void set_search_direction(SearchDirection dir) { current_search.direction = dir; } -SearchDirection current_search_direction(void) +SearchDirection get_search_direction(void) { return current_search.direction; } +void toggle_search_direction(void) +{ + current_search.direction ^= 1; +} + static void free_regex(void) { if (current_search.re_flags) { @@ -230,7 +233,7 @@ if (do_search_fwd(¤t_search.regex, &bi, false)) { info_msg("Continuing at top."); } else { - info_msg("Pattern '%s' not found.", current_search.pattern); + error_msg("Pattern '%s' not found.", current_search.pattern); } } else { size_t cursor_x = block_iter_bol(&bi); @@ -242,7 +245,7 @@ if (do_search_bwd(¤t_search.regex, &bi, -1, false)) { info_msg("Continuing at bottom."); } else { - info_msg("Pattern '%s' not found.", current_search.pattern); + error_msg("Pattern '%s' not found.", current_search.pattern); } } } @@ -271,27 +274,25 @@ regmatch_t *m ) { size_t i = 0; - while (format[i]) { - int ch = format[i++]; - + char ch = format[i++]; if (ch == '\\') { if (format[i] >= '1' && format[i] <= '9') { int n = format[i++] - '0'; int len = m[n].rm_eo - m[n].rm_so; if (len > 0) { - string_add_buf(buf, line + m[n].rm_so, len); + string_append_buf(buf, line + m[n].rm_so, len); } } else if (format[i] != '\0') { - string_add_byte(buf, format[i++]); + string_append_byte(buf, format[i++]); } } else if (ch == '&') { int len = m[0].rm_eo - m[0].rm_so; if (len > 0) { - string_add_buf(buf, line + m[0].rm_so, len); + string_append_buf(buf, line + m[0].rm_so, len); } } else { - string_add_byte(buf, ch); + string_append_byte(buf, ch); } } } @@ -305,13 +306,17 @@ * "foo x bar abc baz" " bar abc baz" */ static unsigned int replace_on_line ( - LineRef *lr, + StringView *line, regex_t *re, const char *format, BlockIter *bi, ReplaceFlags *flagsp ) { - unsigned char *buf = (unsigned char *)lr->line; + enum { + MAX_SUBSTRINGS = 32 + }; + + unsigned char *buf = (unsigned char *)line->data; ReplaceFlags flags = *flagsp; regmatch_t m[MAX_SUBSTRINGS]; size_t pos = 0; @@ -321,7 +326,7 @@ while (regexp_exec ( re, buf + pos, - lr->size - pos, + line->length - pos, MAX_SUBSTRINGS, m, eflags @@ -334,7 +339,7 @@ view->cursor = *bi; if (flags & REPLACE_CONFIRM) { - switch (get_confirmation("Ynaq", "Replace?")) { + switch (status_prompt("Replace? [Y/n/a/q]", "ynaq")) { case 'y': break; case 'n': @@ -359,12 +364,11 @@ block_iter_skip_bytes(&view->cursor, match_len); } else { String b = STRING_INIT; - build_replacement(&b, buf + pos, format, m); - // lineref is invalidated by modification - if (buf == lr->line && lr->size != 0) { - buf = xmemdup(buf, lr->size); + // line ref is invalidated by modification + if (buf == line->data && line->length != 0) { + buf = xmemdup(buf, line->length); } buffer_replace_bytes(match_len, b.buffer, b.len); @@ -396,7 +400,7 @@ eflags = REG_NOTBOL; } out: - if (buf != lr->line) { + if (buf != line->data) { free(buf); } return nr; @@ -453,17 +457,17 @@ while (1) { // Number of bytes to process size_t count; - LineRef lr; + StringView line; unsigned int nr; - fill_line_ref(&bi, &lr); - count = lr.size; - if (lr.size > nr_bytes) { + fill_line_ref(&bi, &line); + count = line.length; + if (line.length > nr_bytes) { // End of selection is not full line - lr.size = nr_bytes; + line.length = nr_bytes; } - nr = replace_on_line(&lr, &re, format, &bi, &flags); + nr = replace_on_line(&line, &re, format, &bi, &flags); if (nr) { nr_substitutions += nr; nr_lines++; @@ -488,7 +492,7 @@ if (nr_substitutions) { info_msg("%u substitutions on %zu lines.", nr_substitutions, nr_lines); } else if (!(flags & REPLACE_CANCEL)) { - info_msg("Pattern '%s' not found.", pattern); + error_msg("Pattern '%s' not found.", pattern); } if (view->selection) { diff -Nru dte-1.9.1/src/search.h dte-1.10/src/search.h --- dte-1.9.1/src/search.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/search.h 2021-04-03 21:08:53.000000000 +0000 @@ -17,16 +17,16 @@ REPLACE_CANCEL = 1 << 4, } ReplaceFlags; -bool search_tag(const char *pattern, bool *err); +SearchDirection get_search_direction(void) PURE; +void set_search_direction(SearchDirection dir); +void toggle_search_direction(void); -void search_set_direction(SearchDirection dir); -SearchDirection current_search_direction(void) PURE; +bool search_tag(const char *pattern, bool *err); void search_set_regexp(const char *pattern); void search_prev(void); void search_next(void); void search_next_word(void); -NONNULL_ARGS -void reg_replace(const char *pattern, const char *format, ReplaceFlags flags); +void reg_replace(const char *pattern, const char *format, ReplaceFlags flags) NONNULL_ARGS; #endif diff -Nru dte-1.9.1/src/selection.c dte-1.10/src/selection.c --- dte-1.9.1/src/selection.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/selection.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,5 +1,6 @@ #include "selection.h" -#include "buffer.h" +#include "editor.h" +#include "util/unicode.h" void init_selection(const View *v, SelectionInfo *info) { @@ -31,8 +32,10 @@ info->so -= block_iter_bol(&info->si); info->eo += block_iter_eat_line(&ei); } else { - // Character under cursor belongs to the selection - info->eo += block_iter_next_column(&ei); + if (editor.options.select_cursor_char) { + // Character under cursor belongs to the selection + info->eo += block_iter_next_column(&ei); + } } } diff -Nru dte-1.9.1/src/selection.h dte-1.10/src/selection.h --- dte-1.9.1/src/selection.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/selection.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,6 +1,9 @@ #ifndef SELECTION_H #define SELECTION_H +#include +#include +#include "block-iter.h" #include "view.h" typedef struct { diff -Nru dte-1.9.1/src/show.c dte-1.10/src/show.c --- dte-1.9.1/src/show.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/show.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,308 @@ +#include +#include +#include +#include "show.h" +#include "alias.h" +#include "bind.h" +#include "block.h" +#include "buffer.h" +#include "change.h" +#include "cmdline.h" +#include "command/env.h" +#include "command/macro.h" +#include "commands.h" +#include "compiler.h" +#include "completion.h" +#include "config.h" +#include "editor.h" +#include "encoding.h" +#include "error.h" +#include "file-option.h" +#include "filetype.h" +#include "frame.h" +#include "options.h" +#include "syntax/color.h" +#include "terminal/color.h" +#include "terminal/key.h" +#include "util/bsearch.h" +#include "util/hashset.h" +#include "util/str-util.h" +#include "util/string.h" +#include "util/unicode.h" +#include "util/xmalloc.h" +#include "util/xsnprintf.h" +#include "view.h" +#include "window.h" + +static void open_temporary_buffer ( + const char *text, + size_t text_len, + const char *cmd, + const char *cmd_arg, + bool dterc_syntax +) { + View *v = window_open_new_file(window); + v->buffer->temporary = true; + do_insert(text, text_len); + set_display_filename(v->buffer, xasprintf("(%s %s)", cmd, cmd_arg)); + buffer_set_encoding(v->buffer, encoding_from_type(UTF8)); + if (dterc_syntax) { + v->buffer->options.filetype = str_intern("dte"); + set_file_options(v->buffer); + buffer_update_syntax(v->buffer); + } +} + +static void show_alias(const char *alias_name, bool cflag) +{ + const char *cmd_str = find_alias(alias_name); + if (!cmd_str) { + if (find_normal_command(alias_name)) { + info_msg("%s is a built-in command, not an alias", alias_name); + } else { + info_msg("%s is not a known alias", alias_name); + } + return; + } + + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, cmd_str); + } else { + info_msg("%s is aliased to: %s", alias_name, cmd_str); + } +} + +static void show_binding(const char *keystr, bool cflag) +{ + KeyCode key; + if (!parse_key_string(&key, keystr)) { + error_msg("invalid key string: %s", keystr); + return; + } + + if (u_is_unicode(key)) { + info_msg("%s is not a bindable key", keystr); + return; + } + + const KeyBinding *b = lookup_binding(key); + if (!b) { + info_msg("%s is not bound to a command", keystr); + return; + } + + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, b->cmd_str); + } else { + info_msg("%s is bound to: %s", keystr, b->cmd_str); + } +} + +static void show_color(const char *color_name, bool cflag) +{ + const TermColor *hl = find_color(color_name); + if (!hl) { + error_msg("no color entry with name '%s'", color_name); + return; + } + + const char *color_str = term_color_to_string(hl); + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, color_str); + } else { + info_msg("color '%s' is set to: %s", color_name, color_str); + } +} + +static void show_env(const char *name, bool cflag) +{ + const char *value = getenv(name); + if (!value) { + error_msg("no environment variable with name '%s'", name); + return; + } + + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, value); + } else { + info_msg("$%s is set to: %s", name, value); + } +} + +static String dump_env(void) +{ + extern char **environ; + String buf = string_new(4096); + for (size_t i = 0; environ[i]; i++) { + string_append_cstring(&buf, environ[i]); + string_append_byte(&buf, '\n'); + } + return buf; +} + +static void show_include(const char *name, bool cflag) +{ + const BuiltinConfig *cfg = get_builtin_config(name); + if (!cfg) { + error_msg("no built-in config with name '%s'", name); + return; + } + + const StringView sv = cfg->text; + if (cflag) { + buffer_insert_bytes(sv.data, sv.length); + } else { + open_temporary_buffer(sv.data, sv.length, "builtin", name, false); + } +} + +static void show_compiler(const char *name, bool cflag) +{ + const Compiler *compiler = find_compiler(name); + if (!compiler) { + error_msg("no errorfmt entry found for '%s'", name); + return; + } + + String str = dump_compiler(compiler, name); + if (cflag) { + buffer_insert_bytes(str.buffer, str.len); + } else { + open_temporary_buffer(str.buffer, str.len, "errorfmt", name, true); + } + string_free(&str); +} + +static void show_option(const char *name, bool cflag) +{ + const char *value = get_option_value_string(name); + if (!value) { + error_msg("invalid option name: %s", name); + return; + } + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, value); + } else { + info_msg("%s is set to: %s", name, value); + } +} + +static void collect_all_options(const char *prefix) +{ + collect_options(prefix, false, false); +} + +static void show_wsplit(const char *name, bool cflag) +{ + if (!streq(name, "this")) { + error_msg("invalid window: %s", name); + return; + } + + const Window *w = window; + char buf[(4 * DECIMAL_STR_MAX(w->x)) + 4]; + xsnprintf(buf, sizeof buf, "%d,%d %dx%d", w->x, w->y, w->w, w->h); + + if (cflag) { + set_input_mode(INPUT_COMMAND); + cmdline_set_text(&editor.cmdline, buf); + } else { + info_msg("current window dimensions: %s", buf); + } +} + +static String do_history_dump(const History *history) +{ + const size_t nr_entries = history->entries.count; + const size_t size = round_size_to_next_multiple(16 * nr_entries, 4096); + String buf = string_new(size); + size_t n = 0; + for (HistoryEntry *e = history->first; e; e = e->next, n++) { + string_append_cstring(&buf, e->text); + string_append_byte(&buf, '\n'); + } + BUG_ON(n != nr_entries); + return buf; +} + +static String dump_command_history(void) +{ + return do_history_dump(&editor.command_history); +} + +static String dump_search_history(void) +{ + return do_history_dump(&editor.search_history); +} + +typedef struct { + const char name[11]; + bool dumps_dterc_syntax; + void (*show)(const char *name, bool cmdline); + String (*dump)(void); + void (*complete_arg)(const char *prefix); +} ShowHandler; + +static const ShowHandler handlers[] = { + {"alias", true, show_alias, dump_aliases, collect_aliases}, + {"bind", true, show_binding, dump_bindings, collect_bound_keys}, + {"color", true, show_color, dump_hl_colors, collect_hl_colors}, + {"command", true, NULL, dump_command_history, NULL}, + {"env", false, show_env, dump_env, collect_env}, + {"errorfmt", true, show_compiler, dump_compilers, collect_compilers}, + {"ft", true, NULL, dump_ft, NULL}, + {"include", false, show_include, dump_builtin_configs, collect_builtin_configs}, + {"macro", true, NULL, dump_macro, NULL}, + {"option", true, show_option, dump_options, collect_all_options}, + {"search", false, NULL, dump_search_history, NULL}, + {"wsplit", false, show_wsplit, dump_frames, NULL}, +}; + +UNITTEST { + CHECK_BSEARCH_ARRAY(handlers, name, strcmp); +} + +void show(const char *type, const char *key, bool cflag) +{ + const ShowHandler *handler = BSEARCH(type, handlers, (CompareFunction)strcmp); + if (!handler) { + error_msg("invalid argument: '%s'", type); + return; + } + + if (key) { + if (handler->show) { + handler->show(key, cflag); + } else { + error_msg("'show %s' doesn't take extra arguments", type); + } + return; + } + + String str = handler->dump(); + bool dte_syntax = handler->dumps_dterc_syntax; + open_temporary_buffer(str.buffer, str.len, "show", type, dte_syntax); + string_free(&str); +} + +void collect_show_subcommands(const char *prefix) +{ + for (size_t i = 0; i < ARRAY_COUNT(handlers); i++) { + if (str_has_prefix(handlers[i].name, prefix)) { + add_completion(xstrdup(handlers[i].name)); + } + } +} + +void collect_show_subcommand_args(const char *name, const char *arg_prefix) +{ + const ShowHandler *handler = BSEARCH(name, handlers, (CompareFunction)strcmp); + if (handler && handler->complete_arg) { + handler->complete_arg(arg_prefix); + } +} diff -Nru dte-1.9.1/src/show.h dte-1.10/src/show.h --- dte-1.9.1/src/show.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/show.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,11 @@ +#ifndef SHOW_H +#define SHOW_H + +#include +#include "util/macros.h" + +void show(const char *type, const char *key, bool cflag) NONNULL_ARG(1); +void collect_show_subcommands(const char *prefix) NONNULL_ARGS; +void collect_show_subcommand_args(const char *name, const char *arg_prefix) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/spawn.c dte-1.10/src/spawn.c --- dte-1.9.1/src/spawn.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/spawn.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -9,8 +9,10 @@ #include "error.h" #include "msg.h" #include "regexp.h" -#include "terminal/terminal.h" +#include "terminal/mode.h" #include "util/exec.h" +#include "util/macros.h" +#include "util/ptr-array.h" #include "util/string.h" #include "util/strtonum.h" #include "util/xmalloc.h" @@ -18,59 +20,75 @@ static void handle_error_msg(const Compiler *c, char *str) { - size_t i, len; + if (str[0] == '\0' || str[0] == '\n') { + return; + } - for (i = 0; str[i]; i++) { - if (str[i] == '\n') { - str[i] = '\0'; - break; - } - if (str[i] == '\t') { - str[i] = ' '; + size_t str_len = 0; + for (char ch; (ch = str[str_len]); str_len++) { + if (ch == '\t') { + str[str_len] = ' '; } } - len = i; - if (len == 0) { - return; + if (str[str_len - 1] == '\n') { + str[--str_len] = '\0'; } - for (i = 0; i < c->error_formats.count; i++) { + for (size_t i = 0, n = c->error_formats.count; i < n; i++) { const ErrorFormat *p = c->error_formats.ptrs[i]; - PointerArray m = PTR_ARRAY_INIT; - - if (!regexp_exec_sub(&p->re, str, len, &m, 0)) { + regmatch_t m[16]; + if (!regexp_exec(&p->re, str, str_len, ARRAY_COUNT(m), m, 0)) { continue; } - if (!p->ignore) { - Message *msg = new_message(m.ptrs[p->msg_idx]); - msg->loc = xnew0(FileLocation, 1); - if (p->file_idx >= 0) { - msg->loc->filename = xstrdup(m.ptrs[p->file_idx]); - if (p->line_idx >= 0) { - str_to_ulong(m.ptrs[p->line_idx], &msg->loc->line); + + if (p->ignore) { + return; + } + + int8_t mi = p->msg_idx; + if (m[mi].rm_so < 0) { + mi = 0; + } + Message *msg = new_message(str + m[mi].rm_so, m[mi].rm_eo - m[mi].rm_so); + msg->loc = xnew0(FileLocation, 1); + + int8_t fi = p->file_idx; + if (fi >= 0 && m[fi].rm_so >= 0) { + msg->loc->filename = xstrslice(str, m[fi].rm_so, m[fi].rm_eo); + int8_t li = p->line_idx; + if (li >= 0 && m[li].rm_so >= 0) { + size_t len = m[li].rm_eo - m[li].rm_so; + unsigned long val; + size_t parsed_len = buf_parse_ulong(str + m[li].rm_so, len, &val); + if (parsed_len == len) { + msg->loc->line = val; } - if (p->column_idx >= 0) { - str_to_ulong(m.ptrs[p->column_idx], &msg->loc->column); + } + int8_t ci = p->column_idx; + if (ci >= 0 && m[ci].rm_so >= 0) { + size_t len = m[ci].rm_eo - m[ci].rm_so; + unsigned long val; + size_t parsed_len = buf_parse_ulong(str + m[ci].rm_so, len, &val); + if (parsed_len == len) { + msg->loc->column = val; } } - add_message(msg); } - ptr_array_free(&m); + + add_message(msg); return; } - add_message(new_message(str)); + + add_message(new_message(str, str_len)); } static void read_errors(const Compiler *c, int fd, bool quiet) { FILE *f = fdopen(fd, "r"); - char line[4096]; - - if (!f) { - // Should not happen + if (unlikely(!f)) { return; } - + char line[4096]; while (fgets(line, sizeof(line), f)) { if (!quiet) { fputs(line, stderr); @@ -80,13 +98,11 @@ fclose(f); } -static void filter(int rfd, int wfd, FilterData *fdata) +static void filter(int rfd, int wfd, SpawnContext *ctx) { size_t wlen = 0; - String buf = STRING_INIT; - - if (!fdata->in_len) { - close(wfd); + if (!ctx->input.length) { + xclose(wfd); wfd = -1; } while (1) { @@ -110,52 +126,49 @@ if (errno == EINTR) { continue; } - error_msg("select: %s", strerror(errno)); + perror_msg("select"); break; } if (FD_ISSET(rfd, &rfds)) { char data[8192]; - ssize_t rc = read(rfd, data, sizeof(data)); if (rc < 0) { - error_msg("read: %s", strerror(errno)); + perror_msg("read"); break; } if (!rc) { - if (wlen < fdata->in_len) { + if (wlen < ctx->input.length) { error_msg("Command did not read all data."); } break; } - string_add_buf(&buf, data, (size_t) rc); + string_append_buf(&ctx->output, data, (size_t) rc); } + if (wfdsp && FD_ISSET(wfd, &wfds)) { - ssize_t rc = write(wfd, fdata->in + wlen, fdata->in_len - wlen); + ssize_t rc = write(wfd, ctx->input.data + wlen, ctx->input.length - wlen); if (rc < 0) { - error_msg("write: %s", strerror(errno)); + perror_msg("write"); break; } wlen += (size_t) rc; - if (wlen == fdata->in_len) { - if (close(wfd)) { - error_msg("close: %s", strerror(errno)); + if (wlen == ctx->input.length) { + if (xclose(wfd)) { + perror_msg("close"); break; } wfd = -1; } } } - fdata->out = string_steal(&buf, &fdata->out_len); } static int open_dev_null(int flags) { - int fd = open("/dev/null", flags); + int fd = xopen("/dev/null", flags | O_CLOEXEC, 0); if (fd < 0) { error_msg("Error opening /dev/null: %s", strerror(errno)); - } else { - close_on_exec(fd); } return fd; } @@ -164,7 +177,7 @@ { int ret = wait_child(pid); if (ret < 0) { - error_msg("waitpid: %s", strerror(errno)); + perror_msg("waitpid"); } else if (ret >= 256) { error_msg("Child received signal %d", ret >> 8); } else if (ret) { @@ -173,115 +186,225 @@ return ret; } -int spawn_filter(char **argv, FilterData *data) +static void yield_terminal(bool quiet) +{ + if (quiet) { + term_raw_isig(); + } else { + editor.child_controls_terminal = true; + ui_end(); + } +} + +static void resume_terminal(bool quiet, bool prompt) +{ + term_raw(); + if (!quiet && editor.child_controls_terminal) { + if (prompt) { + any_key(); + } + ui_start(); + editor.child_controls_terminal = false; + } +} + +static void exec_error(const char *argv0) +{ + error_msg("Unable to exec '%s': %s", argv0, strerror(errno)); +} + +bool spawn_filter(SpawnContext *ctx) { int p0[2] = {-1, -1}; int p1[2] = {-1, -1}; int dev_null = -1; - - data->out = NULL; - data->out_len = 0; - - if (pipe_close_on_exec(p0) || pipe_close_on_exec(p1)) { - error_msg("pipe: %s", strerror(errno)); + if (!pipe_close_on_exec(p0) || !pipe_close_on_exec(p1)) { + perror_msg("pipe"); goto error; } + dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { goto error; } int fd[3] = {p0[0], p1[1], dev_null}; - const pid_t pid = fork_exec(argv, fd); + term_raw_isig(); + const pid_t pid = fork_exec(ctx->argv, ctx->env, fd); if (pid < 0) { - error_msg("Error: %s", strerror(errno)); + exec_error(ctx->argv[0]); goto error; } - close(dev_null); - close(p0[0]); - close(p1[1]); - filter(p1[0], p0[1], data); - close(p1[0]); - close(p0[1]); - - if (handle_child_error(pid)) { - return -1; + xclose(dev_null); + xclose(p0[0]); + xclose(p1[1]); + filter(p1[0], p0[1], ctx); + xclose(p1[0]); + xclose(p0[1]); + + int err = handle_child_error(pid); + term_raw(); + if (err) { + string_free(&ctx->output); + return false; } - return 0; + return true; + error: - close(p0[0]); - close(p0[1]); - close(p1[0]); - close(p1[1]); - close(dev_null); - return -1; + term_raw(); + xclose(p0[0]); + xclose(p0[1]); + xclose(p1[0]); + xclose(p1[1]); + xclose(dev_null); + return false; } -int spawn_writer(char **argv, const char *text, size_t length) +bool spawn_source(SpawnContext *ctx) { - if (length == 0) { - return 0; + bool quiet = !!(ctx->flags & SPAWN_QUIET); + int p[2] = {-1, -1}; + int dev_null_r = -1; + int dev_null_w = -1; + + if (!pipe_close_on_exec(p)) { + perror_msg("pipe"); + return false; + } + + int fd[3] = {0, p[1], 2}; + if (quiet) { + dev_null_r = open_dev_null(O_RDONLY); + if (dev_null_r < 0) { + goto error; + } + fd[0] = dev_null_r; + dev_null_w = open_dev_null(O_WRONLY); + if (dev_null_w < 0) { + goto error; + } + fd[2] = dev_null_w; + } + + yield_terminal(quiet); + const pid_t pid = fork_exec(ctx->argv, ctx->env, fd); + if (pid < 0) { + exec_error(ctx->argv[0]); + goto error; + } + + if (quiet) { + xclose(dev_null_r); + xclose(dev_null_w); + dev_null_r = dev_null_w = -1; } + xclose(p[1]); + p[1] = -1; + while (1) { + char buf[8192]; + ssize_t rc = xread(p[0], buf, sizeof(buf)); + if (unlikely(rc < 0)) { + perror_msg("read"); + goto error; + } + if (rc == 0) { + break; + } + string_append_buf(&ctx->output, buf, (size_t) rc); + } + + xclose(p[0]); + p[0] = -1; + if (handle_child_error(pid)) { + goto error; + } + + resume_terminal(quiet, false); + return true; + +error: + string_free(&ctx->output); + xclose(p[0]); + xclose(p[1]); + if (quiet) { + xclose(dev_null_r); + xclose(dev_null_w); + } + resume_terminal(quiet, false); + return false; +} + +bool spawn_sink(SpawnContext *ctx) +{ int p[2] = {-1, -1}; int dev_null = -1; - if (pipe_close_on_exec(p)) { - error_msg("pipe: %s", strerror(errno)); + + if (!pipe_close_on_exec(p)) { + perror_msg("pipe"); goto error; } + dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { goto error; } int fd[3] = {p[0], dev_null, dev_null}; - const pid_t pid = fork_exec(argv, fd); + term_raw_isig(); + const pid_t pid = fork_exec(ctx->argv, ctx->env, fd); if (pid < 0) { - error_msg("Error: %s", strerror(errno)); + exec_error(ctx->argv[0]); goto error; } - close(dev_null); - close(p[0]); - if (xwrite(p[1], text, length) < 0) { - error_msg("write: %s", strerror(errno)); - close(p[1]); - return -1; + xclose(dev_null); + xclose(p[0]); + p[0] = dev_null = -1; + size_t input_len = ctx->input.length; + if (input_len && xwrite(p[1], ctx->input.data, input_len) < 0) { + perror_msg("write"); + goto error; } - close(p[1]); - return handle_child_error(pid) ? -1 : 0; + xclose(p[1]); + int err = handle_child_error(pid); + term_raw(); + return !err; error: - close(p[0]); - close(p[1]); - close(dev_null); - return -1; + term_raw(); + xclose(p[0]); + xclose(p[1]); + xclose(dev_null); + return false; } void spawn_compiler(char **args, SpawnFlags flags, const Compiler *c) { - const bool read_stdout = !!(flags & SPAWN_READ_STDOUT); - const bool quiet = !!(flags & SPAWN_QUIET); - bool prompt = !!(flags & SPAWN_PROMPT); - int p[2], fd[3]; - + int fd[3]; fd[0] = open_dev_null(O_RDONLY); if (fd[0] < 0) { return; } + const int dev_null = open_dev_null(O_WRONLY); if (dev_null < 0) { - close(fd[0]); + xclose(fd[0]); return; } - if (pipe_close_on_exec(p)) { - error_msg("pipe: %s", strerror(errno)); - close(dev_null); - close(fd[0]); + + int p[2]; + if (!pipe_close_on_exec(p)) { + perror_msg("pipe"); + xclose(dev_null); + xclose(fd[0]); return; } + const bool read_stdout = !!(flags & SPAWN_READ_STDOUT); + const bool quiet = !!(flags & SPAWN_QUIET); + bool prompt = !!(flags & SPAWN_PROMPT); if (read_stdout) { fd[1] = p[1]; fd[2] = quiet ? dev_null : 2; @@ -290,83 +413,54 @@ fd[2] = p[1]; } - if (!quiet) { - editor.child_controls_terminal = true; - editor.ui_end(); - } - - const pid_t pid = fork_exec(args, fd); + yield_terminal(quiet); + const pid_t pid = fork_exec(args, NULL, fd); if (pid < 0) { - error_msg("Error: %s", strerror(errno)); - close(p[1]); + exec_error(args[0]); + xclose(p[1]); prompt = false; } else { // Must close write end of the pipe before read_errors() or // the read end never gets EOF! - close(p[1]); + xclose(p[1]); read_errors(c, p[0], quiet); handle_child_error(pid); } - if (!quiet) { - terminal.raw(); - if (prompt) { - any_key(); - } - editor.resize(); - editor.child_controls_terminal = false; - } - close(p[0]); - close(dev_null); - close(fd[0]); + resume_terminal(quiet, prompt); + + xclose(p[0]); + xclose(dev_null); + xclose(fd[0]); } -void spawn(char **args, int fd[3], bool prompt) +void spawn(SpawnContext *ctx) { - const int dev_null = open_dev_null(O_WRONLY); - if (dev_null < 0) { - return; - } - - unsigned int redir_count = 0; - if (fd[0] < 0) { - fd[0] = open_dev_null(O_RDONLY); - if (fd[0] < 0) { - close(dev_null); + bool quiet = !!(ctx->flags & SPAWN_QUIET); + bool prompt = !!(ctx->flags & SPAWN_PROMPT); + int fd[3] = {0, 1, 2}; + if (quiet) { + if ((fd[0] = open_dev_null(O_RDONLY)) < 0) { return; } - redir_count++; - } - if (fd[1] < 0) { - fd[1] = dev_null; - redir_count++; - } - if (fd[2] < 0) { - fd[2] = dev_null; - redir_count++; - } - - const bool quiet = (redir_count == 3); - if (!quiet) { - editor.child_controls_terminal = true; - editor.ui_end(); + if ((fd[1] = open_dev_null(O_WRONLY)) < 0) { + xclose(fd[0]); + return; + } + fd[2] = fd[1]; } - const pid_t pid = fork_exec(args, fd); + yield_terminal(quiet); + const pid_t pid = fork_exec(ctx->argv, ctx->env, fd); if (pid < 0) { - error_msg("Error: %s", strerror(errno)); + exec_error(ctx->argv[0]); prompt = false; } else { handle_child_error(pid); } - if (!quiet) { - terminal.raw(); - if (prompt) { - any_key(); - } - editor.resize(); - editor.child_controls_terminal = false; - } - if (dev_null >= 0) { - close(dev_null); + resume_terminal(quiet, prompt); + + if (quiet) { + xclose(fd[0]); + xclose(fd[1]); } } diff -Nru dte-1.9.1/src/spawn.h dte-1.10/src/spawn.h --- dte-1.9.1/src/spawn.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/spawn.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,7 +1,10 @@ #ifndef SPAWN_H #define SPAWN_H +#include #include "compiler.h" +#include "util/string.h" +#include "util/string-view.h" typedef enum { SPAWN_DEFAULT = 0, @@ -11,22 +14,17 @@ } SpawnFlags; typedef struct { - char *in; - char *out; - size_t in_len; - size_t out_len; -} FilterData; - -#define FILTER_DATA_INIT { \ - .in = NULL, \ - .out = NULL, \ - .in_len = 0, \ - .out_len = 0 \ -} - -int spawn_filter(char **argv, FilterData *data); -int spawn_writer(char **argv, const char *text, size_t length); + char **argv; + const char **env; + StringView input; + String output; + SpawnFlags flags; +} SpawnContext; + +bool spawn_source(SpawnContext *ctx); +bool spawn_sink(SpawnContext *ctx); +bool spawn_filter(SpawnContext *ctx); +void spawn(SpawnContext *ctx); void spawn_compiler(char **args, SpawnFlags flags, const Compiler *c); -void spawn(char **args, int fd[3], bool prompt); #endif diff -Nru dte-1.9.1/src/syntax/bitset.c dte-1.10/src/syntax/bitset.c --- dte-1.9.1/src/syntax/bitset.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/bitset.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,23 +0,0 @@ -#include "bitset.h" - -static void bitset_add(uint8_t *set, unsigned char ch) -{ - unsigned int byte = ch / 8; - unsigned int bit = ch & 7; - set[byte] |= 1u << bit; -} - -void bitset_add_pattern(uint8_t *set, const unsigned char *pattern) -{ - for (size_t i = 0; pattern[i]; i++) { - unsigned int ch = pattern[i]; - bitset_add(set, ch); - if (pattern[i + 1] == '-' && pattern[i + 2]) { - // Add char range - for (ch = ch + 1; ch <= pattern[i + 2]; ch++) { - bitset_add(set, ch); - } - i += 2; - } - } -} diff -Nru dte-1.9.1/src/syntax/bitset.h dte-1.10/src/syntax/bitset.h --- dte-1.9.1/src/syntax/bitset.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/bitset.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,31 +1,52 @@ #ifndef SYNTAX_BITSET_H #define SYNTAX_BITSET_H +#include #include #include -#include +#include "util/macros.h" -// This is a container type for storing a *set* of chars (bytes). -// It uses an array of 256 bits (32 bytes) for lookups, with each -// bit index used to determine whether or not the byte with that -// value is in the set. +#define BITSET_WORD_BITS (sizeof(BitSetWord) * CHAR_BIT) +#define BITSET_BIT_MASK (BITSET_WORD_BITS - 1) +#define BITSET_NR_WORDS(bits) (((bits) + BITSET_WORD_BITS - 1) / BITSET_WORD_BITS) +#define BITSET_INVERT(set) bitset_invert(set, ARRAY_COUNT(set)) -typedef uint8_t BitSet[256 / 8]; +typedef unsigned long BitSetWord; -static inline bool bitset_contains(const uint8_t *set, unsigned char ch) +static inline bool bitset_contains(const BitSetWord *set, unsigned char ch) { - unsigned int byte = ch / 8; - unsigned int bit = ch & 7; - return set[byte] & 1u << bit; + unsigned int word = ch / BITSET_WORD_BITS; + unsigned int bit = ch & BITSET_BIT_MASK; + return set[word] & ((BitSetWord)1) << bit; } -static inline void bitset_invert(uint8_t *set) +static inline void bitset_add(BitSetWord *set, unsigned char ch) { - for (size_t i = 0; i < 32; i++) { - set[i] = ~set[i]; + unsigned int word = ch / BITSET_WORD_BITS; + unsigned int bit = ch & BITSET_BIT_MASK; + set[word] |= ((BitSetWord)1) << bit; +} + +static inline void bitset_add_char_range(BitSetWord *set, const unsigned char *r) +{ + for (size_t i = 0; r[i]; i++) { + unsigned int ch = r[i]; + bitset_add(set, ch); + if (r[i + 1] == '-' && r[i + 2]) { + // Add char range + for (ch = ch + 1; ch <= r[i + 2]; ch++) { + bitset_add(set, ch); + } + i += 2; + } } } -void bitset_add_pattern(uint8_t *set, const unsigned char *pattern); +static inline void bitset_invert(BitSetWord *set, size_t nr_words) +{ + for (size_t i = 0; i < nr_words; i++) { + set[i] = ~set[i]; + } +} #endif diff -Nru dte-1.9.1/src/syntax/color.c dte-1.10/src/syntax/color.c --- dte-1.9.1/src/syntax/color.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/color.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,121 +1,149 @@ +#include #include #include "color.h" -#include "../debug.h" -#include "../completion.h" -#include "../util/macros.h" -#include "../util/ptr-array.h" -#include "../util/str-util.h" -#include "../util/xmalloc.h" +#include "command/serialize.h" +#include "completion.h" +#include "util/bsearch.h" +#include "util/debug.h" +#include "util/hashmap.h" +#include "util/macros.h" +#include "util/str-util.h" +#include "util/xmalloc.h" -TermColor *builtin_colors[NR_BC]; +TermColor builtin_colors[NR_BC]; -static PointerArray hl_colors = PTR_ARRAY_INIT; +static HashMap hl_colors = HASHMAP_INIT; static const char builtin_color_names[NR_BC][16] = { + [BC_ACTIVETAB] = "activetab", + [BC_COMMANDLINE] = "commandline", + [BC_CURRENTLINE] = "currentline", [BC_DEFAULT] = "default", - [BC_NONTEXT] = "nontext", + [BC_DIALOG] = "dialog", + [BC_ERRORMSG] = "errormsg", + [BC_INACTIVETAB] = "inactivetab", + [BC_INFOMSG] = "infomsg", + [BC_LINENUMBER] = "linenumber", [BC_NOLINE] = "noline", - [BC_WSERROR] = "wserror", + [BC_NONTEXT] = "nontext", [BC_SELECTION] = "selection", - [BC_CURRENTLINE] = "currentline", - [BC_LINENUMBER] = "linenumber", [BC_STATUSLINE] = "statusline", - [BC_COMMANDLINE] = "commandline", - [BC_ERRORMSG] = "errormsg", - [BC_INFOMSG] = "infomsg", [BC_TABBAR] = "tabbar", - [BC_ACTIVETAB] = "activetab", - [BC_INACTIVETAB] = "inactivetab", + [BC_WSERROR] = "wserror", }; UNITTEST { - for (size_t i = 0; i < ARRAY_COUNT(builtin_color_names); i++) { - const char *const name = builtin_color_names[i]; - if (name[0] == '\0') { - BUG("missing string at builtin_color_names[%zu]", i); - } - if (memchr(name, '\0', sizeof(builtin_color_names[0])) == NULL) { - BUG("builtin_color_names[%zu] missing null-terminator", i); - } + CHECK_BSEARCH_STR_ARRAY(builtin_color_names, strcmp); +} + +static TermColor *find_real_color(const char *name) +{ + ssize_t idx = BSEARCH_IDX(name, builtin_color_names, (CompareFunction)strcmp); + if (idx >= 0) { + BUG_ON(idx >= ARRAY_COUNT(builtin_color_names)); + return &builtin_colors[(BuiltinColorEnum)idx]; } + + return hashmap_get(&hl_colors, name); } -void fill_builtin_colors(void) +void set_highlight_color(const char *name, const TermColor *color) { - for (size_t i = 0; i < NR_BC; i++) { - builtin_colors[i] = &find_color(builtin_color_names[i])->color; + TermColor *c = find_real_color(name); + if (c) { + *c = *color; + return; } + + c = xnew(TermColor, 1); + *c = *color; + hashmap_insert(&hl_colors, xstrdup(name), c); } -HlColor *set_highlight_color(const char *name, const TermColor *color) +TermColor *find_color(const char *name) { - for (size_t i = 0, n = hl_colors.count; i < n; i++) { - HlColor *c = hl_colors.ptrs[i]; - if (streq(name, c->name)) { - c->color = *color; - return c; - } + TermColor *c = find_real_color(name); + if (c) { + return c; } - HlColor *c = xnew(HlColor, 1); - c->name = xstrdup(name); - c->color = *color; - ptr_array_add(&hl_colors, c); - return c; + const char *dot = strchr(name, '.'); + return dot ? find_real_color(dot + 1) : NULL; +} + +void clear_hl_colors(void) +{ + hashmap_clear(&hl_colors, free); } -static HlColor *find_real_color(const char *name) -{ - for (size_t i = 0; i < hl_colors.count; i++) { - HlColor *c = hl_colors.ptrs[i]; - if (streq(c->name, name)) { - return c; +void collect_hl_colors(const char *prefix) +{ + for (size_t i = 0; i < NR_BC; i++) { + const char *name = builtin_color_names[i]; + if (str_has_prefix(name, prefix)) { + add_completion(xstrdup(name)); } } - return NULL; + collect_hashmap_keys(&hl_colors, prefix); } -HlColor *find_color(const char *name) +typedef struct { + const char *name; + TermColor color; +} HlColor; + +static int hlcolor_cmp(const void *ap, const void *bp) { - HlColor *color = find_real_color(name); - if (color) { - return color; - } + const HlColor *a = ap; + const HlColor *b = bp; + return strcmp(a->name, b->name); +} - const char *dot = strchr(name, '.'); - if (dot) { - return find_real_color(dot + 1); +static void append_color(String *s, const char *name, const TermColor *color) +{ + string_append_literal(s, "hi "); + string_append_escaped_arg(s, name, true); + string_append_byte(s, ' '); + if (unlikely(name[0] == '-')) { + string_append_literal(s, "-- "); } - - return NULL; + string_append_cstring(s, term_color_to_string(color)); + string_append_byte(s, '\n'); } -// NOTE: you have to call update_all_syntax_colors() after this -void remove_extra_colors(void) +String dump_hl_colors(void) { - BUG_ON(hl_colors.count < NR_BC); - for (size_t i = NR_BC; i < hl_colors.count; i++) { - HlColor *c = hl_colors.ptrs[i]; + String buf = string_new(4096); + string_append_literal(&buf, "# UI colors:\n"); + for (size_t i = 0; i < NR_BC; i++) { + append_color(&buf, builtin_color_names[i], &builtin_colors[i]); + } - // Make possible use after free error easy to see - c->color.fg = COLOR_RED; - c->color.bg = COLOR_YELLOW; - c->color.attr = ATTR_BOLD; - free(c->name); - c->name = NULL; - free(c); + const size_t count = hl_colors.count; + if (unlikely(count == 0)) { + return buf; + } - hl_colors.ptrs[i] = NULL; + // Copy the HashMap entries into an array + HlColor *array = xnew(HlColor, count); + size_t n = 0; + for (HashMapIter it = hashmap_iter(&hl_colors); hashmap_next(&it); ) { + const TermColor *c = it.entry->value; + array[n++] = (HlColor) { + .name = it.entry->key, + .color = *c, + }; } - hl_colors.count = NR_BC; -} -void collect_hl_colors(const char *prefix) -{ - for (size_t i = 0, n = hl_colors.count; i < n; i++) { - const HlColor *c = hl_colors.ptrs[i]; - if (str_has_prefix(c->name, prefix)) { - add_completion(xstrdup(c->name)); - } + // Sort the array + BUG_ON(n != count); + qsort(array, count, sizeof(array[0]), hlcolor_cmp); + + string_append_literal(&buf, "\n# Syntax colors:\n"); + for (size_t i = 0; i < count; i++) { + append_color(&buf, array[i].name, &array[i].color); } + + free(array); + return buf; } diff -Nru dte-1.9.1/src/syntax/color.h dte-1.10/src/syntax/color.h --- dte-1.9.1/src/syntax/color.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/color.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,37 +1,34 @@ #ifndef SYNTAX_COLOR_H #define SYNTAX_COLOR_H -#include "../terminal/color.h" +#include "terminal/color.h" +#include "util/string.h" -enum builtin_color { +typedef enum { + BC_ACTIVETAB, + BC_COMMANDLINE, + BC_CURRENTLINE, BC_DEFAULT, - BC_NONTEXT, + BC_DIALOG, + BC_ERRORMSG, + BC_INACTIVETAB, + BC_INFOMSG, + BC_LINENUMBER, BC_NOLINE, - BC_WSERROR, + BC_NONTEXT, BC_SELECTION, - BC_CURRENTLINE, - BC_LINENUMBER, BC_STATUSLINE, - BC_COMMANDLINE, - BC_ERRORMSG, - BC_INFOMSG, BC_TABBAR, - BC_ACTIVETAB, - BC_INACTIVETAB, + BC_WSERROR, NR_BC -}; - -typedef struct { - char *name; - TermColor color; -} HlColor; +} BuiltinColorEnum; -extern TermColor *builtin_colors[NR_BC]; +extern TermColor builtin_colors[NR_BC]; -void fill_builtin_colors(void); -HlColor *set_highlight_color(const char *name, const TermColor *color); -HlColor *find_color(const char *name); -void remove_extra_colors(void); +void set_highlight_color(const char *name, const TermColor *color); +TermColor *find_color(const char *name); +void clear_hl_colors(void); void collect_hl_colors(const char *prefix); +String dump_hl_colors(void); #endif diff -Nru dte-1.9.1/src/syntax/highlight.c dte-1.10/src/syntax/highlight.c --- dte-1.9.1/src/syntax/highlight.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/highlight.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,13 @@ -#include +#include +#include #include #include "highlight.h" #include "syntax.h" -#include "../debug.h" -#include "../util/ascii.h" -#include "../util/xmalloc.h" +#include "block-iter.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/str-util.h" +#include "util/xmalloc.h" static bool state_is_valid(const State *st) { @@ -23,16 +26,20 @@ return a == b; } -static bool is_buffered(const Condition *cond, const char *str, size_t len) +static bool bufis(const ConditionData *u, const char *buf, size_t len) { - if (len != (size_t)cond->u.cond_bufis.len) { + if (len != (size_t)u->str.len) { return false; } + return mem_equal(u->str.buf, buf, len); +} - if (cond->u.cond_bufis.icase) { - return mem_equal_icase(cond->u.cond_bufis.str, str, len); +static bool bufis_icase(const ConditionData *u, const char *buf, size_t len) +{ + if (len != (size_t)u->str.len) { + return false; } - return !memcmp(cond->u.cond_bufis.str, str, len); + return mem_equal_icase(u->str.buf, buf, len); } static State *handle_heredoc ( @@ -43,7 +50,7 @@ ) { for (size_t i = 0, n = state->heredoc.states.count; i < n; i++) { HeredocState *s = state->heredoc.states.ptrs[i]; - if (s->len == len && !memcmp(s->delim, delim, len)) { + if (s->len == len && mem_equal(s->delim, delim, len)) { return s->state; } } @@ -55,35 +62,39 @@ .delim_len = len }; - HeredocState *s = xnew0(HeredocState, 1); - s->state = merge_syntax(syn, &m); - s->delim = xmemdup(delim, len); - s->len = len; - ptr_array_add(&state->heredoc.states, s); + HeredocState *s = xnew(HeredocState, 1); + *s = (HeredocState) { + .state = merge_syntax(syn, &m), + .delim = xmemdup(delim, len), + .len = len, + }; + + ptr_array_append(&state->heredoc.states, s); return s->state; } // Line should be terminated with \n unless it's the last line -static HlColor **highlight_line ( +static TermColor **highlight_line ( Syntax *syn, State *state, - const LineRef *lr, + const StringView *line_sv, State **ret ) { - static HlColor **colors; + static TermColor **colors; static size_t alloc; - const char *const line = lr->line; - const size_t len = lr->size; + const unsigned char *const line = line_sv->data; + const size_t len = line_sv->length; size_t i = 0; ssize_t sidx = -1; if (len > alloc) { - alloc = ROUND_UP(len, 128); + alloc = round_size_to_next_multiple(len, 128); xrenew(colors, alloc); } while (1) { const Condition *cond; + const ConditionData *u; const Action *a; unsigned char ch; top: @@ -93,10 +104,11 @@ ch = line[i]; for (size_t ci = 0, n = state->conds.count; ci < n; ci++) { cond = state->conds.ptrs[ci]; + u = &cond->u; a = &cond->a; switch (cond->type) { case COND_CHAR_BUFFER: - if (!bitset_contains(cond->u.cond_char.bitset, ch)) { + if (!bitset_contains(u->bitset, ch)) { break; } if (sidx < 0) { @@ -106,7 +118,17 @@ state = a->destination; goto top; case COND_BUFIS: - if (sidx >= 0 && is_buffered(cond, line + sidx, i - sidx)) { + if (sidx >= 0 && bufis(u, line + sidx, i - sidx)) { + for (size_t idx = sidx; idx < i; idx++) { + colors[idx] = a->emit_color; + } + sidx = -1; + state = a->destination; + goto top; + } + break; + case COND_BUFIS_ICASE: + if (sidx >= 0 && bufis_icase(u, line + sidx, i - sidx)) { for (size_t idx = sidx; idx < i; idx++) { colors[idx] = a->emit_color; } @@ -116,7 +138,7 @@ } break; case COND_CHAR: - if (!bitset_contains(cond->u.cond_char.bitset, ch)) { + if (!bitset_contains(u->bitset, ch)) { break; } colors[i++] = a->emit_color; @@ -124,7 +146,7 @@ state = a->destination; goto top; case COND_CHAR1: - if (cond->u.cond_single_char.ch != ch) { + if (u->ch != ch) { break; } colors[i++] = a->emit_color; @@ -134,11 +156,7 @@ case COND_INLIST: if ( sidx >= 0 - && hashset_get ( - &cond->u.cond_inlist.list->strings, - line + sidx, - i - sidx - ) + && hashset_get(&u->str_list->strings, line + sidx, i - sidx) ) { for (size_t idx = sidx; idx < i; idx++) { colors[idx] = a->emit_color; @@ -149,7 +167,7 @@ } break; case COND_RECOLOR: { - ssize_t idx = i - cond->u.cond_recolor.len; + ssize_t idx = i - u->recolor_len; if (idx < 0) { idx = 0; } @@ -166,41 +184,37 @@ } break; case COND_STR: { - size_t slen = cond->u.cond_str.len; + size_t slen = u->str.len; size_t end = i + slen; - if ( - len >= end - && !memcmp(cond->u.cond_str.str, line + i, slen) - ) { - while (i < end) { - colors[i++] = a->emit_color; - } - sidx = -1; - state = a->destination; - goto top; + if (len < end || !mem_equal(u->str.buf, line + i, slen)) { + break; + } + while (i < end) { + colors[i++] = a->emit_color; + } + sidx = -1; + state = a->destination; + goto top; } - } break; case COND_STR_ICASE: { - size_t slen = cond->u.cond_str.len; + size_t slen = u->str.len; size_t end = i + slen; - if ( - len >= end - && mem_equal_icase(cond->u.cond_str.str, line + i, slen) - ) { - while (i < end) { - colors[i++] = a->emit_color; - } - sidx = -1; - state = a->destination; - goto top; + if (len < end || !mem_equal_icase(u->str.buf, line + i, slen)) { + break; + } + while (i < end) { + colors[i++] = a->emit_color; + } + sidx = -1; + state = a->destination; + goto top; } - } break; case COND_STR2: // Optimized COND_STR (length 2, case sensitive) if ( - ch == cond->u.cond_str.str[0] + ch == u->str.buf[0] && len - i > 1 - && line[i + 1] == cond->u.cond_str.str[1] + && line[i + 1] == u->str.buf[1] ) { colors[i++] = a->emit_color; colors[i++] = a->emit_color; @@ -210,10 +224,10 @@ } break; case COND_HEREDOCEND: { - const char *str = cond->u.cond_heredocend.str; - size_t slen = cond->u.cond_heredocend.len; + const char *str = u->heredocend.data; + size_t slen = u->heredocend.length; size_t end = i + slen; - if (len >= end && (slen == 0 || !memcmp(str, line + i, slen))) { + if (len >= end && (slen == 0 || mem_equal(str, line + i, slen))) { while (i < end) { colors[i++] = a->emit_color; } @@ -222,6 +236,8 @@ goto top; } } break; + default: + BUG("unhandled condition type"); } } @@ -244,7 +260,7 @@ break; case STATE_INVALID: default: - BUG("Invalid default action type should never make it here"); + BUG("unhandled default action type"); } } @@ -257,7 +273,7 @@ static void resize_line_states(PointerArray *s, size_t count) { if (s->alloc < count) { - s->alloc = ROUND_UP(count, 64); + s->alloc = round_size_to_next_multiple(count, 64); xrenew(s->ptrs, s->alloc); } } @@ -284,11 +300,11 @@ ssize_t idx = sidx; while (idx < eidx) { - LineRef lr; + StringView line; State *st; - fill_line_nl_ref(bi, &lr); + fill_line_nl_ref(bi, &line); block_iter_eat_line(bi); - highlight_line(b->syn, ptrs[idx++], &lr, &st); + highlight_line(b->syn, ptrs[idx++], &line, &st); if (ptrs[idx] == st) { // Was not invalidated and didn't change @@ -316,7 +332,7 @@ ssize_t current_line = 0; ssize_t idx = 0; - if (b->syn == NULL) { + if (!b->syn) { return; } @@ -351,12 +367,12 @@ // Add new block_iter_move_down(&bi, s->count - 1 - current_line); while (s->count - 1 < line_nr) { - LineRef lr; - fill_line_nl_ref(&bi, &lr); + StringView line; + fill_line_nl_ref(&bi, &line); highlight_line ( b->syn, states[s->count - 1], - &lr, + &line, &states[s->count] ); s->count++; @@ -364,21 +380,21 @@ } } -HlColor **hl_line ( +TermColor **hl_line ( Buffer *b, - const LineRef *lr, + const StringView *line, size_t line_nr, bool *next_changed ) { *next_changed = false; - if (b->syn == NULL) { + if (!b->syn) { return NULL; } PointerArray *s = &b->line_start_states; BUG_ON(line_nr >= s->count); State *next; - HlColor **colors = highlight_line(b->syn, s->ptrs[line_nr++], lr, &next); + TermColor **colors = highlight_line(b->syn, s->ptrs[line_nr++], line, &next); if (line_nr == s->count) { resize_line_states(s, s->count + 1); diff -Nru dte-1.9.1/src/syntax/highlight.h dte-1.10/src/syntax/highlight.h --- dte-1.9.1/src/syntax/highlight.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/highlight.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,10 +3,11 @@ #include #include -#include "../buffer.h" -#include "../terminal/color.h" +#include "color.h" +#include "buffer.h" +#include "util/string-view.h" -HlColor **hl_line(Buffer *b, const LineRef *lr, size_t line_nr, bool *next_changed); +TermColor **hl_line(Buffer *b, const StringView *line, size_t line_nr, bool *next_changed); void hl_fill_start_states(Buffer *b, size_t line_nr); void hl_insert(Buffer *b, size_t first, size_t lines); void hl_delete(Buffer *b, size_t first, size_t lines); diff -Nru dte-1.9.1/src/syntax/state.c dte-1.10/src/syntax/state.c --- dte-1.9.1/src/syntax/state.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/state.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,17 +1,18 @@ #include +#include +#include #include "state.h" -#include "syntax.h" -#include "../command.h" -#include "../config.h" -#include "../editor.h" -#include "../error.h" -#include "../parse-args.h" -#include "../terminal/color.h" -#include "../util/path.h" -#include "../util/str-util.h" -#include "../util/strtonum.h" -#include "../util/xmalloc.h" -#include "../util/xsnprintf.h" +#include "command/args.h" +#include "command/run.h" +#include "editor.h" +#include "error.h" +#include "util/bsearch.h" +#include "util/macros.h" +#include "util/path.h" +#include "util/str-util.h" +#include "util/strtonum.h" +#include "util/xmalloc.h" +#include "util/xsnprintf.h" static Syntax *current_syntax; static State *current_state; @@ -61,7 +62,12 @@ st->name = xstrdup(name); st->defined = false; st->type = STATE_INVALID; - ptr_array_add(¤t_syntax->states, st); + + hashmap_insert(¤t_syntax->states, st->name, st); + if (current_syntax->states.count == 1) { + current_syntax->start_state = st; + } + return st; } @@ -114,7 +120,7 @@ static bool destination_state(const char *name, State **dest) { - const char *const sep = strchr(name, ':'); + const char *sep = strchr(name, ':'); if (sep) { // subsyntax:returnstate char *sub = xstrcut(name, sep - name); @@ -153,72 +159,61 @@ c->a.destination = d; c->a.emit_name = emit ? xstrdup(emit) : NULL; c->type = type; - ptr_array_add(¤t_state->conds, c); + ptr_array_append(¤t_state->conds, c); return c; } static void cmd_bufis(const CommandArgs *a) { - char **args = a->args; - const bool icase = a->flags[0] == 'i'; - const char *str = args[0]; + const char *str = a->args[0]; const size_t len = strlen(str); Condition *c; - - if (len > ARRAY_COUNT(c->u.cond_bufis.str)) { + if (len > ARRAY_COUNT(c->u.str.buf)) { error_msg ( "Maximum length of string is %zu bytes", - ARRAY_COUNT(c->u.cond_bufis.str) + ARRAY_COUNT(c->u.str.buf) ); return; } - c = add_condition(COND_BUFIS, args[1], args[2]); + ConditionType type = a->flags[0] == 'i' ? COND_BUFIS_ICASE : COND_BUFIS; + c = add_condition(type, a->args[1], a->args[2]); if (c) { - memcpy(c->u.cond_bufis.str, str, len); - c->u.cond_bufis.len = len; - c->u.cond_bufis.icase = icase; + memcpy(c->u.str.buf, str, len); + c->u.str.len = len; } } static void cmd_char(const CommandArgs *a) { - const char *pf = a->flags; - bool n_flag = false; - bool b_flag = false; - while (*pf) { - switch (*pf) { - case 'b': - b_flag = true; - break; - case 'n': - n_flag = true; - break; - } - pf++; + const char *chars = a->args[0]; + if (chars[0] == '\0') { + error_msg("char argument can't be empty"); + return; } - char **args = a->args; + bool add_to_buffer = cmdargs_has_flag(a, 'b'); + bool invert = cmdargs_has_flag(a, 'n'); ConditionType type; - if (b_flag) { + if (add_to_buffer) { type = COND_CHAR_BUFFER; - } else if (!n_flag && args[0][0] != '\0' && args[0][1] == '\0') { + } else if (!invert && chars[1] == '\0') { type = COND_CHAR1; } else { type = COND_CHAR; } - Condition *c = add_condition(type, args[1], args[2]); + Condition *c = add_condition(type, a->args[1], a->args[2]); if (!c) { return; } if (type == COND_CHAR1) { - c->u.cond_single_char.ch = (unsigned char)args[0][0]; + c->u.ch = (unsigned char)chars[0]; } else { - bitset_add_pattern(c->u.cond_char.bitset, args[0]); - if (n_flag) { - bitset_invert(c->u.cond_char.bitset); + bitset_add_char_range(c->u.bitset, chars); + if (invert) { + BITSET_INVERT(c->u.bitset); } } } @@ -229,10 +224,21 @@ if (no_syntax()) { return; } - ptr_array_add ( - ¤t_syntax->default_colors, - copy_string_array(a->args, a->nr_args) - ); + + const char *value = str_intern(a->args[0]); + HashMap *map = ¤t_syntax->default_colors; + for (size_t i = 1, n = a->nr_args; i < n; i++) { + const char *name = a->args[i]; + void *oldval = hashmap_insert_or_replace(map, xstrdup(name), (char*)value); + if (unlikely(oldval)) { + DEBUG_LOG ( + "duplicate 'default' argument in %s:%d: '%s'", + current_config.file, + current_config.line, + name + ); + } + } } static void cmd_eat(const CommandArgs *a) @@ -298,10 +304,9 @@ char **args = a->args; const char *name = args[0]; StringList *list = find_string_list(current_syntax, name); - if (list == NULL) { + if (!list) { list = xnew0(StringList, 1); - list->name = xstrdup(name); - ptr_array_add(¤t_syntax->string_lists, list); + hashmap_insert(¤t_syntax->string_lists, xstrdup(name), list); } else if (list->defined) { error_msg("List %s already exists.", name); return; @@ -309,9 +314,12 @@ list->defined = true; bool icase = a->flags[0] == 'i'; - size_t nstrings = a->nr_args - 1; - hashset_init(&list->strings, nstrings, icase); - hashset_add_many(&list->strings, args + 1, nstrings); + HashSet *set = &list->strings; + hashset_init(set, a->nr_args - 1, icase); + for (size_t i = 1, n = a->nr_args; i < n; i++) { + const char *str = args[i]; + hashset_add(set, str, strlen(str)); + } } static void cmd_inlist(const CommandArgs *a) @@ -322,18 +330,17 @@ StringList *list = find_string_list(current_syntax, name); Condition *c = add_condition(COND_INLIST, args[1], emit); - if (c == NULL) { + if (!c) { return; } - if (list == NULL) { + if (!list) { // Add undefined list list = xnew0(StringList, 1); - list->name = xstrdup(name); - ptr_array_add(¤t_syntax->string_lists, list); + hashmap_insert(¤t_syntax->string_lists, xstrdup(name), list); } list->used = true; - c->u.cond_inlist.list = list; + c->u.str_list = list; } static void cmd_noeat(const CommandArgs *a) @@ -344,10 +351,7 @@ const char *arg = a->args[0]; if (streq(arg, current_state->name)) { - error_msg ( - "Using noeat to to jump to parent state causes" - " infinite loop" - ); + error_msg("Using noeat to jump to parent state causes infinite loop"); return; } @@ -393,7 +397,7 @@ Condition *c = add_condition(type, NULL, a->args[0]); if (c && type == COND_RECOLOR) { - c->u.cond_recolor.len = len; + c->u.recolor_len = len; } } @@ -428,10 +432,10 @@ Condition *c; size_t len = strlen(str); - if (len > ARRAY_COUNT(c->u.cond_str.str)) { + if (len > ARRAY_COUNT(c->u.str.buf)) { error_msg ( "Maximum length of string is %zu bytes", - ARRAY_COUNT(c->u.cond_str.str) + ARRAY_COUNT(c->u.str.buf) ); return; } @@ -442,8 +446,8 @@ } c = add_condition(type, a->args[1], a->args[2]); if (c) { - memcpy(c->u.cond_str.str, str, len); - c->u.cond_str.len = len; + memcpy(c->u.str.buf, str, len); + c->u.str.len = len; } } @@ -468,31 +472,39 @@ } static void cmd_include(const CommandArgs *a); +static void cmd_require(const CommandArgs *a); -// Prevent Clang whining about .max_args = -1 -#if HAS_WARNING("-Wbitfield-constant-conversion") - IGNORE_WARNING("-Wbitfield-constant-conversion") -#endif - -static const Command syntax_commands[] = { - {"bufis", "i", 2, 3, cmd_bufis}, - {"char", "bn", 2, 3, cmd_char}, - {"default", "", 2, -1, cmd_default}, - {"eat", "", 1, 2, cmd_eat}, - {"heredocbegin", "", 2, 2, cmd_heredocbegin}, - {"heredocend", "", 1, 2, cmd_heredocend}, - {"include", "b", 1, 1, cmd_include}, - {"inlist", "", 2, 3, cmd_inlist}, - {"list", "i", 2, -1, cmd_list}, - {"noeat", "b", 1, 1, cmd_noeat}, - {"recolor", "", 1, 2, cmd_recolor}, - {"state", "", 1, 2, cmd_state}, - {"str", "i", 2, 3, cmd_str}, - {"syntax", "", 1, 1, cmd_syntax}, - {"", "", 0, 0, NULL} +static const Command cmds[] = { + {"bufis", "i", true, 2, 3, cmd_bufis}, + {"char", "bn", true, 2, 3, cmd_char}, + {"default", "", true, 2, -1, cmd_default}, + {"eat", "", true, 1, 2, cmd_eat}, + {"heredocbegin", "", true, 2, 2, cmd_heredocbegin}, + {"heredocend", "", true, 1, 2, cmd_heredocend}, + {"include", "b", true, 1, 1, cmd_include}, + {"inlist", "", true, 2, 3, cmd_inlist}, + {"list", "i", true, 2, -1, cmd_list}, + {"noeat", "b", true, 1, 1, cmd_noeat}, + {"recolor", "", true, 1, 2, cmd_recolor}, + {"require", "f", true, 1, 1, cmd_require}, + {"state", "", true, 1, 2, cmd_state}, + {"str", "i", true, 2, 3, cmd_str}, + {"syntax", "", true, 1, 1, cmd_syntax}, }; -UNIGNORE_WARNINGS +UNITTEST { + CHECK_BSEARCH_ARRAY(cmds, name, strcmp); +} + +static const Command *find_syntax_command(const char *name) +{ + return BSEARCH(name, cmds, command_cmp); +} + +static const CommandSet syntax_commands = { + .lookup = find_syntax_command, + .allow_recording = NULL, +}; static void cmd_include(const CommandArgs *a) { @@ -500,32 +512,63 @@ if (a->flags[0] == 'b') { flags |= CFG_BUILTIN; } - read_config(syntax_commands, a->args[0], flags); + read_config(&syntax_commands, a->args[0], flags); } -Syntax *load_syntax_file(const char *filename, ConfigFlags flags, int *err) +static void cmd_require(const CommandArgs *a) { - const char *saved_config_file = config_file; - int saved_config_line = config_line; + static HashSet loaded_files; + static HashSet loaded_builtins; + if (!loaded_files.table_size) { + hashset_init(&loaded_files, 8, false); + hashset_init(&loaded_builtins, 8, false); + } - *err = do_read_config(syntax_commands, filename, flags); + char buf[4096]; + char *path; + size_t path_len; + HashSet *set; + ConfigFlags flags = CFG_MUST_EXIST; + + if (a->flags[0] == 'f') { + set = &loaded_files; + path = a->args[0]; + path_len = strlen(path); + } else { + set = &loaded_builtins; + path_len = xsnprintf(buf, sizeof(buf), "syntax/inc/%s", a->args[0]); + path = buf; + flags |= CFG_BUILTIN; + } + + if (hashset_get(set, path, path_len)) { + return; + } + + if (read_config(&syntax_commands, path, flags) == 0) { + hashset_add(set, path, path_len); + } +} + +Syntax *load_syntax_file(const char *filename, ConfigFlags flags, int *err) +{ + const ConfigState saved = current_config; + *err = do_read_config(&syntax_commands, filename, flags); if (*err) { - config_file = saved_config_file; - config_line = saved_config_line; + current_config = saved; return NULL; } if (current_syntax) { finish_syntax(); find_unused_subsyntaxes(); } - config_file = saved_config_file; - config_line = saved_config_line; + current_config = saved; Syntax *syn = find_syntax(path_basename(filename)); if (syn && editor.status != EDITOR_INITIALIZING) { update_syntax_colors(syn); } - if (syn == NULL) { + if (!syn) { *err = EINVAL; } return syn; diff -Nru dte-1.9.1/src/syntax/state.h dte-1.10/src/syntax/state.h --- dte-1.9.1/src/syntax/state.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/state.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,9 +1,8 @@ #ifndef SYNTAX_STATE_H #define SYNTAX_STATE_H -#include #include "syntax.h" -#include "../config.h" +#include "config.h" Syntax *load_syntax_file(const char *filename, ConfigFlags f, int *err); Syntax *load_syntax_by_filetype(const char *filetype); diff -Nru dte-1.9.1/src/syntax/syntax.c dte-1.10/src/syntax/syntax.c --- dte-1.9.1/src/syntax/syntax.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/syntax.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,55 +1,31 @@ #include +#include +#include #include "syntax.h" -#include "state.h" -#include "../error.h" -#include "../util/ascii.h" -#include "../util/str-util.h" -#include "../util/xmalloc.h" +#include "error.h" +#include "util/str-util.h" +#include "util/xmalloc.h" -static PointerArray syntaxes = PTR_ARRAY_INIT; +static HashMap syntaxes = HASHMAP_INIT; StringList *find_string_list(const Syntax *syn, const char *name) { - for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { - StringList *list = syn->string_lists.ptrs[i]; - if (streq(list->name, name)) { - return list; - } - } - return NULL; + return hashmap_get(&syn->string_lists, name); } State *find_state(const Syntax *syn, const char *name) { - for (size_t i = 0, n = syn->states.count; i < n; i++) { - State *s = syn->states.ptrs[i]; - if (streq(s->name, name)) { - return s; - } - } - return NULL; + return hashmap_get(&syn->states, name); } static bool has_destination(ConditionType type) { - switch (type) { - case COND_RECOLOR: - case COND_RECOLOR_BUFFER: - return false; - default: - return true; - } + return !(type == COND_RECOLOR || type == COND_RECOLOR_BUFFER); } Syntax *find_any_syntax(const char *name) { - for (size_t i = 0; i < syntaxes.count; i++) { - Syntax *syn = syntaxes.ptrs[i]; - if (streq(syn->name, name)) { - return syn; - } - } - return NULL; + return hashmap_get(&syntaxes, name); } static const char *fix_name(const char *name, const char *prefix) @@ -59,7 +35,7 @@ return buf; } -static void fix_action(Syntax *syn, Action *a, const char *prefix) +static void fix_action(const Syntax *syn, Action *a, const char *prefix) { if (a->destination) { const char *name = fix_name(a->destination->name, prefix); @@ -71,73 +47,64 @@ } static void fix_conditions ( - Syntax *syn, + const Syntax *syn, State *s, - SyntaxMerge *m, + const SyntaxMerge *m, const char *prefix ) { for (size_t i = 0, n = s->conds.count; i < n; i++) { Condition *c = s->conds.ptrs[i]; fix_action(syn, &c->a, prefix); - if (c->a.destination == NULL && has_destination(c->type)) { + if (!c->a.destination && has_destination(c->type)) { c->a.destination = m->return_state; } if (m->delim && c->type == COND_HEREDOCEND) { - c->u.cond_heredocend.str = xmemdup(m->delim, m->delim_len); - c->u.cond_heredocend.len = m->delim_len; + c->u.heredocend.data = xmemdup(m->delim, m->delim_len); + c->u.heredocend.length = m->delim_len; } } fix_action(syn, &s->a, prefix); - if (s->a.destination == NULL) { + if (!s->a.destination) { s->a.destination = m->return_state; } } static const char *get_prefix(void) { - static int counter; + static unsigned int counter; static char prefix[32]; - snprintf(prefix, sizeof(prefix), "%d-", counter++); + snprintf(prefix, sizeof(prefix), "%u-", counter++); return prefix; } -static void update_state_colors(Syntax *syn, State *s); +static void update_state_colors(const Syntax *syn, State *s); -State *merge_syntax(Syntax *syn, SyntaxMerge *m) +State *merge_syntax(Syntax *syn, SyntaxMerge *merge) { // NOTE: string_lists is owned by Syntax so there's no need to // copy it. Freeing Condition does not free any string lists. const char *prefix = get_prefix(); - PointerArray *states = &syn->states; - size_t old_count = states->count; + const HashMap *subsyn_states = &merge->subsyn->states; + HashMap *states = &syn->states; - states->count += m->subsyn->states.count; - if (states->count > states->alloc) { - states->alloc = states->count; - xrenew(states->ptrs, states->alloc); - } - memcpy ( - states->ptrs + old_count, - m->subsyn->states.ptrs, - sizeof(*states->ptrs) * m->subsyn->states.count - ); - - for (size_t i = old_count; i < states->count; i++) { - State *s = xmemdup(states->ptrs[i], sizeof(State)); - - states->ptrs[i] = s; + for (HashMapIter it = hashmap_iter(subsyn_states); hashmap_next(&it); ) { + State *s = xmemdup(it.entry->value, sizeof(State)); s->name = xstrdup(fix_name(s->name, prefix)); s->emit_name = xstrdup(s->emit_name); + hashmap_insert(states, s->name, s); + if (s->conds.count > 0) { - s->conds.ptrs = xmemdup ( - s->conds.ptrs, - sizeof(void *) * s->conds.alloc - ); - for (size_t j = 0; j < s->conds.count; j++) { - s->conds.ptrs[j] = xmemdup(s->conds.ptrs[j], sizeof(Condition)); + // Deep copy conds PointerArray + BUG_ON(s->conds.alloc < s->conds.count); + void **ptrs = xnew(void*, s->conds.alloc); + for (size_t i = 0, n = s->conds.count; i < n; i++) { + ptrs[i] = xmemdup(s->conds.ptrs[i], sizeof(Condition)); } + s->conds.ptrs = ptrs; + } else { + BUG_ON(s->conds.alloc != 0); } // Mark unvisited so that state that is used only as a return @@ -148,15 +115,24 @@ s->copied = true; } - for (size_t i = old_count; i < states->count; i++) { - fix_conditions(syn, states->ptrs[i], m, prefix); - if (m->delim) { - update_state_colors(syn, states->ptrs[i]); + // Fix conditions and update colors for newly merged states + for (HashMapIter it = hashmap_iter(subsyn_states); hashmap_next(&it); ) { + const State *subsyn_state = it.entry->value; + BUG_ON(!subsyn_state); + const char *new_name = fix_name(subsyn_state->name, prefix); + State *new_state = hashmap_get(states, new_name); + BUG_ON(!new_state); + fix_conditions(syn, new_state, merge, prefix); + if (merge->delim) { + update_state_colors(syn, new_state); } } - m->subsyn->used = true; - return states->ptrs[old_count]; + const char *name = fix_name(merge->subsyn->start_state->name, prefix); + State *start_state = hashmap_get(states, name); + BUG_ON(!start_state); + merge->subsyn->used = true; + return start_state; } static void visit(State *s) @@ -166,7 +142,7 @@ } s->visited = true; for (size_t i = 0, n = s->conds.count; i < n; i++) { - Condition *cond = s->conds.ptrs[i]; + const Condition *cond = s->conds.ptrs[i]; if (cond->a.destination) { visit(cond->a.destination); } @@ -184,7 +160,6 @@ static void free_state(State *s) { - free(s->name); free(s->emit_name); ptr_array_free_cb(&s->conds, FREE_FUNC(free_condition)); free(s->a.emit_name); @@ -194,37 +169,53 @@ static void free_string_list(StringList *list) { hashset_free(&list->strings); - free(list->name); free(list); } -static void free_syntax(Syntax *syn) +static void free_syntax_contents(Syntax *syn) { - ptr_array_free_cb(&syn->states, FREE_FUNC(free_state)); - ptr_array_free_cb(&syn->string_lists, FREE_FUNC(free_string_list)); - ptr_array_free_cb(&syn->default_colors, FREE_FUNC(free_string_array)); + hashmap_free(&syn->states, FREE_FUNC(free_state)); + hashmap_free(&syn->string_lists, FREE_FUNC(free_string_list)); + hashmap_free(&syn->default_colors, NULL); +} +static void free_syntax(Syntax *syn) +{ + free_syntax_contents(syn); free(syn->name); free(syn); } +static void free_syntax_cb(Syntax *syn) +{ + free_syntax_contents(syn); + free(syn); +} + +// This function is only called by the test binary, just to ensure +// the various free_*() functions get exercised by ASan/UBSan +void free_syntaxes(void) +{ + hashmap_free(&syntaxes, FREE_FUNC(free_syntax_cb)); +} + void finalize_syntax(Syntax *syn, unsigned int saved_nr_errors) { if (syn->states.count == 0) { error_msg("Empty syntax"); } - for (size_t i = 0, n = syn->states.count; i < n; i++) { - State *s = syn->states.ptrs[i]; + for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) { + const State *s = it.entry->value; if (!s->defined) { // This state has been referenced but not defined - error_msg("No such state %s", s->name); + error_msg("No such state %s", it.entry->key); } } - for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { - StringList *list = syn->string_lists.ptrs[i]; + for (HashMapIter it = hashmap_iter(&syn->string_lists); hashmap_next(&it); ) { + const StringList *list = it.entry->value; if (!list->defined) { - error_msg("No such list %s", list->name); + error_msg("No such list %s", it.entry->key); } } @@ -242,21 +233,21 @@ } // Unused states and lists cause warning only - visit(syn->states.ptrs[0]); - for (size_t i = 0, n = syn->states.count; i < n; i++) { - State *s = syn->states.ptrs[i]; + visit(syn->start_state); + for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) { + const State *s = it.entry->value; if (!s->visited && !s->copied) { - error_msg("State %s is unreachable", s->name); + error_msg("State %s is unreachable", it.entry->key); } } - for (size_t i = 0, n = syn->string_lists.count; i < n; i++) { - StringList *list = syn->string_lists.ptrs[i]; + for (HashMapIter it = hashmap_iter(&syn->string_lists); hashmap_next(&it); ) { + const StringList *list = it.entry->value; if (!list->used) { - error_msg("List %s never used", list->name); + error_msg("List %s never used", it.entry->key); } } - ptr_array_add(&syntaxes, syn); + hashmap_insert(&syntaxes, syn->name, syn); } Syntax *find_syntax(const char *name) @@ -268,36 +259,26 @@ return syn; } -static const char *find_default_color(Syntax *syn, const char *name) +static const char *find_default_color(const Syntax *syn, const char *name) { - for (size_t i = 0, n = syn->default_colors.count; i < n; i++) { - char **strs = syn->default_colors.ptrs[i]; - for (size_t j = 1; strs[j]; j++) { - if (streq(strs[j], name)) { - return strs[0]; - } - } - } - return NULL; + return hashmap_get(&syn->default_colors, name); } -static void update_action_color(Syntax *syn, Action *a) +static void update_action_color(const Syntax *syn, Action *a) { const char *name = a->emit_name; - const char *def; - char full[64]; - if (!name) { name = a->destination->emit_name; } + char full[64]; snprintf(full, sizeof(full), "%s.%s", syn->name, name); a->emit_color = find_color(full); if (a->emit_color) { return; } - def = find_default_color(syn, name); + const char *def = find_default_color(syn, name); if (!def) { return; } @@ -306,7 +287,7 @@ a->emit_color = find_color(full); } -static void update_state_colors(Syntax *syn, State *s) +static void update_state_colors(const Syntax *syn, State *s) { for (size_t i = 0, n = s->conds.count; i < n; i++) { Condition *c = s->conds.ptrs[i]; @@ -321,27 +302,26 @@ // No point to update colors of a sub-syntax return; } - for (size_t i = 0, n = syn->states.count; i < n; i++) { - update_state_colors(syn, syn->states.ptrs[i]); + for (HashMapIter it = hashmap_iter(&syn->states); hashmap_next(&it); ) { + update_state_colors(syn, it.entry->value); } } void update_all_syntax_colors(void) { - for (size_t i = 0; i < syntaxes.count; i++) { - update_syntax_colors(syntaxes.ptrs[i]); + for (HashMapIter it = hashmap_iter(&syntaxes); hashmap_next(&it); ) { + update_syntax_colors(it.entry->value); } } void find_unused_subsyntaxes(void) { - // Don't complain multiple times about same unused subsyntaxes - static size_t i; - - for (; i < syntaxes.count; i++) { - Syntax *s = syntaxes.ptrs[i]; - if (!s->used && is_subsyntax(s)) { + for (HashMapIter it = hashmap_iter(&syntaxes); hashmap_next(&it); ) { + Syntax *s = it.entry->value; + if (!s->used && !s->warned_unused_subsyntax && is_subsyntax(s)) { error_msg("Subsyntax %s is unused", s->name); + // Don't complain multiple times about the same unused subsyntaxes + s->warned_unused_subsyntax = true; } } } diff -Nru dte-1.9.1/src/syntax/syntax.h dte-1.10/src/syntax/syntax.h --- dte-1.9.1/src/syntax/syntax.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/syntax/syntax.h 2021-04-03 21:08:53.000000000 +0000 @@ -6,11 +6,14 @@ #include #include "bitset.h" #include "color.h" -#include "../util/hashset.h" -#include "../util/ptr-array.h" +#include "util/hashmap.h" +#include "util/hashset.h" +#include "util/ptr-array.h" +#include "util/string-view.h" typedef enum { COND_BUFIS, + COND_BUFIS_ICASE, COND_CHAR, COND_CHAR_BUFFER, COND_CHAR1, @@ -31,55 +34,42 @@ char *emit_name; // Set after all colors have been added (config loaded). - HlColor *emit_color; + TermColor *emit_color; } Action; typedef struct { - char *name; HashSet strings; bool used; bool defined; } StringList; +typedef union { + BitSetWord bitset[BITSET_NR_WORDS(256)]; + StringView heredocend; + StringList *str_list; + unsigned char ch; + size_t recolor_len; + struct { + uint8_t len; + unsigned char buf[31]; + } str; +} ConditionData; + typedef struct { - union { - struct { - uint8_t len; - bool icase; - char str[30]; - } cond_bufis; - struct { - BitSet bitset; - } cond_char; - struct { - unsigned char ch; - } cond_single_char; - struct { - StringList *list; - } cond_inlist; - struct { - size_t len; - } cond_recolor; - struct { - uint8_t len; - char str[31]; - } cond_str; - struct { - size_t len; - char *str; - } cond_heredocend; - } u; - Action a; ConditionType type; + ConditionData u; + Action a; } Condition; typedef struct { char *name; - PointerArray states; - PointerArray string_lists; - PointerArray default_colors; + HashMap states; + struct State *start_state; + HashMap string_lists; + HashMap default_colors; bool heredoc; bool used; + bool warned_unused_subsyntax; } Syntax; typedef struct State { @@ -134,5 +124,6 @@ void update_syntax_colors(Syntax *syn); void update_all_syntax_colors(void); void find_unused_subsyntaxes(void); +void free_syntaxes(void); #endif diff -Nru dte-1.9.1/src/tag.c dte-1.10/src/tag.c --- dte-1.9.1/src/tag.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/tag.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,18 +1,16 @@ #include -#include +#include #include #include #include "tag.h" #include "completion.h" -#include "debug.h" -#include "error.h" #include "util/path.h" #include "util/str-util.h" #include "util/xmalloc.h" #include "util/xreadwrite.h" static TagFile *current_tag_file; -static char *current_filename; // For sorting tags +static const char *current_filename; // For sorting tags static int visibility_cmp(const Tag *a, const Tag *b) { @@ -52,11 +50,11 @@ if (b->local && b_this_file) { return 0; } - // a is more interesting bacause it is local symbol + // a is more interesting because it is local symbol return -1; } if (b->local && b_this_file) { - // b is more interesting bacause it is local symbol + // b is more interesting because it is local symbol return 1; } return 0; @@ -92,36 +90,29 @@ { const Tag *a = *(const Tag **)ap; const Tag *b = *(const Tag **)bp; - - int ret = visibility_cmp(a, b); - if (ret) { - return ret; - } - - return kind_cmp(a, b); + int r = visibility_cmp(a, b); + return r ? r : kind_cmp(a, b); } // Find "tags" file from directory path and its parent directories static int open_tag_file(char *path) { - const char tags[] = "tags"; - + static const char tags[] = "tags"; while (*path) { size_t len = strlen(path); char *slash = strrchr(path, '/'); - if (slash != path + len - 1) { path[len++] = '/'; } memcpy(path + len, tags, sizeof(tags)); - int fd = open(path, O_RDONLY); + int fd = xopen(path, O_RDONLY | O_CLOEXEC, 0); if (fd >= 0) { return fd; } if (errno != ENOENT) { return -1; } - *slash = 0; + *slash = '\0'; } errno = ENOENT; return -1; @@ -132,10 +123,7 @@ const char *filename, const struct stat *st ) { - if (tf->mtime != st->st_mtime) { - return true; - } - return !streq(tf->filename, filename); + return tf->mtime != st->st_mtime || !streq(tf->filename, filename); } static void tag_file_free(TagFile *tf) @@ -148,7 +136,7 @@ TagFile *load_tag_file(void) { char path[4096]; - if (!getcwd(path, sizeof(path) - 5)) { // 5 = length of "/tags" + if (!getcwd(path, sizeof(path) - STRLEN("/tags"))) { return NULL; } @@ -159,35 +147,36 @@ struct stat st; if (fstat(fd, &st) != 0 || st.st_size <= 0) { - close(fd); + xclose(fd); return NULL; } - if ( - current_tag_file != NULL - && tag_file_changed(current_tag_file, path, &st) - ) { - tag_file_free(current_tag_file); - current_tag_file = NULL; - } - if (current_tag_file != NULL) { - close(fd); - return current_tag_file; + if (current_tag_file) { + if (tag_file_changed(current_tag_file, path, &st)) { + tag_file_free(current_tag_file); + current_tag_file = NULL; + } else { + xclose(fd); + return current_tag_file; + } } char *buf = xmalloc(st.st_size); ssize_t size = xread(fd, buf, st.st_size); - close(fd); + xclose(fd); if (size < 0) { free(buf); return NULL; } - TagFile *tf = xnew0(TagFile, 1); - tf->filename = xstrdup(path); - tf->buf = buf; - tf->size = size; - tf->mtime = st.st_mtime; + TagFile *tf = xnew(TagFile, 1); + *tf = (TagFile) { + .filename = xstrdup(path), + .buf = buf, + .size = size, + .mtime = st.st_mtime, + }; + current_tag_file = tf; return current_tag_file; } @@ -204,21 +193,19 @@ } // Both parameters must be absolute and clean -static char *path_relative(const char *filename, const char *dir) +static const char *path_relative(const char *filename, const StringView dir) { - size_t dlen = strlen(dir); - - if (!str_has_prefix(filename, dir)) { + if (strncmp(filename, dir.data, dir.length) != 0) { + // Filename doesn't start with dir return NULL; } - if (filename[dlen] == '\0') { - // Equal strings - return xmemdup_literal("."); + switch (filename[dir.length]) { + case '\0': // Equal strings + return "."; + case '/': + return filename + dir.length + 1; } - if (filename[dlen] != '/') { - return NULL; - } - return xstrdup(filename + dlen + 1); + return NULL; } void tag_file_find_tags ( @@ -230,20 +217,18 @@ Tag *t = xnew(Tag, 1); size_t pos = 0; while (next_tag(tf, &pos, name, true, t)) { - ptr_array_add(tags, t); + ptr_array_append(tags, t); t = xnew(Tag, 1); } free(t); - if (filename == NULL) { + if (!filename) { current_filename = NULL; } else { - char *dir = path_dirname(tf->filename); + StringView dir = path_slice_dirname(tf->filename); current_filename = path_relative(filename, dir); - free(dir); } ptr_array_sort(tags, tag_cmp); - free(current_filename); current_filename = NULL; } @@ -264,7 +249,6 @@ Tag t; size_t pos = 0; char *prev = NULL; - while (next_tag(tf, &pos, prefix, false, &t)) { if (!prev || !streq(prev, t.name)) { add_completion(t.name); diff -Nru dte-1.9.1/src/terminal/color.c dte-1.10/src/terminal/color.c --- dte-1.9.1/src/terminal/color.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/color.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,91 +1,81 @@ -#include #include #include "color.h" -#include "../debug.h" -#include "../error.h" -#include "../util/ascii.h" -#include "../util/strtonum.h" - -#define CMP(str, val) cmp_str = str; cmp_val = val; goto compare - -static unsigned int lookup_attr(const char *s, size_t len) -{ - const char *cmp_str; - unsigned int cmp_val; - switch (len) { - case 3: CMP("dim", ATTR_DIM); - case 5: CMP("blink", ATTR_BLINK); - case 6: CMP("italic", ATTR_ITALIC); - case 7: CMP("reverse", ATTR_REVERSE); - case 12: CMP("lowintensity", ATTR_DIM); - case 13: CMP("strikethrough", ATTR_STRIKETHROUGH); - case 4: - switch (s[0]) { - case 'b': CMP("bold", ATTR_BOLD); - case 'k': CMP("keep", ATTR_KEEP); - } - break; - case 9: - switch (s[0]) { - case 'i': CMP("invisible", ATTR_INVIS); - case 'u': CMP("underline", ATTR_UNDERLINE); +#include "completion.h" +#include "error.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/str-util.h" +#include "util/strtonum.h" +#include "util/xmalloc.h" +#include "util/xsnprintf.h" + +static const char attr_names[][16] = { + "keep", + "underline", + "reverse", + "blink", + "dim", + "bold", + "invisible", + "italic", + "strikethrough" +}; + +static const char color_names[][16] = { + "keep", + "default", + "black", + "red", + "green", + "yellow", + "blue", + "magenta", + "cyan", + "gray", + "darkgray", + "lightred", + "lightgreen", + "lightyellow", + "lightblue", + "lightmagenta", + "lightcyan", + "white" +}; + +static unsigned int lookup_attr(const char *s) +{ + for (size_t i = 0; i < ARRAY_COUNT(attr_names); i++) { + if (streq(s, attr_names[i])) { + return 1U << i; } - break; + } + if (streq(s, "lowintensity")) { + return ATTR_DIM; } return 0; -compare: - return memcmp(s, cmp_str, len) ? 0 : cmp_val; } -static int32_t lookup_color(const char *s, size_t len) +static int32_t lookup_color(const char *s) { - const char *cmp_str; - int32_t cmp_val; - switch (len) { - case 3: CMP("red", COLOR_RED); - case 6: CMP("yellow", COLOR_YELLOW); - case 8: CMP("darkgray", 8); - case 4: - switch (s[0]) { - case 'b': CMP("blue", COLOR_BLUE); - case 'c': CMP("cyan", COLOR_CYAN); - case 'g': CMP("gray", COLOR_GRAY); - case 'k': CMP("keep", COLOR_KEEP); - } - break; - case 5: - switch (s[0]) { - case 'b': CMP("black", COLOR_BLACK); - case 'g': CMP("green", COLOR_GREEN); - case 'w': CMP("white", 15); - } - break; - case 7: - switch (s[0]) { - case 'd': CMP("default", COLOR_DEFAULT); - case 'm': CMP("magenta", COLOR_MAGENTA); + for (size_t i = 0; i < ARRAY_COUNT(color_names); i++) { + if (streq(s, color_names[i])) { + return i - 2; } - break; } return COLOR_INVALID; -compare: - return memcmp(s, cmp_str, len) ? COLOR_INVALID : cmp_val; } static int32_t parse_rrggbb(const char *str) { - uint8_t digits[6]; + int32_t color = 0; for (size_t i = 0; i < 6; i++) { - int val = hex_decode(str[i]); - if (val < 0) { + unsigned int digit = hex_decode(str[i]); + if (unlikely(digit > 0xF)) { return COLOR_INVALID; } - digits[i] = val; + color = (color << 4) | digit; } - int32_t r = (digits[0] << 4) | digits[1]; - int32_t g = (digits[2] << 4) | digits[3]; - int32_t b = (digits[4] << 4) | digits[5]; - return r << 16 | g << 8 | b | COLOR_FLAG_RGB; + return COLOR_RGB(color); } UNITTEST { @@ -93,19 +83,20 @@ BUG_ON(parse_rrggbb("011011") != COLOR_RGB(0x011011)); BUG_ON(parse_rrggbb("fffffg") != COLOR_INVALID); BUG_ON(parse_rrggbb(".") != COLOR_INVALID); + BUG_ON(parse_rrggbb("") != COLOR_INVALID); BUG_ON(parse_rrggbb("11223") != COLOR_INVALID); } static int32_t parse_color(const char *str) { size_t len = strlen(str); - if (len == 0) { + if (unlikely(len == 0)) { return COLOR_INVALID; } // Parse #rrggbb if (str[0] == '#') { - if (len != 7) { + if (unlikely(len != 7)) { return COLOR_INVALID; } return parse_rrggbb(str + 1); @@ -116,54 +107,51 @@ uint8_t r = ((uint8_t)str[0]) - '0'; uint8_t g = ((uint8_t)str[2]) - '0'; uint8_t b = ((uint8_t)str[4]) - '0'; - if (r > 5 || g > 5 || b > 5 || str[3] != '/') { + if (unlikely(r > 5 || g > 5 || b > 5 || str[3] != '/')) { return COLOR_INVALID; } // Convert to color index 16..231 (xterm 6x6x6 color cube) - return 16 + r * 36 + g * 6 + b; + return 16 + (r * 36) + (g * 6) + b; } // Parse -2 .. 255 if (len <= 3 && (str[0] == '-' || ascii_isdigit(str[0]))) { int x; - if (!str_to_int(str, &x) || x < -2 || x > 255) { + if (unlikely(!str_to_int(str, &x) || x < -2 || x > 255)) { return COLOR_INVALID; } return x; } - bool light = false; - if (len >= 8 && memcmp(str, "light", 5) == 0) { - light = true; - str += 5; - len -= 5; - } - - const int32_t c = lookup_color(str, len); - switch (c) { - case COLOR_INVALID: - return COLOR_INVALID; - case COLOR_RED: - case COLOR_GREEN: - case COLOR_YELLOW: - case COLOR_BLUE: - case COLOR_MAGENTA: - case COLOR_CYAN: - return light ? c + 8 : c; - default: - return light ? COLOR_INVALID : c; - } + return lookup_color(str); } -static bool parse_attr(const char *str, unsigned int *attr) -{ - const unsigned int a = lookup_attr(str, strlen(str)); - if (a) { - *attr |= a; - return true; - } - - return false; +UNITTEST { + BUG_ON(parse_color("-2") != COLOR_KEEP); + BUG_ON(parse_color("-1") != COLOR_DEFAULT); + BUG_ON(parse_color("0") != COLOR_BLACK); + BUG_ON(parse_color("1") != COLOR_RED); + BUG_ON(parse_color("255") != 255); + BUG_ON(parse_color("0/0/0") != 16); + BUG_ON(parse_color("2/3/4") != 110); + BUG_ON(parse_color("5/5/5") != 231); + BUG_ON(parse_color("black") != COLOR_BLACK); + BUG_ON(parse_color("white") != COLOR_WHITE); + BUG_ON(parse_color("keep") != COLOR_KEEP); + BUG_ON(parse_color("default") != COLOR_DEFAULT); + BUG_ON(parse_color("#abcdef") != COLOR_RGB(0xABCDEF)); + BUG_ON(parse_color("#a1b2c3") != COLOR_RGB(0xA1B2C3)); + BUG_ON(parse_color("-3") != COLOR_INVALID); + BUG_ON(parse_color("256") != COLOR_INVALID); + BUG_ON(parse_color("//0/0") != COLOR_INVALID); + BUG_ON(parse_color("0/0/:") != COLOR_INVALID); + BUG_ON(parse_color("lightblack") != COLOR_INVALID); + BUG_ON(parse_color("lightwhite") != COLOR_INVALID); + BUG_ON(parse_color("light_") != COLOR_INVALID); + BUG_ON(parse_color("") != COLOR_INVALID); + BUG_ON(parse_color(".") != COLOR_INVALID); + BUG_ON(parse_color("#11223") != COLOR_INVALID); + BUG_ON(parse_color("#fffffg") != COLOR_INVALID); } bool parse_term_color(TermColor *color, char **strs) @@ -191,10 +179,15 @@ } count++; } - } else if (!parse_attr(str, &color->attr)) { - error_msg("invalid color or attribute %s", str); - return false; + continue; + } + const unsigned int attr = lookup_attr(str); + if (attr) { + color->attr |= attr; + continue; } + error_msg("invalid color or attribute %s", str); + return false; } return true; } @@ -209,11 +202,8 @@ // Convert RGB color component (0-255) to nearest xterm color cube index (0-5) static uint8_t nearest_cube_index(uint8_t c) { - if (c < 48) { - return 0; - } - if (c < 114) { - return 1; + if (c < 75) { + c += 27; } return (c - 35) / 40; } @@ -245,9 +235,9 @@ return 16 + (36 * r_idx) + (6 * g_idx) + b_idx; } - if (r >= 8 && r <= 238 && r == g && r == b) { + if (r == g && r == b) { uint8_t v = r - 8; - if (v % 10 == 0) { + if (v <= 230 && v % 10 == 0) { *exact = true; return (v / 10) + 232; } @@ -359,3 +349,54 @@ // (i.e. BUG() expands to nothing). return COLOR_DEFAULT; } + +void collect_colors_and_attributes(const char *prefix) +{ + for (size_t i = 1; i < ARRAY_COUNT(color_names); i++) { + if (str_has_prefix(color_names[i], prefix)) { + add_completion(xstrdup(color_names[i])); + } + } + for (size_t i = 0; i < ARRAY_COUNT(attr_names); i++) { + if (str_has_prefix(attr_names[i], prefix)) { + add_completion(xstrdup(attr_names[i])); + } + } +} + +static size_t append_color(char *buf, int32_t color) +{ + if (color < 16) { + BUG_ON(color <= COLOR_INVALID); + const char *name = color_names[color + 2]; + size_t len = strlen(name); + memcpy(buf, name, len); + return len; + } else if (color < 256) { + return xsnprintf(buf, 4, "%u", (unsigned int)color); + } + BUG_ON((color & COLOR_FLAG_RGB) == 0); + uint8_t r, g, b; + color_split_rgb(color, &r, &g, &b); + return xsnprintf(buf, 8, "#%02hhx%02hhx%02hhx", r, g, b); +} + +const char *term_color_to_string(const TermColor *color) +{ + static char buf[128]; + size_t pos = append_color(buf, color->fg); + if (color->bg != COLOR_DEFAULT || (color->attr & ATTR_KEEP) != 0) { + buf[pos++] = ' '; + pos += append_color(buf + pos, color->bg); + } + for (size_t i = 0; i < ARRAY_COUNT(attr_names); i++) { + if (color->attr & (1U << i)) { + buf[pos++] = ' '; + size_t len = strlen(attr_names[i]); + memcpy(buf + pos, attr_names[i], len); + pos += len; + } + } + buf[pos] = '\0'; + return buf; +} diff -Nru dte-1.9.1/src/terminal/color.h dte-1.10/src/terminal/color.h --- dte-1.9.1/src/terminal/color.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/color.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,9 +3,8 @@ #include #include -#include "../util/macros.h" +#include "util/macros.h" -#define COLOR_FLAG_RGB INT32_C(0x01000000) #define COLOR_RGB(x) (COLOR_FLAG_RGB | (x)) typedef enum { @@ -35,7 +34,11 @@ COLOR_LIGHTBLUE = 12, COLOR_LIGHTMAGENTA = 13, COLOR_LIGHTCYAN = 14, - COLOR_WHITE = 15 + COLOR_WHITE = 15, + + // This bit flag is used to allow 24-bit RGB colors to be differentiated + // from basic colors (e.g. #000004 vs. COLOR_BLUE). + COLOR_FLAG_RGB = INT32_C(1) << 24 }; enum { @@ -72,7 +75,9 @@ ; } -bool parse_term_color(TermColor *color, char **strs); +bool parse_term_color(TermColor *color, char **strs) NONNULL_ARGS; int32_t color_to_nearest(int32_t color, TermColorCapabilityType type); +const char *term_color_to_string(const TermColor *color) NONNULL_ARGS_AND_RETURN; +void collect_colors_and_attributes(const char *prefix) NONNULL_ARGS; #endif diff -Nru dte-1.9.1/src/terminal/ecma48.c dte-1.10/src/terminal/ecma48.c --- dte-1.9.1/src/terminal/ecma48.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/ecma48.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,53 +1,14 @@ -#include -#include -#include -#undef CTRL // macro from sys/ttydefaults.h clashes with the one in key.h +#include #include "ecma48.h" -#include "no-op.h" #include "output.h" -#include "terminfo.h" -#include "xterm.h" -#include "../util/ascii.h" -#include "../util/macros.h" -#include "../util/xsnprintf.h" - -static struct termios termios_save; - -void term_raw(void) -{ - // Get and save current attributes - struct termios termios; - tcgetattr(STDIN_FILENO, &termios); - termios_save = termios; - - // Enter "raw" mode (roughly equivalent to cfmakeraw(3) on Linux/BSD) - termios.c_iflag &= ~( - ICRNL | IXON | IXOFF - | IGNBRK | BRKINT | PARMRK - | ISTRIP | INLCR | IGNCR - ); - termios.c_oflag &= ~OPOST; - termios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - termios.c_cflag &= ~(CSIZE | PARENB); - termios.c_cflag |= CS8; - - // Read at least 1 char on each read() - termios.c_cc[VMIN] = 1; - - // Read blocks until there are MIN(VMIN, requested) bytes available - termios.c_cc[VTIME] = 0; - - tcsetattr(STDIN_FILENO, 0, &termios); -} - -void term_cooked(void) -{ - tcsetattr(STDIN_FILENO, 0, &termios_save); -} +#include "terminal.h" +#include "util/ascii.h" +#include "util/macros.h" void ecma48_clear_screen(void) { term_add_literal ( + "\033[0m" // Reset colors and attributes "\033[H" // Move cursor to 1,1 (done only to mimic terminfo(5) "clear") "\033[2J" // Clear whole screen (regardless of cursor position) ); @@ -58,91 +19,93 @@ term_add_literal("\033[K"); } -void ecma48_move_cursor(int x, int y) +void ecma48_move_cursor(unsigned int x, unsigned int y) { - if (x < 0 || x >= 999 || y < 0 || y >= 999) { - return; + term_add_literal("\033["); + term_add_uint(y + 1); + if (x != 0) { + term_add_byte(';'); + term_add_uint(x + 1); } - term_sprintf ( - "\033[%u;%uH", - // x and y are zero-based - ((unsigned int)y) + 1, - ((unsigned int)x) + 1 - ); + term_add_byte('H'); } -void ecma48_set_color(const TermColor *const color) +void ecma48_repeat_byte(char ch, size_t count) { - if (same_color(color, &obuf.color)) { + if (!ascii_isprint(ch) || count < 6 || count > 30000) { + term_repeat_byte(ch, count); return; } + term_add_byte(ch); + term_add_literal("\033["); + term_add_uint(count - 1); + term_add_byte('b'); +} - char buf[32] = "\033[0"; - size_t i = 3; - static_assert(sizeof(buf) >= STRLEN("\033[0;1;7;30;40m")); - - TermColor c = *color; - if (c.attr & ATTR_BOLD) { - buf[i++] = ';'; - buf[i++] = '1'; - } - if (c.attr & ATTR_REVERSE) { - buf[i++] = ';'; - buf[i++] = '7'; +static void do_set_color(int32_t color, char ch) +{ + if (color < 0) { + return; } - if (c.fg >= 0 && c.fg < 8) { - buf[i++] = ';'; - buf[i++] = '3'; - buf[i++] = '0' + (char) c.fg; - } + term_add_byte(';'); + term_add_byte(ch); - if (c.bg >= 0 && c.bg < 8) { - buf[i++] = ';'; - buf[i++] = '4'; - buf[i++] = '0' + (char) c.bg; + if (likely(color < 8)) { + term_add_byte('0' + color); + } else if (color < 256) { + term_add_literal("8;5;"); + term_add_uint(color); + } else { + uint8_t r, g, b; + color_split_rgb(color, &r, &g, &b); + term_add_literal("8;2;"); + term_add_uint(r); + term_add_byte(';'); + term_add_uint(g); + term_add_byte(';'); + term_add_uint(b); } - - buf[i++] = 'm'; - term_add_bytes(buf, i); - obuf.color = *color; } -void ecma48_repeat_byte(char ch, size_t count) -{ - if (!ascii_isprint(ch) || count < 6 || count > 30000) { - term_repeat_byte(ch, count); - return; - } - term_sprintf("%c\033[%zub", ch, count - 1); +static bool attr_is_set(const TermColor *color, unsigned int attr) +{ + if (color->attr & attr) { + if (unlikely(terminal.ncv_attributes & attr)) { + // Terminal only allows attr when not using colors + return color->fg == COLOR_DEFAULT && color->bg == COLOR_DEFAULT; + } + return true; + } + return false; +} + +void ecma48_set_color(const TermColor *color) +{ + static const struct { + char code; + unsigned int attr; + } attr_map[] = { + {'1', ATTR_BOLD}, + {'2', ATTR_DIM}, + {'3', ATTR_ITALIC}, + {'4', ATTR_UNDERLINE}, + {'5', ATTR_BLINK}, + {'7', ATTR_REVERSE}, + {'8', ATTR_INVIS}, + {'9', ATTR_STRIKETHROUGH} + }; + + term_add_literal("\033[0"); + + for (size_t i = 0; i < ARRAY_COUNT(attr_map); i++) { + if (attr_is_set(color, attr_map[i].attr)) { + term_add_byte(';'); + term_add_byte(attr_map[i].code); + } + } + + do_set_color(color->fg, '3'); + do_set_color(color->bg, '4'); + term_add_byte('m'); } - -Terminal terminal = { - .color_type = TERM_8_COLOR, - .width = 80, - .height = 24, - .raw = &term_raw, - .cooked = &term_cooked, - .parse_key_sequence = &xterm_parse_key, - .put_control_code = &term_add_string_view, - .clear_screen = &ecma48_clear_screen, - .clear_to_eol = &ecma48_clear_to_eol, - .set_color = &ecma48_set_color, - .move_cursor = &ecma48_move_cursor, - .repeat_byte = &term_repeat_byte, - .save_title = &no_op, - .restore_title = &no_op, - .set_title = &no_op_s, - .control_codes = { - .init = STRING_VIEW_INIT, - .deinit = STRING_VIEW_INIT, - .reset_colors = STRING_VIEW("\033[39;49m"), - .reset_attrs = STRING_VIEW("\033[0m"), - .keypad_off = STRING_VIEW_INIT, - .keypad_on = STRING_VIEW_INIT, - .cup_mode_off = STRING_VIEW_INIT, - .cup_mode_on = STRING_VIEW_INIT, - .show_cursor = STRING_VIEW_INIT, - .hide_cursor = STRING_VIEW_INIT, - } -}; diff -Nru dte-1.9.1/src/terminal/ecma48.h dte-1.10/src/terminal/ecma48.h --- dte-1.9.1/src/terminal/ecma48.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/ecma48.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,15 +1,13 @@ #ifndef TERMINAL_ECMA48_H #define TERMINAL_ECMA48_H +#include #include "color.h" -#include "../util/string-view.h" void ecma48_clear_screen(void); void ecma48_clear_to_eol(void); -void ecma48_move_cursor(int x, int y); +void ecma48_move_cursor(unsigned int x, unsigned int y); void ecma48_set_color(const TermColor *color); void ecma48_repeat_byte(char ch, size_t count); -void term_raw(void); -void term_cooked(void); #endif diff -Nru dte-1.9.1/src/terminal/input.c dte-1.10/src/terminal/input.c --- dte-1.9.1/src/terminal/input.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/input.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,51 +1,49 @@ #include #include #include -#include #include #include #include -#undef CTRL // undef glibc macro pollution from sys/ttydefaults.h #include "input.h" #include "terminal.h" -#include "../editor.h" -#include "../util/ascii.h" -#include "../util/xmalloc.h" - -static char input_buf[256]; -static size_t input_buf_fill; -static bool input_can_be_truncated; +#include "editor.h" +#include "util/ascii.h" +#include "util/xmalloc.h" + +static struct { + char buf[256]; + size_t len; + bool can_be_truncated; +} input; static void consume_input(size_t len) { - input_buf_fill -= len; - if (input_buf_fill) { - memmove(input_buf, input_buf + len, input_buf_fill); + input.len -= len; + if (input.len) { + memmove(input.buf, input.buf + len, input.len); // Keys are sent faster than we can read - input_can_be_truncated = true; + input.can_be_truncated = true; } } static bool fill_buffer(void) { - if (input_buf_fill == sizeof(input_buf)) { + if (input.len == sizeof(input.buf)) { return false; } - if (!input_buf_fill) { - input_can_be_truncated = false; + if (!input.len) { + input.can_be_truncated = false; } - ssize_t rc = read ( - STDIN_FILENO, - input_buf + input_buf_fill, - sizeof(input_buf) - input_buf_fill - ); + size_t avail = sizeof(input.buf) - input.len; + ssize_t rc = read(STDIN_FILENO, input.buf + input.len, avail); if (rc <= 0) { return false; } - input_buf_fill += (size_t)rc; + + input.len += (size_t)rc; return true; } @@ -75,17 +73,17 @@ static bool input_get_byte(unsigned char *ch) { - if (!input_buf_fill && !fill_buffer()) { + if (!input.len && !fill_buffer()) { return false; } - *ch = input_buf[0]; + *ch = input.buf[0]; consume_input(1); return true; } static bool read_special(KeyCode *key) { - ssize_t len = terminal.parse_key_sequence(input_buf, input_buf_fill, key); + ssize_t len = terminal.parse_key_sequence(input.buf, input.len, key); switch (len) { case -1: // Possibly truncated @@ -99,7 +97,7 @@ return true; } - if (input_can_be_truncated && fill_buffer()) { + if (input.can_be_truncated && fill_buffer()) { return read_special(key); } @@ -150,7 +148,7 @@ return true; } -static bool is_text(const char *const str, size_t len) +static bool is_text(const char *str, size_t len) { for (size_t i = 0; i < len; i++) { if (ascii_is_nonspace_cntrl(str[i])) { @@ -162,26 +160,25 @@ bool term_read_key(KeyCode *key) { - if (!input_buf_fill && !fill_buffer()) { + if (!input.len && !fill_buffer()) { return false; } - if (input_buf_fill > 4 && is_text(input_buf, input_buf_fill)) { + if (input.len > 4 && is_text(input.buf, input.len)) { *key = KEY_PASTE; return true; } - if (input_buf[0] == '\033') { - if (input_buf_fill > 1 || input_can_be_truncated) { + if (input.buf[0] == '\033') { + if (input.len > 1 || input.can_be_truncated) { if (read_special(key)) { return true; } } - if (input_buf_fill == 1) { + if (input.len == 1) { // Sometimes alt-key gets split into two reads fill_buffer_timeout(); - - if (input_buf_fill > 1 && input_buf[1] == '\033') { + if (input.len > 1 && input.buf[1] == '\033') { /* * Double-esc (+ maybe some other characters) * @@ -198,7 +195,7 @@ return read_simple(key); } } - if (input_buf_fill > 1) { + if (input.len > 1) { // Unknown escape sequence or 'esc key' / 'alt-key' // Throw escape away @@ -209,14 +206,14 @@ return false; } - if (input_buf_fill == 0 || input_buf[0] == '\033') { + if (input.len == 0 || input.buf[0] == '\033') { // 'esc key' or 'alt-key' *key |= MOD_META; return true; } // Unknown escape sequence; avoid inserting it - input_buf_fill = 0; + input.len = 0; return false; } } @@ -226,14 +223,14 @@ char *term_read_paste(size_t *size) { - size_t alloc = ROUND_UP(input_buf_fill + 1, 1024); + size_t alloc = round_size_to_next_multiple(input.len + 1, 1024); size_t count = 0; char *buf = xmalloc(alloc); - if (input_buf_fill) { - memcpy(buf, input_buf, input_buf_fill); - count = input_buf_fill; - input_buf_fill = 0; + if (input.len) { + memcpy(buf, input.buf, input.len); + count = input.len; + input.len = 0; } while (1) { @@ -286,14 +283,3 @@ size_t size; free(term_read_paste(&size)); } - -bool term_get_size(int *w, int *h) -{ - struct winsize ws; - if (ioctl(0, TIOCGWINSZ, &ws) != -1) { - *w = ws.ws_col; - *h = ws.ws_row; - return true; - } - return false; -} diff -Nru dte-1.9.1/src/terminal/input.h dte-1.10/src/terminal/input.h --- dte-1.9.1/src/terminal/input.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/input.h 2021-04-03 21:08:53.000000000 +0000 @@ -9,6 +9,4 @@ char *term_read_paste(size_t *size); void term_discard_paste(void); -bool term_get_size(int *w, int *h); - #endif diff -Nru dte-1.9.1/src/terminal/key.c dte-1.10/src/terminal/key.c --- dte-1.9.1/src/terminal/key.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/key.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,8 @@ #include #include "key.h" -#include "../debug.h" -#include "../util/ascii.h" -#include "../util/utf8.h" +#include "util/ascii.h" +#include "util/utf8.h" +#include "util/xsnprintf.h" // Note: these strings must be kept in sync with the enum in key.h static const char special_names[][8] = { @@ -12,10 +12,11 @@ "down", "right", "left", - "pgdown", + "begin", "end", - "pgup", + "pgdown", "home", + "pgup", "F1", "F2", "F3", @@ -29,9 +30,8 @@ "F11", "F12", }; -static_assert(ARRAY_COUNT(special_names) == NR_SPECIAL_KEYS); -static size_t parse_modifiers(const char *const str, KeyCode *modifiersp) +static size_t parse_modifiers(const char *str, KeyCode *modifiersp) { KeyCode modifiers = 0; size_t i = 0; @@ -63,7 +63,7 @@ return i; } -bool parse_key(KeyCode *key, const char *str) +bool parse_key_string(KeyCode *key, const char *str) { KeyCode modifiers; if (str[0] == '^' && str[1] != '\0') { @@ -82,7 +82,7 @@ switch (ch) { case 'i': case 'I': - ch = '\t'; + ch = KEY_TAB; modifiers = 0; break; case 'm': @@ -96,11 +96,11 @@ return true; } if (ascii_streq_icase(str, "space")) { - *key = modifiers | ' '; + *key = modifiers | KEY_SPACE; return true; } if (ascii_streq_icase(str, "tab")) { - *key = modifiers | '\t'; + *key = modifiers | KEY_TAB; return true; } if (ascii_streq_icase(str, "enter")) { @@ -108,6 +108,7 @@ return true; } + static_assert(ARRAY_COUNT(special_names) == NR_SPECIAL_KEYS); for (i = 0; i < NR_SPECIAL_KEYS; i++) { if (ascii_streq_icase(str, special_names[i])) { *key = modifiers | (KEY_SPECIAL_MIN + i); @@ -120,7 +121,7 @@ #define COPY(dest, src) memcpy(dest, src, STRLEN(src) + 1) -const char *key_to_string(KeyCode k) +const char *keycode_to_string(KeyCode k) { static char buf[32]; size_t i = 0; @@ -142,31 +143,28 @@ const KeyCode key = keycode_get_key(k); if (u_is_unicode(key)) { switch (key) { - case '\t': + case KEY_TAB: COPY(ptr, "tab"); break; case KEY_ENTER: COPY(ptr, "enter"); break; - case ' ': + case KEY_SPACE: COPY(ptr, "space"); break; default: u_set_char(buf, &i, key); buf[i] = '\0'; } - } else if (key >= KEY_SPECIAL_MIN && key <= KEY_SPECIAL_MAX) { - static_assert(sizeof(special_names[0]) == 8); - memcpy ( - ptr, - special_names[key - KEY_SPECIAL_MIN], - sizeof(special_names[0]) - ); - } else if (key == KEY_PASTE) { - COPY(ptr, "paste"); - } else { - COPY(ptr, "???"); + return buf; + } + + if (key >= KEY_SPECIAL_MIN && key <= KEY_SPECIAL_MAX) { + const char *name = special_names[key - KEY_SPECIAL_MIN]; + memcpy(ptr, name, sizeof(special_names[0])); + return buf; } + xsnprintf(buf, sizeof buf, "INVALID (0x%08X)", (unsigned int)k); return buf; } diff -Nru dte-1.9.1/src/terminal/key.h dte-1.10/src/terminal/key.h --- dte-1.9.1/src/terminal/key.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/key.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,16 +3,18 @@ #include #include -#include "../util/macros.h" +#include "util/macros.h" enum { + KEY_TAB = '\t', KEY_ENTER = '\n', + KEY_SPACE = ' ', // This is the maximum Unicode codepoint allowed by RFC 3629. // When stored in a 32-bit integer, it only requires the first // 21 low-order bits, leaving 11 high-order bits available to // be used as bit flags. - KEY_UNICODE_MAX = 0x010FFFF, + KEY_UNICODE_MAX = 0x10FFFF, // In addition to the 11 unused, high-order bits, there are also // some unused values in the range from KEY_UNICODE_MAX + 1 to @@ -26,10 +28,11 @@ KEY_DOWN, KEY_RIGHT, KEY_LEFT, - KEY_PAGE_DOWN, + KEY_BEGIN, KEY_END, - KEY_PAGE_UP, + KEY_PAGE_DOWN, KEY_HOME, + KEY_PAGE_UP, KEY_F1, KEY_F2, KEY_F3, @@ -45,14 +48,16 @@ KEY_SPECIAL_MAX = KEY_F12, NR_SPECIAL_KEYS = KEY_SPECIAL_MAX - KEY_SPECIAL_MIN + 1, + MOD_OFFSET = 24, // Modifier bit flags (as described above) - MOD_CTRL = 0x1000000, - MOD_META = 0x2000000, - MOD_SHIFT = 0x4000000, - MOD_MASK = MOD_CTRL | MOD_META | MOD_SHIFT, + MOD_SHIFT = 1 << MOD_OFFSET, + MOD_META = 2 << MOD_OFFSET, + MOD_CTRL = 4 << MOD_OFFSET, + MOD_MASK = MOD_SHIFT | MOD_META | MOD_CTRL, KEY_PASTE = 0x8000000, + KEY_IGNORE = 0x8000001, }; typedef uint32_t KeyCode; @@ -70,7 +75,7 @@ static inline KeyCode keycode_normalize(KeyCode k) { switch (k) { - case '\t': return k; + case '\t': return KEY_TAB; case '\r': return KEY_ENTER; case 0x7F: return MOD_CTRL | '?'; } @@ -82,7 +87,7 @@ #define CTRL(x) (MOD_CTRL | (x)) -bool parse_key(KeyCode *key, const char *str); -const char *key_to_string(KeyCode key) RETURNS_NONNULL; +bool parse_key_string(KeyCode *key, const char *str); +const char *keycode_to_string(KeyCode key) RETURNS_NONNULL; #endif diff -Nru dte-1.9.1/src/terminal/kitty.c dte-1.10/src/terminal/kitty.c --- dte-1.9.1/src/terminal/kitty.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/kitty.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,190 @@ +#include "kitty.h" +#include "util/ascii.h" +#include "util/base64.h" +#include "util/macros.h" + +/* + Kitty's extended key encoding scheme is based on a quirky base85 + alphabet and a set of key mappings that are very unamenable to + algorithmic translation. Since most of the useful keys are encoded + in a single digit, we may as well just map the bytes directly to + KeyCodes, instead of doing a base85 decode followed by yet another + table lookup for translation. + + See: https://sw.kovidgoyal.net/kitty/key-encoding.html +*/ +static const KeyCode kitty_1digit_keys[] = { + ['0'] = KEY_TAB, + ['1'] = MOD_CTRL | '?', // Backspace + ['2'] = KEY_INSERT, + ['3'] = KEY_DELETE, + ['4'] = KEY_RIGHT, + ['5'] = KEY_LEFT, + ['6'] = KEY_DOWN, + ['7'] = KEY_UP, + ['8'] = KEY_PAGE_UP, + ['9'] = KEY_PAGE_DOWN, + ['-'] = KEY_END, + ['.'] = KEY_HOME, + ['/'] = KEY_F1, + ['*'] = KEY_F2, + ['?'] = KEY_F3, + ['&'] = KEY_F4, + ['<'] = KEY_F5, + ['>'] = KEY_F6, + ['('] = KEY_F7, + [')'] = KEY_F8, + ['['] = KEY_F9, + [']'] = KEY_F10, + ['{'] = KEY_F11, + ['}'] = KEY_F12, + ['@'] = KEY_IGNORE, // F13 + ['%'] = KEY_IGNORE, // F14 + ['$'] = KEY_IGNORE, // F15 + ['#'] = KEY_IGNORE, // F16 + ['!'] = KEY_IGNORE, // Pause + ['+'] = KEY_IGNORE, // Scroll Lock + [':'] = KEY_IGNORE, // Caps Lock + ['='] = KEY_IGNORE, // Num Lock + ['^'] = KEY_IGNORE, // Print Screen + + ['A'] = KEY_SPACE, + ['B'] = '\'', ['C'] = ',', ['D'] = '-', ['E'] = '.', ['F'] = '/', + + ['G'] = '0', ['H'] = '1', ['I'] = '2', ['J'] = '3', ['K'] = '4', + ['L'] = '5', ['M'] = '6', ['N'] = '7', ['O'] = '8', ['P'] = '9', + + ['Q'] = ';', ['R'] = '=', + + ['S'] = 'a', ['T'] = 'b', ['U'] = 'c', ['V'] = 'd', ['W'] = 'e', + ['X'] = 'f', ['Y'] = 'g', ['Z'] = 'h', + + ['a'] = 'i', ['b'] = 'j', ['c'] = 'k', ['d'] = 'l', ['e'] = 'm', + ['f'] = 'n', ['g'] = 'o', ['h'] = 'p', ['i'] = 'q', ['j'] = 'r', + ['k'] = 's', ['l'] = 't', ['m'] = 'u', ['n'] = 'v', ['o'] = 'w', + ['p'] = 'x', ['q'] = 'y', ['r'] = 'z', + + ['s'] = '[', ['t'] = '\\', ['u'] = ']', ['v'] = '`', + + ['w'] = KEY_IGNORE, // "World 1" + ['x'] = KEY_IGNORE, // "World 2" + ['y'] = MOD_CTRL | '[', // Escape + ['z'] = KEY_ENTER, +}; + +// https://sw.kovidgoyal.net/kitty/protocol-extensions.html#keyboard-handling +ssize_t kitty_parse_full_mode_key(const char *buf, size_t length, size_t i, KeyCode *k) +{ + if (unlikely(i >= length)) { + return -1; + } + + // Note: "\e_K" has already been parsed by parse_apc() + bool is_key_release; + switch (buf[i++]) { + case 'p': // Press + case 't': // Repeat + is_key_release = false; + break; + case 'r': // Release + is_key_release = true; + break; + default: + return 0; + } + + if (unlikely(i >= length)) { + return -1; + } + KeyCode mods = base64_decode(buf[i++]); + if (unlikely(mods > 15)) { + return 0; + } + + if (unlikely(i >= length)) { + return -1; + } + + unsigned char c1 = buf[i++]; + KeyCode key; + if (c1 == 'B') { + // Handle (possibly 2-digit) keys starting with 'B' + if (unlikely(i >= length)) { + return -1; + } + unsigned char c2 = buf[i++]; + switch (c2) { + // 1-digit 'B' is apostrophe key (\033 is probably start of ST) + case '\033': + key = '\''; + goto st_slash; + // Left/right Shift/Ctrl/Alt/Super + case 'a': case 'b': case 'c': case 'd': + case 'e': case 'f': case 'g': case 'h': + // F17-F25 + case 'A': case 'B': case 'C': case 'D': + case 'E': case 'F': case 'G': case 'H': + case 'I': + key = KEY_IGNORE; + break; + // Keypad 0-9 + case 'J': case 'K': case 'L': case 'M': + case 'N': case 'O': case 'P': case 'Q': + case 'R': case 'S': + key = '0' + (c2 - 'J'); + break; + // Keypad other + case 'T': key = '.'; break; + case 'U': key = '/'; break; + case 'V': key = '*'; break; + case 'W': key = '-'; break; + case 'X': key = '+'; break; + case 'Y': key = KEY_ENTER; break; + case 'Z': key = '='; break; + default: + return 0; + } + } else { + // Look up 1-digit keys + if (unlikely(c1 >= ARRAY_COUNT(kitty_1digit_keys))) { + return 0; + } + key = kitty_1digit_keys[c1]; + if (unlikely(key == 0)) { + return 0; + } + } + + // Check string terminator (ST) + if (unlikely(i >= length)) { + return -1; + } else if (buf[i++] != '\033') { + return 0; + } + st_slash: + if (unlikely(i >= length)) { + return -1; + } else if (buf[i++] != '\\') { + return 0; + } + + if (is_key_release || key == KEY_IGNORE || mods > 7) { + *k = KEY_IGNORE; + return i; + } + + mods <<= MOD_OFFSET; + if (unlikely( + mods & (MOD_CTRL | MOD_SHIFT) + && key < 127 + && ascii_isalpha(key) + )) { + // Remove Shift modifier for Ctrl+Shift+[A-Z] combos; for the + // same reason as noted in normalize_modified_other_key() + mods &= ~MOD_SHIFT; + key = ascii_toupper(key); + } + + *k = mods | key; + return i; +} diff -Nru dte-1.9.1/src/terminal/kitty.h dte-1.10/src/terminal/kitty.h --- dte-1.9.1/src/terminal/kitty.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/kitty.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +#ifndef TERMINAL_KITTY_H +#define TERMINAL_KITTY_H + +#include +#include "key.h" + +ssize_t kitty_parse_full_mode_key(const char *buf, size_t length, size_t i, KeyCode *k); + +#endif diff -Nru dte-1.9.1/src/terminal/mode.c dte-1.10/src/terminal/mode.c --- dte-1.9.1/src/terminal/mode.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/mode.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,70 @@ +#include +#include +#include +#include "mode.h" +#include "util/debug.h" +#include "util/macros.h" + +static bool initialized; +static struct termios cooked, raw, raw_isig; + +bool term_mode_init(void) +{ + BUG_ON(initialized); + if (unlikely(tcgetattr(STDIN_FILENO, &cooked) != 0)) { + return false; + } + + // Set up "raw" mode (roughly equivalent to cfmakeraw(3) on Linux/BSD) + raw = cooked; + raw.c_iflag &= ~( + ICRNL | IXON | IXOFF + | IGNBRK | BRKINT | PARMRK + | ISTRIP | INLCR | IGNCR + ); + raw.c_oflag &= ~OPOST; + raw.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + raw.c_cflag &= ~(CSIZE | PARENB); + raw.c_cflag |= CS8; + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + + // Set up raw+ISIG mode + raw_isig = raw; + raw_isig.c_lflag |= ISIG; + raw_isig.c_cc[VSUSP] = _POSIX_VDISABLE; + + initialized = true; + return true; +} + +static void xtcsetattr(const struct termios *t) +{ + if (unlikely(!initialized)) { + return; + } + + int ret; + do { + ret = tcsetattr(STDIN_FILENO, TCSANOW, t); + } while (unlikely(ret != 0 && errno == EINTR)); + + if (unlikely(ret != 0)) { + fatal_error("tcsetattr", errno); + } +} + +void term_raw(void) +{ + xtcsetattr(&raw); +} + +void term_raw_isig(void) +{ + xtcsetattr(&raw_isig); +} + +void term_cooked(void) +{ + xtcsetattr(&cooked); +} diff -Nru dte-1.9.1/src/terminal/mode.h dte-1.10/src/terminal/mode.h --- dte-1.9.1/src/terminal/mode.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/mode.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,11 @@ +#ifndef TERMINAL_MODE_H +#define TERMINAL_MODE_H + +#include + +bool term_mode_init(void); +void term_raw(void); +void term_raw_isig(void); +void term_cooked(void); + +#endif diff -Nru dte-1.9.1/src/terminal/no-op.c dte-1.10/src/terminal/no-op.c --- dte-1.9.1/src/terminal/no-op.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/no-op.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -#include "no-op.h" -#include "../util/macros.h" - -void no_op(void) -{ -} - -void no_op_s(const char* UNUSED_ARG(s)) -{ -} diff -Nru dte-1.9.1/src/terminal/no-op.h dte-1.10/src/terminal/no-op.h --- dte-1.9.1/src/terminal/no-op.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/no-op.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -#ifndef TERMINAL_NO_OP_H -#define TERMINAL_NO_OP_H - -void no_op(void); -void no_op_s(const char *s); - -#endif diff -Nru dte-1.9.1/src/terminal/output.c dte-1.10/src/terminal/output.c --- dte-1.9.1/src/terminal/output.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/output.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,25 +1,19 @@ #include -#include -#include +#include #include #include "output.h" #include "terminal.h" -#include "../debug.h" -#include "../util/ascii.h" -#include "../util/utf8.h" -#include "../util/xmalloc.h" -#include "../util/xreadwrite.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/numtostr.h" +#include "util/utf8.h" +#include "util/xreadwrite.h" TermOutputBuffer obuf; -static size_t obuf_avail(void) -{ - return sizeof(obuf.buf) - obuf.count; -} - static void obuf_need_space(size_t count) { - if (obuf_avail() < count) { + if (unlikely(obuf_avail() < count)) { term_output_flush(); } } @@ -35,11 +29,11 @@ } // Does not update obuf.x -void term_add_bytes(const char *const str, size_t count) +void term_add_bytes(const char *str, size_t count) { - if (count > obuf_avail()) { + if (unlikely(count > obuf_avail())) { term_output_flush(); - if (count >= sizeof(obuf.buf)) { + if (unlikely(count >= sizeof(obuf.buf))) { xwrite(STDOUT_FILENO, str, count); return; } @@ -91,42 +85,7 @@ } } -VPRINTF(1) -static void term_vsprintf(const char *fmt, va_list ap) -{ - va_list ap2; - va_copy(ap2, ap); - // Calculate the required size - int n = vsnprintf(NULL, 0, fmt, ap2); - va_end(ap2); - BUG_ON(n < 0); - - if (n >= obuf_avail()) { - term_output_flush(); - if (n >= sizeof(obuf.buf)) { - char *tmp = xmalloc(n + 1); - int wrote = vsnprintf(tmp, n + 1, fmt, ap); - BUG_ON(wrote != n); - xwrite(STDOUT_FILENO, tmp, n); - free(tmp); - return; - } - } - - int wrote = vsnprintf(obuf.buf + obuf.count, n + 1, fmt, ap); - BUG_ON(wrote != n); - obuf.count += wrote; -} - -void term_sprintf(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - term_vsprintf(fmt, ap); - va_end(ap); -} - -void term_add_str(const char *const str) +void term_add_str(const char *str) { size_t i = 0; while (str[i]) { @@ -136,6 +95,13 @@ } } +// Does not update obuf.x +void term_add_uint(unsigned int x) +{ + obuf_need_space(DECIMAL_STR_MAX(x)); + obuf.count += buf_uint_to_str(x, obuf.buf + obuf.count); +} + void term_hide_cursor(void) { terminal.put_control_code(terminal.control_codes.hide_cursor); @@ -146,18 +112,36 @@ terminal.put_control_code(terminal.control_codes.show_cursor); } +void term_move_cursor(unsigned int x, unsigned int y) +{ + terminal.move_cursor(x, y); +} + +void term_save_title(void) +{ + terminal.put_control_code(terminal.control_codes.save_title); +} + +void term_restore_title(void) +{ + terminal.put_control_code(terminal.control_codes.restore_title); +} + void term_clear_eol(void) { - if (obuf.x < obuf.scroll_x + obuf.width) { - if ( - obuf.can_clear - && (obuf.color.bg < 0 || terminal.back_color_erase) - ) { - terminal.clear_to_eol(); - obuf.x = obuf.scroll_x + obuf.width; - } else { - term_set_bytes(' ', obuf.scroll_x + obuf.width - obuf.x); - } + const size_t end = obuf.scroll_x + obuf.width; + if (obuf.x >= end) { + return; + } + if ( + obuf.can_clear + && (obuf.color.bg < 0 || terminal.back_color_erase) + && !(obuf.color.attr & ATTR_REVERSE) + ) { + terminal.clear_to_eol(); + obuf.x = end; + } else { + term_set_bytes(' ', end - obuf.x); } } @@ -172,27 +156,26 @@ static void skipped_too_much(CodePoint u) { size_t n = obuf.x - obuf.scroll_x; - + char *buf = obuf.buf + obuf.count; obuf_need_space(8); if (u == '\t' && obuf.tab != TAB_CONTROL) { - char ch = ' '; - if (obuf.tab == TAB_SPECIAL) { - ch = '-'; - } - memset(obuf.buf + obuf.count, ch, n); + memset(buf, (obuf.tab == TAB_SPECIAL) ? '-' : ' ', n); obuf.count += n; } else if (u < 0x20) { - obuf.buf[obuf.count++] = u | 0x40; + *buf = u | 0x40; + obuf.count++; } else if (u == 0x7f) { - obuf.buf[obuf.count++] = '?'; + *buf = '?'; + obuf.count++; } else if (u_is_unprintable(u)) { char tmp[4]; size_t idx = 0; u_set_hex(tmp, &idx, u); - memcpy(obuf.buf + obuf.count, tmp + 4 - n, n); + memcpy(buf, tmp + 4 - n, n); obuf.count += n; } else { - obuf.buf[obuf.count++] = '>'; + *buf = '>'; + obuf.count++; } } @@ -220,8 +203,7 @@ static void print_tab(size_t width) { char ch = ' '; - - if (obuf.tab == TAB_SPECIAL) { + if (unlikely(obuf.tab == TAB_SPECIAL)) { obuf.buf[obuf.count++] = '>'; obuf.x++; width--; @@ -236,41 +218,41 @@ bool term_put_char(CodePoint u) { - size_t space = obuf.scroll_x + obuf.width - obuf.x; - size_t width; - if (obuf.x < obuf.scroll_x) { // Scrolled, char (at least partially) invisible buf_skip(u); return true; } + const size_t space = obuf.scroll_x + obuf.width - obuf.x; if (!space) { return false; } obuf_need_space(8); - if (u < 0x80) { - if (!ascii_iscntrl(u)) { + if (likely(u < 0x80)) { + if (likely(!ascii_iscntrl(u))) { obuf.buf[obuf.count++] = u; obuf.x++; } else if (u == '\t' && obuf.tab != TAB_CONTROL) { - width = (obuf.x + obuf.tab_width) / obuf.tab_width * obuf.tab_width - obuf.x; + const size_t tw = obuf.tab_width; + const size_t x = obuf.x; + size_t width = (x + tw) / tw * tw - x; if (width > space) { width = space; } print_tab(width); } else { - u_set_ctrl(obuf.buf, &obuf.count, u); - obuf.x += 2; - if (unlikely(space == 1)) { - // Wrote too much - obuf.count--; - obuf.x--; + // Use caret notation for control chars: + obuf.buf[obuf.count++] = '^'; + obuf.x++; + if (space > 1) { + obuf.buf[obuf.count++] = (u + 64) & 0x7F; + obuf.x++; } } } else { - width = u_char_width(u); + const size_t width = u_char_width(u); if (width <= space) { obuf.x += width; u_set_char(obuf.buf, &obuf.count, u); @@ -287,5 +269,6 @@ obuf.x++; } } + return true; } diff -Nru dte-1.9.1/src/terminal/output.h dte-1.10/src/terminal/output.h --- dte-1.9.1/src/terminal/output.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/output.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,11 +3,10 @@ #include #include -#include #include "color.h" -#include "../util/macros.h" -#include "../util/string-view.h" -#include "../util/unicode.h" +#include "util/macros.h" +#include "util/string-view.h" +#include "util/unicode.h" typedef struct { char buf[8192]; @@ -37,16 +36,24 @@ #define term_add_literal(s) term_add_bytes(s, STRLEN(s)) +static inline size_t obuf_avail(void) +{ + return sizeof(obuf.buf) - obuf.count; +} + void term_output_reset(size_t start_x, size_t width, size_t scroll_x); void term_add_byte(char ch); void term_add_bytes(const char *str, size_t count); void term_set_bytes(char ch, size_t count); void term_repeat_byte(char ch, size_t count); void term_add_string_view(StringView sv); -void term_sprintf(const char *fmt, ...) PRINTF(1); void term_add_str(const char *str); +void term_add_uint(unsigned int x); void term_hide_cursor(void); void term_show_cursor(void); +void term_move_cursor(unsigned int x, unsigned int y); +void term_save_title(void); +void term_restore_title(void); void term_clear_eol(void); void term_output_flush(void); bool term_put_char(CodePoint u); diff -Nru dte-1.9.1/src/terminal/rxvt.c dte-1.10/src/terminal/rxvt.c --- dte-1.9.1/src/terminal/rxvt.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/rxvt.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,61 @@ +#include "rxvt.h" +#include "xterm.h" + +/* + rxvt uses some key codes that differ from the ones used by xterm. + They don't conflict or overlap with the xterm codes, but specifically + aren't handled by xterm_parse_key() because some of them violate the + ECMA-48 spec (notably, the ones ending with '$', which isn't a valid + final byte). +*/ +ssize_t rxvt_parse_key(const char *buf, size_t length, KeyCode *k) +{ + KeyCode extra_mods = 0; + size_t extra_bytes = 0; + + if (length < 2) { + goto xterm; + } + + if (buf[0] == '\033' && buf[1] == '\033') { + extra_mods = MOD_META; + extra_bytes = 1; + buf++; + length--; + } + + if (length < 4 || buf[0] != '\033' || buf[1] != '[') { + goto xterm; + } + + KeyCode key; + ssize_t n; + switch (buf[3]) { + case '~': key = 0; break; + case '^': key = MOD_CTRL; break; + case '$': key = MOD_SHIFT; break; + case '@': key = MOD_SHIFT | MOD_CTRL; break; + default: goto xterm; + } + + switch (buf[2]) { + case '2': key |= KEY_INSERT; break; + case '3': key |= KEY_DELETE; break; + case '5': key |= KEY_PAGE_UP; break; + case '6': key |= KEY_PAGE_DOWN; break; + case '7': key |= KEY_HOME; break; + case '8': key |= KEY_END; break; + default: goto xterm; + } + + *k = key | extra_mods; + return 4 + extra_bytes; + +xterm: + n = xterm_parse_key(buf, length, &key); + if (n <= 0) { + return n; + } + *k = key | extra_mods; + return n + extra_bytes; +} diff -Nru dte-1.9.1/src/terminal/rxvt.h dte-1.10/src/terminal/rxvt.h --- dte-1.9.1/src/terminal/rxvt.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/rxvt.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +#ifndef TERMINAL_RXVT_H +#define TERMINAL_RXVT_H + +#include +#include "key.h" + +ssize_t rxvt_parse_key(const char *buf, size_t length, KeyCode *k); + +#endif diff -Nru dte-1.9.1/src/terminal/terminal.c dte-1.10/src/terminal/terminal.c --- dte-1.9.1/src/terminal/terminal.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/terminal.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,135 +1,232 @@ +#include #include +#include #include #include +#include #include "terminal.h" #include "ecma48.h" -#include "terminfo.h" +#include "mode.h" +#include "output.h" +#include "rxvt.h" #include "xterm.h" -#include "../debug.h" -#include "../util/macros.h" -#include "../util/str-util.h" - -#define S(str) str,STRLEN(str) +#include "util/bsearch.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/str-util.h" typedef enum { - TERM_OTHER, - TERM_LINUX, - TERM_SCREEN, - TERM_ST, - TERM_TMUX, - TERM_URXVT, - TERM_XTERM, - TERM_KITTY, -} TerminalType; + BCE = 0x01, // Can erase with specific background color (back color erase) + REP = 0x02, // Supports ECMA-48 "REP" (repeat character) + TITLE = 0x04, // Supports xterm control codes for setting window title +} TermFlags; + +typedef struct { + const char name[12]; + uint8_t name_len; + uint8_t color_type; + uint8_t ncv_attributes; + uint8_t flags; +} TermEntry; + +static const TermEntry terms[] = { + {"Eterm", 5, TERM_8_COLOR, 0, BCE}, + {"alacritty", 9, TERM_TRUE_COLOR, 0, BCE | REP}, + {"ansi", 4, TERM_8_COLOR, 3, 0}, + {"ansiterm", 8, TERM_0_COLOR, 0, 0}, + {"aterm", 5, TERM_8_COLOR, 0, BCE}, + {"cx", 2, TERM_8_COLOR, 0, 0}, + {"cx100", 5, TERM_8_COLOR, 0, 0}, + {"cygwin", 6, TERM_8_COLOR, 0, 0}, + {"cygwinB19", 9, TERM_8_COLOR, 3, 0}, + {"cygwinDBG", 9, TERM_8_COLOR, 3, 0}, + {"decansi", 7, TERM_8_COLOR, 0, 0}, + {"domterm", 7, TERM_8_COLOR, 0, BCE}, + {"dtterm", 6, TERM_8_COLOR, 0, 0}, + {"dvtm", 4, TERM_8_COLOR, 0, 0}, + {"fbterm", 6, TERM_256_COLOR, 18, BCE}, + {"foot", 4, TERM_TRUE_COLOR, 0, BCE | REP | TITLE}, + {"hurd", 4, TERM_8_COLOR, 18, BCE}, + {"iTerm.app", 9, TERM_256_COLOR, 0, BCE}, + {"iTerm2.app", 10, TERM_256_COLOR, 0, BCE | TITLE}, + {"iterm", 5, TERM_256_COLOR, 0, BCE}, + {"iterm2", 6, TERM_256_COLOR, 0, BCE | TITLE}, + {"jfbterm", 7, TERM_8_COLOR, 18, BCE}, + {"kitty", 5, TERM_TRUE_COLOR, 0, TITLE}, + {"kon", 3, TERM_8_COLOR, 18, BCE}, + {"kon2", 4, TERM_8_COLOR, 18, BCE}, + {"konsole", 7, TERM_8_COLOR, 0, BCE}, + {"kterm", 5, TERM_8_COLOR, 0, 0}, + {"linux", 5, TERM_8_COLOR, 18, BCE}, + {"mgt", 3, TERM_8_COLOR, 0, BCE}, + {"mintty", 6, TERM_8_COLOR, 0, BCE | REP | TITLE}, + {"mlterm", 6, TERM_8_COLOR, 0, TITLE}, + {"mlterm2", 7, TERM_8_COLOR, 0, TITLE}, + {"mlterm3", 7, TERM_8_COLOR, 0, TITLE}, + {"mrxvt", 5, TERM_8_COLOR, 0, BCE | TITLE}, + {"pcansi", 6, TERM_8_COLOR, 3, 0}, + {"putty", 5, TERM_8_COLOR, 22, BCE}, + {"rxvt", 4, TERM_8_COLOR, 0, BCE | TITLE}, + {"screen", 6, TERM_8_COLOR, 0, TITLE}, + {"st", 2, TERM_8_COLOR, 0, BCE}, + {"stterm", 6, TERM_8_COLOR, 0, BCE}, + {"teken", 5, TERM_8_COLOR, 21, BCE}, + {"terminator", 10, TERM_256_COLOR, 0, BCE | TITLE}, + {"termite", 7, TERM_8_COLOR, 0, TITLE}, + {"tmux", 4, TERM_8_COLOR, 0, TITLE}, + {"xfce", 4, TERM_8_COLOR, 0, BCE | TITLE}, + {"xterm", 5, TERM_8_COLOR, 0, BCE | TITLE}, + {"xterm.js", 8, TERM_8_COLOR, 0, BCE}, +}; + +static const struct { + const char suffix[11]; + uint8_t suffix_len; + TermColorCapabilityType color_type; +} color_suffixes[] = { + {"direct", 6, TERM_TRUE_COLOR}, + {"256color", 8, TERM_256_COLOR}, + {"16color", 7, TERM_16_COLOR}, + {"mono", 4, TERM_0_COLOR}, + {"m", 1, TERM_0_COLOR}, +}; + +Terminal terminal = { + .back_color_erase = false, + .color_type = TERM_8_COLOR, + .width = 80, + .height = 24, + .parse_key_sequence = &xterm_parse_key, + .put_control_code = &term_add_string_view, + .clear_screen = &ecma48_clear_screen, + .clear_to_eol = &ecma48_clear_to_eol, + .set_color = &ecma48_set_color, + .move_cursor = &ecma48_move_cursor, + .repeat_byte = &term_repeat_byte, + .control_codes = { + // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + .init = STRING_VIEW ( + // 1036 = metaSendsEscape + // 1039 = altSendsEscape + // 2017 = kitty "full" keyboard mode + "\033[?1036;1039s" // Save + "\033[?1036;1039;2017h" // Enable + ), + .deinit = STRING_VIEW ( + "\033[?1036;1039r" // Restore + "\033[?2017l" // Disable + ), + .keypad_off = STRING_VIEW("\033[?1l\033>"), + .keypad_on = STRING_VIEW("\033[?1h\033="), + .cup_mode_off = STRING_VIEW("\033[?1049l"), + .cup_mode_on = STRING_VIEW("\033[?1049h"), + .hide_cursor = STRING_VIEW("\033[?25l"), + .show_cursor = STRING_VIEW("\033[?25h"), + .set_title_begin = STRING_VIEW_INIT, + .set_title_end = STRING_VIEW_INIT, + .save_title = STRING_VIEW_INIT, + .restore_title = STRING_VIEW_INIT, + } +}; -static TerminalType get_term_type(const char *term) +static int term_name_compare(const void *key, const void *elem) { - static const struct { - const char name[14]; - uint8_t name_len; - uint8_t type; - } builtin_terminals[] = { - {S("xterm-kitty"), TERM_KITTY}, - {S("xterm"), TERM_XTERM}, - {S("st"), TERM_ST}, - {S("stterm"), TERM_ST}, - {S("tmux"), TERM_TMUX}, - {S("screen"), TERM_SCREEN}, - {S("linux"), TERM_LINUX}, - {S("rxvt-unicode"), TERM_URXVT}, - }; - const size_t term_len = strlen(term); - for (size_t i = 0; i < ARRAY_COUNT(builtin_terminals); i++) { - const size_t n = builtin_terminals[i].name_len; - if (term_len >= n && memcmp(term, builtin_terminals[i].name, n) == 0) { - if (term[n] == '-' || term[n] == '\0') { - return builtin_terminals[i].type; - } - } + const StringView *prefix = key; + const TermEntry *entry = elem; + size_t cmplen = MIN(prefix->length, (size_t)entry->name_len); + int res = memcmp(prefix->data, entry->name, cmplen); + if (res == 0) { + return (int)prefix->length - (int)entry->name_len; } - return TERM_OTHER; + return res; } UNITTEST { - BUG_ON(get_term_type("xterm") != TERM_XTERM); - BUG_ON(get_term_type("xterm-kitty") != TERM_KITTY); - BUG_ON(get_term_type("tmux") != TERM_TMUX); - BUG_ON(get_term_type("st") != TERM_ST); - BUG_ON(get_term_type("stterm") != TERM_ST); - BUG_ON(get_term_type("linux") != TERM_LINUX); - BUG_ON(get_term_type("xterm-256color") != TERM_XTERM); - BUG_ON(get_term_type("screen-256color") != TERM_SCREEN); - BUG_ON(get_term_type("x") != TERM_OTHER); - BUG_ON(get_term_type("xter") != TERM_OTHER); - BUG_ON(get_term_type("xtermz") != TERM_OTHER); -} - -NORETURN -void term_init_fail(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - putc('\n', stderr); - fflush(stderr); - exit(1); + static const StringView tests[] = { + STRING_VIEW("Eterm"), + STRING_VIEW("alacritty"), + STRING_VIEW("ansi"), + STRING_VIEW("mlterm"), + STRING_VIEW("mlterm2"), + STRING_VIEW("mlterm3"), + STRING_VIEW("st"), + STRING_VIEW("stterm"), + STRING_VIEW("xterm"), + }; + CHECK_BSEARCH_ARRAY(terms, name, strcmp); + for (size_t i = 0; i < ARRAY_COUNT(tests); i++) { + const TermEntry *entry = BSEARCH(&tests[i], terms, term_name_compare); + BUG_ON(!entry); + BUG_ON(!strview_equal_strn(&tests[i], entry->name, entry->name_len)); + } } void term_init(void) { const char *const term = getenv("TERM"); - if (term == NULL || term[0] == '\0') { - term_init_fail("'TERM' not set"); + if (!term || term[0] == '\0') { + init_error("'TERM' not set"); + } + + if (!term_mode_init()) { + init_error("tcgetattr: %s", strerror(errno)); } - if (getenv("DTE_FORCE_TERMINFO")) { - if (term_init_terminfo(term)) { - return; - } else { - term_init_fail("'DTE_FORCE_TERMINFO' set but terminfo not linked"); + // Strip phony "xterm-" prefix used by certain terminals + const char *real_term = term; + if (str_has_prefix(term, "xterm-")) { + const char *str = term + STRLEN("xterm-"); + if (str_has_prefix(str, "kitty") || str_has_prefix(str, "termite")) { + real_term = str; } } - switch (get_term_type(term)) { - case TERM_XTERM: - terminal = xterm; - // terminal.repeat_byte = &ecma48_repeat_byte; - break; - case TERM_ST: - case TERM_URXVT: - terminal = xterm; - break; - case TERM_TMUX: - case TERM_SCREEN: - case TERM_KITTY: - terminal = xterm; - terminal.back_color_erase = false; - break; - case TERM_LINUX: - // Use the default Terminal and just change the control codes - terminal.control_codes.hide_cursor = xterm.control_codes.hide_cursor; - terminal.control_codes.show_cursor = xterm.control_codes.show_cursor; - break; - case TERM_OTHER: - if (term_init_terminfo(term)) { - return; + // Extract the "root name" from $TERM, as defined by terminfo(5). + // This is the initial part of the string up to the first hyphen. + size_t pos = 0; + size_t rtlen = strlen(real_term); + StringView name = get_delim(real_term, &pos, rtlen, '-'); + + // Look up the root name in the list of known terminals + const TermEntry *entry = BSEARCH(&name, terms, term_name_compare); + if (entry) { + terminal.color_type = entry->color_type; + terminal.ncv_attributes = entry->ncv_attributes; + terminal.back_color_erase = !!(entry->flags & BCE); + if (entry->flags & REP) { + terminal.repeat_byte = ecma48_repeat_byte; + } + if (entry->flags & TITLE) { + terminal.control_codes.save_title = strview_from_cstring("\033[22;2t"); + terminal.control_codes.restore_title = strview_from_cstring("\033[23;2t"); + terminal.control_codes.set_title_begin = strview_from_cstring("\033]2;"); + terminal.control_codes.set_title_end = strview_from_cstring("\033\\"); + } + if (streq(entry->name, "rxvt") || streq(entry->name, "mrxvt")) { + terminal.parse_key_sequence = rxvt_parse_key; } - break; + const int n = (int)name.length; + DEBUG_LOG("using built-in terminal support for '%.*s'", n, name.data); } - const char *colorterm = getenv("COLORTERM"); - if (colorterm && streq(colorterm, "truecolor")) { + if (xstreq(getenv("COLORTERM"), "truecolor")) { terminal.color_type = TERM_TRUE_COLOR; + DEBUG_LOG("true color support detected ($COLORTERM)"); + } + if (terminal.color_type == TERM_TRUE_COLOR) { return; } - if ( - terminal.color_type < TERM_256_COLOR - && str_has_suffix(term, "256color") - ) { - terminal.color_type = TERM_256_COLOR; - } else if (str_has_suffix(term, "-direct")) { - terminal.color_type = TERM_TRUE_COLOR; + while (pos < rtlen) { + const StringView str = get_delim(real_term, &pos, rtlen, '-'); + for (size_t i = 0; i < ARRAY_COUNT(color_suffixes); i++) { + const char *suffix = color_suffixes[i].suffix; + size_t len = color_suffixes[i].suffix_len; + if (strview_equal_strn(&str, suffix, len)) { + terminal.color_type = color_suffixes[i].color_type; + DEBUG_LOG("color type detected from $TERM suffix '-%s'", suffix); + return; + } + } } } diff -Nru dte-1.9.1/src/terminal/terminal.h dte-1.10/src/terminal/terminal.h --- dte-1.9.1/src/terminal/terminal.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/terminal.h 2021-04-03 21:08:53.000000000 +0000 @@ -5,27 +5,29 @@ #include #include "color.h" #include "key.h" -#include "../util/macros.h" -#include "../util/string-view.h" +#include "util/macros.h" +#include "util/string-view.h" typedef struct { StringView init; StringView deinit; - StringView reset_colors; - StringView reset_attrs; StringView keypad_off; StringView keypad_on; StringView cup_mode_off; StringView cup_mode_on; StringView show_cursor; StringView hide_cursor; + StringView save_title; + StringView restore_title; + StringView set_title_begin; + StringView set_title_end; } TermControlCodes; typedef struct { bool back_color_erase; TermColorCapabilityType color_type; - int width; - int height; + unsigned int width; + unsigned int height; unsigned int ncv_attributes; TermControlCodes control_codes; ssize_t (*parse_key_sequence)(const char *buf, size_t length, KeyCode *key); @@ -33,20 +35,12 @@ void (*clear_screen)(void); void (*clear_to_eol)(void); void (*set_color)(const TermColor *color); - void (*move_cursor)(int x, int y); + void (*move_cursor)(unsigned int x, unsigned int y); void (*repeat_byte)(char ch, size_t count); - void (*raw)(void); - void (*cooked)(void); - void (*save_title)(void); - void (*restore_title)(void); - void (*set_title)(const char *title); } Terminal; extern Terminal terminal; void term_init(void); -NORETURN COLD PRINTF(1) -void term_init_fail(const char *fmt, ...); - #endif diff -Nru dte-1.9.1/src/terminal/terminfo.c dte-1.10/src/terminal/terminfo.c --- dte-1.9.1/src/terminal/terminfo.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/terminfo.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,375 +0,0 @@ -#include "terminfo.h" -#include "../debug.h" -#include "../util/macros.h" - -#ifndef TERMINFO_DISABLE - -#include -#include -#include "key.h" -#include "output.h" -#include "terminal.h" -#include "xterm.h" -#include "../util/ascii.h" -#include "../util/str-util.h" -#include "../util/string-view.h" - -#define KEY(c, k) { \ - .code = (c), \ - .code_length = (sizeof(c) - 1), \ - .key = (k) \ -} - -#define XKEYS(p, key) \ - KEY(p, key | MOD_SHIFT), \ - KEY(p "3", key | MOD_META), \ - KEY(p "4", key | MOD_SHIFT | MOD_META), \ - KEY(p "5", key | MOD_CTRL), \ - KEY(p "6", key | MOD_SHIFT | MOD_CTRL), \ - KEY(p "7", key | MOD_META | MOD_CTRL), \ - KEY(p "8", key | MOD_SHIFT | MOD_META | MOD_CTRL) - -static struct { - const char *clear; - const char *cup; - const char *el; - const char *setab; - const char *setaf; - const char *sgr; -} terminfo; - -static struct TermKeyMap { - const char *code; - uint32_t code_length; - KeyCode key; -} keymap[] = { - KEY("kcuu1", KEY_UP), - KEY("kcud1", KEY_DOWN), - KEY("kcub1", KEY_LEFT), - KEY("kcuf1", KEY_RIGHT), - KEY("kdch1", KEY_DELETE), - KEY("kpp", KEY_PAGE_UP), - KEY("knp", KEY_PAGE_DOWN), - KEY("khome", KEY_HOME), - KEY("kend", KEY_END), - KEY("kich1", KEY_INSERT), - KEY("kcbt", MOD_SHIFT | '\t'), - KEY("kf1", KEY_F1), - KEY("kf2", KEY_F2), - KEY("kf3", KEY_F3), - KEY("kf4", KEY_F4), - KEY("kf5", KEY_F5), - KEY("kf6", KEY_F6), - KEY("kf7", KEY_F7), - KEY("kf8", KEY_F8), - KEY("kf9", KEY_F9), - KEY("kf10", KEY_F10), - KEY("kf11", KEY_F11), - KEY("kf12", KEY_F12), - XKEYS("kUP", KEY_UP), - XKEYS("kDN", KEY_DOWN), - XKEYS("kLFT", KEY_LEFT), - XKEYS("kRIT", KEY_RIGHT), - XKEYS("kDC", KEY_DELETE), - XKEYS("kPRV", KEY_PAGE_UP), - XKEYS("kNXT", KEY_PAGE_DOWN), - XKEYS("kHOM", KEY_HOME), - XKEYS("kEND", KEY_END), -}; - -static_assert(ARRAY_COUNT(keymap) == 23 + (9 * 7)); - -static size_t keymap_length = 0; - -static ssize_t parse_key_from_keymap(const char *buf, size_t fill, KeyCode *key) -{ - bool possibly_truncated = false; - for (size_t i = 0; i < keymap_length; i++) { - const struct TermKeyMap *const km = &keymap[i]; - const char *const keycode = km->code; - const size_t len = km->code_length; - BUG_ON(keycode == NULL); - BUG_ON(len == 0); - if (len > fill) { - // This might be a truncated escape sequence - if ( - possibly_truncated == false - && memcmp(keycode, buf, fill) == 0 - ) { - possibly_truncated = true; - } - continue; - } - if (memcmp(keycode, buf, len) != 0) { - continue; - } - *key = km->key; - return len; - } - return possibly_truncated ? -1 : 0; -} - -// These are normally declared in the and headers. -// They are not included here because of the insane number of unprefixed -// symbols they declare and because of previous bugs caused by using them. -int setupterm(const char *term, int filedes, int *errret); -int tigetflag(const char *capname); -int tigetnum(const char *capname); -char *tigetstr(const char *capname); -int tputs(const char *str, int affcnt, int (*putc_fn)(int)); -char *tparm(const char*, long, long, long, long, long, long, long, long, long); -#define tparm_1(str, p1) tparm(str, p1, 0, 0, 0, 0, 0, 0, 0, 0) -#define tparm_2(str, p1, p2) tparm(str, p1, p2, 0, 0, 0, 0, 0, 0, 0) - -static char *get_terminfo_string(const char *capname) -{ - char *str = tigetstr(capname); - if (str == (char *)-1) { - // Not a string cap (bug?) - return NULL; - } - // NULL = canceled or absent - return str; -} - -static StringView get_terminfo_string_view(const char *capname) -{ - return string_view_from_cstring(get_terminfo_string(capname)); -} - -static bool get_terminfo_flag(const char *capname) -{ - switch (tigetflag(capname)) { - case -1: // Not a boolean capability - case 0: // Canceled or absent - return false; - } - return true; -} - -static int tputs_putc(int ch) -{ - term_add_byte(ch); - return ch; -} - -static void tputs_control_code(StringView code) -{ - if (code.length) { - tputs(code.data, 1, tputs_putc); - } -} - -static void tputs_clear_screen(void) -{ - if (terminfo.clear) { - tputs(terminfo.clear, terminal.height, tputs_putc); - } -} - -static void tputs_clear_to_eol(void) -{ - if (terminfo.el) { - tputs(terminfo.el, 1, tputs_putc); - } -} - -static void tputs_move_cursor(int x, int y) -{ - if (terminfo.cup) { - const char *seq = tparm_2(terminfo.cup, y, x); - if (seq) { - tputs(seq, 1, tputs_putc); - } - } -} - -static bool attr_is_set(const TermColor *color, unsigned int attr) -{ - if (!(color->attr & attr)) { - return false; - } else if (terminal.ncv_attributes & attr) { - // Terminal only allows attr when not using colors - return color->fg == COLOR_DEFAULT && color->bg == COLOR_DEFAULT; - } - return true; -} - -static void tputs_set_color(const TermColor *color) -{ - if (same_color(color, &obuf.color)) { - return; - } - - if (terminfo.sgr) { - const char *attrs = tparm ( - terminfo.sgr, - 0, // p1 = "standout" (unused) - attr_is_set(color, ATTR_UNDERLINE), - attr_is_set(color, ATTR_REVERSE), - attr_is_set(color, ATTR_BLINK), - attr_is_set(color, ATTR_DIM), - attr_is_set(color, ATTR_BOLD), - attr_is_set(color, ATTR_INVIS), - 0, // p8 = "protect" (unused) - 0 // p9 = "altcharset" (unused) - ); - tputs(attrs, 1, tputs_putc); - } - - TermColor c = *color; - if (terminfo.setaf && c.fg >= 0) { - const char *seq = tparm_1(terminfo.setaf, c.fg); - if (seq) { - tputs(seq, 1, tputs_putc); - } - } - if (terminfo.setab && c.bg >= 0) { - const char *seq = tparm_1(terminfo.setab, c.bg); - if (seq) { - tputs(seq, 1, tputs_putc); - } - } - - obuf.color = *color; -} - -static unsigned int convert_ncv_flags_to_attrs(unsigned int ncv) -{ - // These flags should have values equal to their terminfo - // counterparts: - static_assert(ATTR_UNDERLINE == 2); - static_assert(ATTR_REVERSE == 4); - static_assert(ATTR_BLINK == 8); - static_assert(ATTR_DIM == 16); - static_assert(ATTR_BOLD == 32); - static_assert(ATTR_INVIS == 64); - - // Mask flags to supported, common subset - unsigned int attrs = ncv & ( - ATTR_UNDERLINE | ATTR_REVERSE | ATTR_BLINK - | ATTR_DIM | ATTR_BOLD | ATTR_INVIS - ); - - // Italic is a special case; it occupies bit 16 in terminfo - // but bit 7 here - if (ncv & 0x8000) { - attrs |= ATTR_ITALIC; - } - - return attrs; -} - -bool term_init_terminfo(const char *term) -{ - // Initialize terminfo database (or call exit(3) on failure) - setupterm(term, 1, (int*)0); - - terminal.put_control_code = &tputs_control_code; - terminal.clear_screen = &tputs_clear_screen; - terminal.clear_to_eol = &tputs_clear_to_eol; - terminal.set_color = &tputs_set_color; - terminal.move_cursor = &tputs_move_cursor; - - if (get_terminfo_flag("nxon")) { - term_init_fail ( - "TERM type '%s' not supported: 'nxon' flag is set", - term - ); - } - - terminfo.cup = get_terminfo_string("cup"); - if (terminfo.cup == NULL) { - term_init_fail ( - "TERM type '%s' not supported: no 'cup' capability", - term - ); - } - - terminfo.clear = get_terminfo_string("clear"); - terminfo.el = get_terminfo_string("el"); - terminfo.setab = get_terminfo_string("setab"); - terminfo.setaf = get_terminfo_string("setaf"); - terminfo.sgr = get_terminfo_string("sgr"); - - terminal.back_color_erase = get_terminfo_flag("bce"); - terminal.width = tigetnum("cols"); - terminal.height = tigetnum("lines"); - - switch (tigetnum("colors")) { - case 16777216: - // Just use the built-in xterm_set_color() function if true color - // support is indicated. This bypasses tputs(3), but no true color - // terminal in existence actually depends on archaic tputs(3) - // features (like e.g. baudrate-dependant padding). - terminal.color_type = TERM_TRUE_COLOR; - terminal.set_color = &xterm_set_color; - break; - case 256: - terminal.color_type = TERM_256_COLOR; - break; - case 16: - terminal.color_type = TERM_16_COLOR; - break; - case 88: - case 8: - terminal.color_type = TERM_8_COLOR; - break; - default: - terminal.color_type = TERM_0_COLOR; - break; - } - - const int ncv = tigetnum("ncv"); - if (ncv <= 0) { - terminal.ncv_attributes = 0; - } else { - terminal.ncv_attributes = convert_ncv_flags_to_attrs(ncv); - } - - terminal.control_codes = (TermControlCodes) { - .reset_colors = get_terminfo_string_view("op"), - .reset_attrs = get_terminfo_string_view("sgr0"), - .keypad_off = get_terminfo_string_view("rmkx"), - .keypad_on = get_terminfo_string_view("smkx"), - .cup_mode_off = get_terminfo_string_view("rmcup"), - .cup_mode_on = get_terminfo_string_view("smcup"), - .show_cursor = get_terminfo_string_view("cnorm"), - .hide_cursor = get_terminfo_string_view("civis") - }; - - bool xterm_compatible_key_codes = true; - - for (size_t i = 0; i < ARRAY_COUNT(keymap); i++) { - const char *const code = get_terminfo_string(keymap[i].code); - if (code && code[0] != '\0') { - const size_t code_len = strlen(code); - const KeyCode key = keymap[i].key; - keymap[keymap_length++] = (struct TermKeyMap) { - .code = code, - .code_length = code_len, - .key = key - }; - KeyCode parsed_key; - const ssize_t parsed_len = xterm_parse_key(code, code_len, &parsed_key); - if (parsed_len <= 0 || parsed_key != key) { - xterm_compatible_key_codes = false; - } - } - } - - if (!xterm_compatible_key_codes) { - terminal.parse_key_sequence = &parse_key_from_keymap; - } - - return true; // Initialization succeeded -} - -#else - -bool term_init_terminfo(const char* UNUSED_ARG(term)) -{ - return false; // terminfo not available -} - -#endif // ifndef TERMINFO_DISABLE diff -Nru dte-1.9.1/src/terminal/terminfo.h dte-1.10/src/terminal/terminfo.h --- dte-1.9.1/src/terminal/terminfo.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/terminfo.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -#ifndef TERMINAL_TERMINFO_H -#define TERMINAL_TERMINFO_H - -#include - -bool term_init_terminfo(const char *term); - -#endif diff -Nru dte-1.9.1/src/terminal/winsize.c dte-1.10/src/terminal/winsize.c --- dte-1.9.1/src/terminal/winsize.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/winsize.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,42 @@ +#include "../../build/feature.h" +#include +#include "winsize.h" + +#if defined(HAVE_TIOCGWINSZ) + +#include + +bool term_get_size(unsigned int *w, unsigned int *h) +{ + struct winsize ws; + if (unlikely(ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1)) { + return false; + } + *w = ws.ws_col; + *h = ws.ws_row; + return true; +} + +#elif defined(HAVE_TCGETWINSIZE) + +#include + +bool term_get_size(unsigned int *w, unsigned int *h) +{ + struct winsize ws; + if (unlikely(tcgetwinsize(STDIN_FILENO, &ws) != 0)) { + return false; + } + *w = ws.ws_col; + *h = ws.ws_row; + return true; +} + +#else + +bool term_get_size(unsigned int* UNUSED_ARG(w), unsigned int* UNUSED_ARG(h)) +{ + return false; +} + +#endif diff -Nru dte-1.9.1/src/terminal/winsize.h dte-1.10/src/terminal/winsize.h --- dte-1.9.1/src/terminal/winsize.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/terminal/winsize.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +#ifndef TERMINAL_WINSIZE_H +#define TERMINAL_WINSIZE_H + +#include +#include "util/macros.h" + +bool term_get_size(unsigned int *w, unsigned int *h) NONNULL_ARGS; + +#endif diff -Nru dte-1.9.1/src/terminal/xterm.c dte-1.10/src/terminal/xterm.c --- dte-1.9.1/src/terminal/xterm.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/xterm.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,116 +1,307 @@ -#include +// Escape sequence parser for xterm function keys. +// Copyright 2018-2020 Craig Barnes. +// SPDX-License-Identifier: GPL-2.0-only +// See also: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html + +#include #include "xterm.h" -#include "ecma48.h" -#include "output.h" +#include "kitty.h" +#include "util/ascii.h" +#include "util/debug.h" +#include "util/macros.h" +#include "util/unicode.h" + +static const KeyCode special_keys[] = { + [1] = KEY_HOME, + [2] = KEY_INSERT, + [3] = KEY_DELETE, + [4] = KEY_END, + [5] = KEY_PAGE_UP, + [6] = KEY_PAGE_DOWN, + [7] = KEY_HOME, + [8] = KEY_END, + [11] = KEY_F1, + [12] = KEY_F2, + [13] = KEY_F3, + [14] = KEY_F4, + [15] = KEY_F5, + [17] = KEY_F6, + [18] = KEY_F7, + [19] = KEY_F8, + [20] = KEY_F9, + [21] = KEY_F10, + [23] = KEY_F11, + [24] = KEY_F12, +}; -void xterm_save_title(void) +static KeyCode decode_modifiers(uint32_t n) { - term_add_literal("\033[22;2t"); + n--; + if (unlikely(n > 15)) { + return 0; + } + + // Decode Meta (bit 4) and/or Alt (bit 2) as just Meta + KeyCode mods = (n & 7) | ((n & 8) >> 2); + BUG_ON(mods > 7); + + return mods << MOD_OFFSET; } -void xterm_restore_title(void) -{ - term_add_literal("\033[23;2t"); -} - -void xterm_set_title(const char *title) -{ - term_add_literal("\033]2;"); - term_add_bytes(title, strlen(title)); - term_add_byte('\007'); -} - -static void do_set_color(int32_t color, char ch) -{ - if (color < 0) { - return; - } - - term_add_byte(';'); - term_add_byte(ch); - - if (color < 8) { - term_add_byte('0' + color); - } else if (color < 256) { - term_sprintf("8;5;%hhu", (uint8_t)color); - } else { - uint8_t r, g, b; - color_split_rgb(color, &r, &g, &b); - term_sprintf("8;2;%hhu;%hhu;%hhu", r, g, b); - } -} - -void xterm_set_color(const TermColor *color) -{ - static const struct { - char code; - unsigned int attr; - } attr_map[] = { - {'1', ATTR_BOLD}, - {'2', ATTR_DIM}, - {'3', ATTR_ITALIC}, - {'4', ATTR_UNDERLINE}, - {'5', ATTR_BLINK}, - {'7', ATTR_REVERSE}, - {'8', ATTR_INVIS}, - {'9', ATTR_STRIKETHROUGH} - }; - - if (same_color(color, &obuf.color)) { - return; - } - - term_add_literal("\033[0"); - - for (size_t j = 0; j < ARRAY_COUNT(attr_map); j++) { - if (color->attr & attr_map[j].attr) { - term_add_byte(';'); - term_add_byte(attr_map[j].code); - } - } - - do_set_color(color->fg, '3'); - do_set_color(color->bg, '4'); - - term_add_byte('m'); - obuf.color = *color; -} - -const Terminal xterm = { - .back_color_erase = true, - .color_type = TERM_8_COLOR, - .width = 80, - .height = 24, - .raw = &term_raw, - .cooked = &term_cooked, - .parse_key_sequence = &xterm_parse_key, - .put_control_code = &term_add_string_view, - .clear_screen = &ecma48_clear_screen, - .clear_to_eol = &ecma48_clear_to_eol, - .set_color = &xterm_set_color, - .move_cursor = &ecma48_move_cursor, - .repeat_byte = &term_repeat_byte, - .save_title = &xterm_save_title, - .restore_title = &xterm_restore_title, - .set_title = &xterm_set_title, - .control_codes = { - // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html - .init = STRING_VIEW ( - // 1036 = metaSendsEscape - // 1039 = altSendsEscape - "\033[?1036;1039s" // Save - "\033[?1036;1039h" // Enable - ), - .deinit = STRING_VIEW ( - "\033[?1036;1039r" // Restore - ), - .reset_colors = STRING_VIEW("\033[39;49m"), - .reset_attrs = STRING_VIEW("\033[0m"), - .keypad_off = STRING_VIEW("\033[?1l\033>"), - .keypad_on = STRING_VIEW("\033[?1h\033="), - .cup_mode_off = STRING_VIEW("\033[?1049l"), - .cup_mode_on = STRING_VIEW("\033[?1049h"), - .hide_cursor = STRING_VIEW("\033[?25l"), - .show_cursor = STRING_VIEW("\033[?25h"), +static KeyCode decode_special_key(uint32_t n) +{ + return (n >= ARRAY_COUNT(special_keys)) ? 0 : special_keys[n]; +} + +// Fix quirky key codes sent when "modifyOtherKeys" is enabled +static KeyCode normalize_modified_other_key(KeyCode mods, KeyCode key) +{ + if (key > 0x20 && key < 0x80) { + // The Shift modifier is never appropriate with the + // printable ASCII range, since pressing Shift causes + // the base key itself to change (i.e. "r" becomes "R', + // "." becomes ">", etc.) + mods &= ~MOD_SHIFT; + if (mods & MOD_CTRL) { + // The Ctrl modifier should always cause letters to + // be uppercase -- this assumption is too ingrained + // and causes too much breakage if not enforced + key = ascii_toupper(key); + } } -}; + return mods | keycode_normalize(key); +} + +static ssize_t parse_ss3(const char *buf, size_t length, size_t i, KeyCode *k) +{ + if (unlikely(i >= length)) { + return -1; + } + const char ch = buf[i++]; + switch (ch) { + case 'A': // Up + case 'B': // Down + case 'C': // Right + case 'D': // Left + case 'E': // Begin (keypad '5') + case 'F': // End + case 'H': // Home + *k = KEY_UP + (ch - 'A'); + return i; + case 'M': + *k = KEY_ENTER; + return i; + case 'P': // F1 + case 'Q': // F2 + case 'R': // F3 + case 'S': // F4 + *k = KEY_F1 + (ch - 'P'); + return i; + case 'X': + *k = '='; + return i; + case ' ': + *k = KEY_SPACE; + return i; + case 'a': // Ctrl+Up (rxvt) + case 'b': // Ctrl+Down (rxvt) + case 'c': // Ctrl+Right (rxvt) + case 'd': // Ctrl+Left (rxvt) + *k = MOD_CTRL | (KEY_UP + (ch - 'a')); + return i; + case 'j': case 'k': case 'l': + case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': + case 's': case 't': case 'u': + case 'v': case 'w': case 'x': + case 'y': case 'I': + *k = ch - 64; + return i; + } + return 0; +} + +static ssize_t parse_csi_num(const char *buf, size_t len, size_t i, KeyCode *k) +{ + uint32_t params[4] = {0, 0, 0, 0}; + size_t nparams = 0; + uint8_t final_byte = 0; + + uint32_t num = 0; + size_t digits = 0; + while (i < len) { + const char ch = buf[i++]; + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + num = (num * 10) + (ch - '0'); + if (unlikely(num > UNICODE_MAX_VALID_CODEPOINT)) { + return 0; + } + digits++; + continue; + case ';': + params[nparams++] = num; + if (unlikely(nparams > 2)) { + return 0; + } + num = 0; + digits = 0; + continue; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'H': case 'P': case 'Q': case 'R': + case 'S': case 'u': case '~': + final_byte = ch; + if (digits > 0) { + params[nparams++] = num; + } + goto exit_loop; + } + return 0; + } +exit_loop: + + if (unlikely(final_byte == 0)) { + return (i >= len) ? -1 : 0; + } + + KeyCode mods = 0; + KeyCode key; + + switch (nparams) { + case 3: + if (params[0] == 27 && final_byte == '~') { + mods = decode_modifiers(params[1]); + if (unlikely(mods == 0)) { + return 0; + } + *k = normalize_modified_other_key(mods, params[2]); + return i; + } + return 0; + case 2: + mods = decode_modifiers(params[1]); + if (unlikely(mods == 0)) { + return 0; + } + switch (final_byte) { + case '~': + goto check_first_param_is_special_key; + case 'u': + *k = normalize_modified_other_key(mods, params[0]); + return i; + case 'A': // Up + case 'B': // Down + case 'C': // Right + case 'D': // Left + case 'E': // Begin (keypad '5') + case 'F': // End + case 'H': // Home + key = KEY_UP + (final_byte - 'A'); + goto check_first_param_is_1; + case 'P': // F1 + case 'Q': // F2 + case 'R': // F3 + case 'S': // F4 + key = KEY_F1 + (final_byte - 'P'); + goto check_first_param_is_1; + } + return 0; + case 1: + if (final_byte == '~') { + goto check_first_param_is_special_key; + } + return 0; + } + return 0; + +check_first_param_is_special_key: + key = decode_special_key(params[0]); + if (unlikely(key == 0)) { + return 0; + } + goto set_k_and_return_i; +check_first_param_is_1: + if (unlikely(params[0] != 1)) { + return 0; + } +set_k_and_return_i: + *k = mods | key; + return i; +} + +static ssize_t parse_csi(const char *buf, size_t length, size_t i, KeyCode *k) +{ + if (unlikely(i >= length)) { + return -1; + } + char ch = buf[i++]; + switch (ch) { + case 'A': // Up + case 'B': // Down + case 'C': // Right + case 'D': // Left + case 'E': // Begin (keypad '5') + case 'F': // End + case 'H': // Home + *k = KEY_UP + (ch - 'A'); + return i; + case 'a': // Shift+Up (rxvt) + case 'b': // Shift+Down (rxvt) + case 'c': // Shift+Right (rxvt) + case 'd': // Shift+Left (rxvt) + *k = MOD_SHIFT | (KEY_UP + (ch - 'a')); + return i; + case 'L': + *k = KEY_INSERT; + return i; + case 'Z': + *k = MOD_SHIFT | KEY_TAB; + return i; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + return parse_csi_num(buf, length, i - 1, k); + case '[': + if (unlikely(i >= length)) { + return -1; + } + switch (ch = buf[i++]) { + // Linux console keys + case 'A': // F1 + case 'B': // F2 + case 'C': // F3 + case 'D': // F4 + case 'E': // F5 + *k = KEY_F1 + (ch - 'A'); + return i; + } + return 0; + } + return 0; +} + +static ssize_t parse_apc(const char *buf, size_t length, size_t i, KeyCode *k) +{ + if (unlikely(i >= length)) { + return -1; + } + if (buf[i] == 'K') { + return kitty_parse_full_mode_key(buf, length, i + 1, k); + } + return 0; +} + +ssize_t xterm_parse_key(const char *buf, size_t length, KeyCode *k) +{ + if (unlikely(length == 0 || buf[0] != '\033')) { + return 0; + } else if (unlikely(length == 1)) { + return -1; + } + switch (buf[1]) { + case 'O': return parse_ss3(buf, length, 2, k); + case '[': return parse_csi(buf, length, 2, k); + case '_': return parse_apc(buf, length, 2, k); + } + return 0; +} diff -Nru dte-1.9.1/src/terminal/xterm.h dte-1.10/src/terminal/xterm.h --- dte-1.9.1/src/terminal/xterm.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/xterm.h 2021-04-03 21:08:53.000000000 +0000 @@ -2,16 +2,8 @@ #define TERMINAL_XTERM_H #include -#include "color.h" #include "key.h" -#include "terminal.h" -void xterm_save_title(void); -void xterm_restore_title(void); -void xterm_set_title(const char *title); -void xterm_set_color(const TermColor *color); ssize_t xterm_parse_key(const char *buf, size_t length, KeyCode *k); -extern const Terminal xterm; - #endif diff -Nru dte-1.9.1/src/terminal/xterm-keys.c dte-1.10/src/terminal/xterm-keys.c --- dte-1.9.1/src/terminal/xterm-keys.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/terminal/xterm-keys.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,291 +0,0 @@ -// Escape sequence parser for xterm function keys. -// Copyright 2018-2019 Craig Barnes. -// SPDX-License-Identifier: GPL-2.0-only -// See also: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html - -#include -#include "xterm.h" -#include "../util/ascii.h" -#include "../util/macros.h" -#include "../util/unicode.h" - -static const KeyCode modifiers[] = { - [2] = MOD_SHIFT, - [3] = MOD_META, - [4] = MOD_SHIFT | MOD_META, - [5] = MOD_CTRL, - [6] = MOD_SHIFT | MOD_CTRL, - [7] = MOD_META | MOD_CTRL, - [8] = MOD_SHIFT | MOD_META | MOD_CTRL, -}; - -static const KeyCode special_keys[] = { - [1] = KEY_HOME, - [2] = KEY_INSERT, - [3] = KEY_DELETE, - [4] = KEY_END, - [5] = KEY_PAGE_UP, - [6] = KEY_PAGE_DOWN, - [7] = KEY_HOME, - [8] = KEY_END, - [11] = KEY_F1, - [12] = KEY_F2, - [13] = KEY_F3, - [14] = KEY_F4, - [15] = KEY_F5, - [17] = KEY_F6, - [18] = KEY_F7, - [19] = KEY_F8, - [20] = KEY_F9, - [21] = KEY_F10, - [23] = KEY_F11, - [24] = KEY_F12, -}; - -static KeyCode decode_modifiers(uint32_t n) -{ - return (n >= ARRAY_COUNT(modifiers)) ? 0 : modifiers[n]; -} - -static KeyCode decode_special_key(uint32_t n) -{ - return (n >= ARRAY_COUNT(special_keys)) ? 0 : special_keys[n]; -} - -// Fix quirky key codes sent when "modifyOtherKeys" is enabled -static KeyCode normalize_modified_other_key(KeyCode mods, KeyCode key) -{ - if (key > 0x20 && key < 0x80) { - // The Shift modifier is never appropriate with the - // printable ASCII range, since pressing Shift causes - // the base key itself to change (i.e. "r" becomes "R', - // "." becomes ">", etc.) - mods &= ~MOD_SHIFT; - if (mods & MOD_CTRL) { - // The Ctrl modifier should always cause letters to - // be uppercase -- this assumption is too ingrained - // and causes too much breakage if not enforced - key = ascii_toupper(key); - } - } - return mods | keycode_normalize(key); -} - -static ssize_t parse_ss3(const char *buf, size_t length, size_t i, KeyCode *k) -{ - if (i >= length) { - return -1; - } - const char ch = buf[i++]; - switch (ch) { - case 'A': // Up - case 'B': // Down - case 'C': // Right - case 'D': // Left - case 'F': // End - case 'H': // Home - *k = KEY_UP + (ch - 'A'); - return i; - case 'M': - *k = KEY_ENTER; - return i; - case 'P': // F1 - case 'Q': // F2 - case 'R': // F3 - case 'S': // F4 - *k = KEY_F1 + (ch - 'P'); - return i; - case 'X': - *k = '='; - return i; - case ' ': - *k = ch; - return i; - case 'a': // Ctrl+Up (rxvt) - case 'b': // Ctrl+Down (rxvt) - case 'c': // Ctrl+Right (rxvt) - case 'd': // Ctrl+Left (rxvt) - *k = MOD_CTRL | (KEY_UP + (ch - 'a')); - return i; - case 'j': case 'k': case 'l': - case 'm': case 'n': case 'o': - case 'p': case 'q': case 'r': - case 's': case 't': case 'u': - case 'v': case 'w': case 'x': - case 'y': case 'I': - *k = ch - 64; - return i; - } - return 0; -} - -static ssize_t parse_csi_num(const char *buf, size_t len, size_t i, KeyCode *k) -{ - uint32_t params[4] = {0, 0, 0, 0}; - size_t nparams = 0; - uint8_t final_byte = 0; - - uint32_t num = 0; - size_t digits = 0; - while (i < len) { - const char ch = buf[i++]; - switch (ch) { - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - num = (num * 10) + (ch - '0'); - if (num > UNICODE_MAX_VALID_CODEPOINT) { - return 0; - } - digits++; - continue; - case ';': - params[nparams++] = num; - if (nparams > 2) { - return 0; - } - num = 0; - digits = 0; - continue; - case 'A': case 'B': case 'C': case 'D': case 'F': - case 'H': case 'P': case 'Q': case 'R': case 'S': - case 'u': case '~': - final_byte = ch; - if (digits > 0) { - params[nparams++] = num; - } - goto exit_loop; - } - return 0; - } -exit_loop: - - if (final_byte == 0) { - return (i >= len) ? -1 : 0; - } - - KeyCode mods = 0; - KeyCode key; - - switch (nparams) { - case 3: - if (params[0] == 27 && final_byte == '~') { - mods = decode_modifiers(params[1]); - if (mods == 0) { - return 0; - } - *k = normalize_modified_other_key(mods, params[2]); - return i; - } - return 0; - case 2: - mods = decode_modifiers(params[1]); - if (mods == 0) { - return 0; - } - switch (final_byte) { - case '~': - goto check_first_param_is_special_key; - case 'u': - *k = normalize_modified_other_key(mods, params[0]); - return i; - case 'A': // Up - case 'B': // Down - case 'C': // Right - case 'D': // Left - case 'F': // End - case 'H': // Home - key = KEY_UP + (final_byte - 'A'); - goto check_first_param_is_1; - case 'P': // F1 - case 'Q': // F2 - case 'R': // F3 - case 'S': // F4 - key = KEY_F1 + (final_byte - 'P'); - goto check_first_param_is_1; - } - return 0; - case 1: - if (final_byte == '~') { - goto check_first_param_is_special_key; - } - return 0; - } - return 0; - -check_first_param_is_special_key: - key = decode_special_key(params[0]); - if (key == 0) { - return 0; - } - goto set_k_and_return_i; -check_first_param_is_1: - if (params[0] != 1) { - return 0; - } -set_k_and_return_i: - *k = mods | key; - return i; -} - -static ssize_t parse_csi(const char *buf, size_t length, size_t i, KeyCode *k) -{ - if (i >= length) { - return -1; - } - char ch = buf[i++]; - switch (ch) { - case 'A': // Up - case 'B': // Down - case 'C': // Right - case 'D': // Left - case 'F': // End - case 'H': // Home - *k = KEY_UP + (ch - 'A'); - return i; - case 'a': // Shift+Up (rxvt) - case 'b': // Shift+Down (rxvt) - case 'c': // Shift+Right (rxvt) - case 'd': // Shift+Left (rxvt) - *k = MOD_SHIFT | (KEY_UP + (ch - 'a')); - return i; - case 'L': - *k = KEY_INSERT; - return i; - case 'Z': - *k = MOD_SHIFT | '\t'; - return i; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - return parse_csi_num(buf, length, i - 1, k); - case '[': - if (i >= length) { - return -1; - } - switch (ch = buf[i++]) { - // Linux console keys - case 'A': // F1 - case 'B': // F2 - case 'C': // F3 - case 'D': // F4 - case 'E': // F5 - *k = KEY_F1 + (ch - 'A'); - return i; - } - return 0; - } - return 0; -} - -ssize_t xterm_parse_key(const char *buf, size_t length, KeyCode *k) -{ - if (length == 0 || buf[0] != '\033') { - return 0; - } else if (length == 1) { - return -1; - } - switch (buf[1]) { - case 'O': return parse_ss3(buf, length, 2, k); - case '[': return parse_csi(buf, length, 2, k); - } - return 0; -} diff -Nru dte-1.9.1/src/util/ascii.c dte-1.10/src/util/ascii.c --- dte-1.9.1/src/util/ascii.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/ascii.c 2021-04-03 21:08:53.000000000 +0000 @@ -13,33 +13,14 @@ }; const uint8_t ascii_table[256] = { - [0x00] = C, [0x01] = C, [0x02] = C, [0x03] = C, [0x04] = C, - [0x05] = C, [0x06] = C, ['\a'] = C, ['\b'] = C, ['\t'] = s, - ['\n'] = s, ['\v'] = C, ['\f'] = C, ['\r'] = s, [0x0E] = C, - C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // 0x0F .. 0x1F - - [' '] = S, - ['_'] = u, - - ['('] = R, [')'] = R, ['*'] = R, ['+'] = R, ['.'] = R, - ['?'] = R, ['['] = R, ['{'] = R, ['|'] = R, ['\\'] = R, - - ['0'] = D, ['1'] = D, ['2'] = D, ['3'] = D, ['4'] = D, - ['5'] = D, ['6'] = D, ['7'] = D, ['8'] = D, ['9'] = D, - - ['A'] = U, ['B'] = U, ['C'] = U, ['D'] = U, ['E'] = U, ['F'] = U, - ['G'] = U, ['H'] = U, ['I'] = U, ['J'] = U, ['K'] = U, ['L'] = U, - ['M'] = U, ['N'] = U, ['O'] = U, ['P'] = U, ['Q'] = U, ['R'] = U, - ['S'] = U, ['T'] = U, ['U'] = U, ['V'] = U, ['W'] = U, ['X'] = U, - ['Y'] = U, ['Z'] = U, - - ['a'] = L, ['b'] = L, ['c'] = L, ['d'] = L, ['e'] = L, ['f'] = L, - ['g'] = L, ['h'] = L, ['i'] = L, ['j'] = L, ['k'] = L, ['l'] = L, - ['m'] = L, ['n'] = L, ['o'] = L, ['p'] = L, ['q'] = L, ['r'] = L, - ['s'] = L, ['t'] = L, ['u'] = L, ['v'] = L, ['w'] = L, ['x'] = L, - ['y'] = L, ['z'] = L, - - [0x7F] = C, + C, C, C, C, C, C, C, C, C, s, s, C, C, s, C, C, // 0x00 .. 0x0F + C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // 0x10 .. 0x1F + S, 0, 0, 0, 0, 0, 0, 0, R, R, R, R, 0, 0, R, 0, // 0x20 .. 0x2F + D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, R, // 0x30 .. 0x3F + 0, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, // 0x40 .. 0x4F + U, U, U, U, U, U, U, U, U, U, U, R, R, 0, 0, u, // 0x50 .. 0x5F + 0, L, L, L, L, L, L, L, L, L, L, L, L, L, L, L, // 0x60 .. 0x6F + L, L, L, L, L, L, L, L, L, L, L, R, R, 0, 0, C, // 0x70 .. 0x7F N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0x80 .. 0x8F N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0x90 .. 0x9F N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xA0 .. 0xAF @@ -49,22 +30,3 @@ N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xE0 .. 0xEF N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, // 0xF0 .. 0xFF }; - -const int8_t hex_table[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 -}; diff -Nru dte-1.9.1/src/util/ascii.h dte-1.10/src/util/ascii.h --- dte-1.9.1/src/util/ascii.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/ascii.h 2021-04-03 21:08:53.000000000 +0000 @@ -7,22 +7,21 @@ #include "macros.h" extern const uint8_t ascii_table[256]; -extern const int8_t hex_table[256]; -#define ASCII_SPACE 0x01 -#define ASCII_DIGIT 0x02 -#define ASCII_CNTRL 0x04 -#define ASCII_REGEX 0x08 -#define ASCII_LOWER 0x10 -#define ASCII_UPPER 0x20 -#define ASCII_UNDERSCORE 0x40 -#define ASCII_NONASCII 0x80 - -#define ASCII_ALPHA (ASCII_LOWER | ASCII_UPPER) -#define ASCII_ALNUM (ASCII_ALPHA | ASCII_DIGIT) -#define ASCII_WORDBYTE (ASCII_ALNUM | ASCII_UNDERSCORE | ASCII_NONASCII) +typedef enum { + ASCII_SPACE = 0x01, + ASCII_DIGIT = 0x02, + ASCII_CNTRL = 0x04, + ASCII_REGEX = 0x08, + ASCII_LOWER = 0x10, + ASCII_UPPER = 0x20, + ASCII_UNDERSCORE = 0x40, + ASCII_NONASCII = 0x80, + ASCII_ALPHA = ASCII_LOWER | ASCII_UPPER, + ASCII_ALNUM = ASCII_ALPHA | ASCII_DIGIT, + ASCII_WORDBYTE = ASCII_ALNUM | ASCII_UNDERSCORE | ASCII_NONASCII, +} AsciiCharType; -#define ascii_test(x, mask) ((ascii_table[(unsigned char)(x)] & (mask)) != 0) #define ascii_isspace(x) ascii_test(x, ASCII_SPACE) #define ascii_isdigit(x) ascii_test(x, ASCII_DIGIT) #define ascii_iscntrl(x) ascii_test(x, ASCII_CNTRL) @@ -31,13 +30,17 @@ #define ascii_isalpha(x) ascii_test(x, ASCII_ALPHA) #define ascii_isalnum(x) ascii_test(x, ASCII_ALNUM) #define ascii_isprint(x) (!ascii_test(x, ASCII_CNTRL | ASCII_NONASCII)) -#define ascii_isxdigit(x) (hex_decode(x) != -1) #define is_alpha_or_underscore(x) ascii_test(x, ASCII_ALPHA | ASCII_UNDERSCORE) #define is_alnum_or_underscore(x) ascii_test(x, ASCII_ALNUM | ASCII_UNDERSCORE) #define is_regex_special_char(x) ascii_test(x, ASCII_REGEX) #define is_word_byte(x) ascii_test(x, ASCII_WORDBYTE) +static inline bool ascii_test(unsigned char c, AsciiCharType mask) +{ + return (ascii_table[c] & mask) != 0; +} + static inline bool ascii_isblank(unsigned char c) { return c == ' ' || c == '\t'; @@ -50,33 +53,37 @@ static inline unsigned char ascii_tolower(unsigned char c) { + static_assert(ASCII_UPPER == 0x20); return c + (ascii_table[c] & ASCII_UPPER); } static inline unsigned char ascii_toupper(unsigned char c) { + static_assert(ASCII_LOWER << 1 == 0x20); return c - ((ascii_table[c] & ASCII_LOWER) << 1); } NONNULL_ARGS -static inline bool ascii_streq_icase(const char *s1, const char *s2) +static inline int ascii_strcmp_icase(const char *s1, const char *s2) { unsigned char c1, c2; - bool chars_equal; + int result; size_t i = 0; - // Iterate to the index where the strings differ or a NUL byte is found do { c1 = ascii_tolower(s1[i]); c2 = ascii_tolower(s2[i]); - chars_equal = (c1 == c2); + result = c1 - c2; i++; - } while (c1 && chars_equal); + } while (c1 && result == 0); + + return result; +} - // If the loop terminated because a NUL byte was found and the - // last characters were the same, both strings terminate in the - // same place and are therefore equal - return chars_equal; +NONNULL_ARGS +static inline bool ascii_streq_icase(const char *s1, const char *s2) +{ + return ascii_strcmp_icase(s1, s2) == 0; } NONNULL_ARGS @@ -84,18 +91,12 @@ { const unsigned char *s1 = p1; const unsigned char *s2 = p2; - while (n) { + while (n--) { if (ascii_tolower(*s1++) != ascii_tolower(*s2++)) { return false; } - n--; } return true; } -static inline int hex_decode(unsigned char c) -{ - return hex_table[c]; -} - #endif diff -Nru dte-1.9.1/src/util/base64.c dte-1.10/src/util/base64.c --- dte-1.9.1/src/util/base64.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/base64.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,25 @@ +#include "base64.h" + +enum { + I = BASE64_INVALID, + P = BASE64_PADDING, +}; + +const uint8_t base64_table[256] = { + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, 62, I, I, I, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, I, I, I, P, I, I, + I, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, I, I, I, I, I, + I, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I +}; diff -Nru dte-1.9.1/src/util/base64.h dte-1.10/src/util/base64.h --- dte-1.9.1/src/util/base64.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/base64.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,19 @@ +#ifndef UTIL_BASE64_H +#define UTIL_BASE64_H + +#include + +enum { + BASE64_PADDING = 1 << 6, // Return value for padding bytes (=) + BASE64_INVALID = 1 << 7, // Return value for invalid bytes ([^A-Za-z0-9+/=]) +}; + +// Decodes a single, base64 digit and returns a numerical value between 0-63, +// or one of the special enum values above. +static inline unsigned int base64_decode(unsigned char c) +{ + extern const uint8_t base64_table[256]; + return base64_table[c]; +} + +#endif diff -Nru dte-1.9.1/src/util/bit.h dte-1.10/src/util/bit.h --- dte-1.9.1/src/util/bit.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/bit.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,93 +0,0 @@ -#ifndef UTIL_BIT_H -#define UTIL_BIT_H - -#include -#include "macros.h" -#include "../debug.h" - -#define U64 UINT64_C -#define U32 UINT32_C - -#if GNUC_AT_LEAST(3, 4) -#define USE_BUILTIN(fn, arg) \ - if (__builtin_types_compatible_p(__typeof__(arg), unsigned long long)) { \ - return __builtin_ ## fn ## ll(arg); \ - } else if (__builtin_types_compatible_p(__typeof__(arg), unsigned long)) { \ - return __builtin_ ## fn ## l(arg); \ - } else if (__builtin_types_compatible_p(__typeof__(arg), unsigned int)) { \ - return __builtin_ ## fn(arg); \ - } -#else -#define USE_BUILTIN(fn, arg) -#endif - -static inline unsigned int bit_popcount_u64(uint64_t n) -{ - USE_BUILTIN(popcount, n); - n -= ((n >> 1) & U64(0x5555555555555555)); - n = (n & U64(0x3333333333333333)) + ((n >> 2) & U64(0x3333333333333333)); - n = (n + (n >> 4)) & U64(0x0F0F0F0F0F0F0F0F); - return (n * U64(0x0101010101010101)) >> 56; -} - -static inline unsigned int bit_popcount_u32(uint32_t n) -{ - USE_BUILTIN(popcount, n); - n -= ((n >> 1) & U32(0x55555555)); - n = (n & U32(0x33333333)) + ((n >> 2) & U32(0x33333333)); - n = (n + (n >> 4)) & U32(0x0F0F0F0F); - return (n * U32(0x01010101)) >> 24; -} - -static inline unsigned int bit_count_leading_zeros_u64(uint64_t n) -{ - BUG_ON(n == 0); - USE_BUILTIN(clz, n); - n |= (n >> 1); - n |= (n >> 2); - n |= (n >> 4); - n |= (n >> 8); - n |= (n >> 16); - n |= (n >> 32); - return bit_popcount_u64(~n); -} - -static inline unsigned int bit_count_leading_zeros_u32(uint32_t n) -{ - BUG_ON(n == 0); - USE_BUILTIN(clz, n); - n |= (n >> 1); - n |= (n >> 2); - n |= (n >> 4); - n |= (n >> 8); - n |= (n >> 16); - return bit_popcount_u32(~n); -} - -static inline unsigned int bit_count_trailing_zeros_u64(uint64_t n) -{ - BUG_ON(n == 0); - USE_BUILTIN(ctz, n); - return bit_popcount_u64(~n & (n - 1)); -} - -static inline unsigned int bit_count_trailing_zeros_u32(uint32_t n) -{ - BUG_ON(n == 0); - USE_BUILTIN(ctz, n); - return bit_popcount_u32(~n & (n - 1)); -} - -static inline unsigned int bit_find_first_set_u64(uint64_t n) -{ - USE_BUILTIN(ffs, n); - return n ? bit_count_trailing_zeros_u64(n) + 1 : 0; -} - -static inline unsigned int bit_find_first_set_u32(uint32_t n) -{ - USE_BUILTIN(ffs, n); - return n ? bit_count_trailing_zeros_u32(n) + 1 : 0; -} - -#endif diff -Nru dte-1.9.1/src/util/bsearch.h dte-1.10/src/util/bsearch.h --- dte-1.9.1/src/util/bsearch.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/bsearch.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,100 @@ +#ifndef UTIL_BSEARCH_H +#define UTIL_BSEARCH_H + +#include +#include +#include +#include +#include "debug.h" +#include "macros.h" + +typedef int (*CompareFunction)(const void *key, const void *elem); +typedef int (*StringCompareFunction)(const char *key, const char *elem); + +#define BSEARCH(key, a, cmp) bsearch(key, a, ARRAY_COUNT(a), sizeof(a[0]), cmp) +#define BSEARCH_IDX(key, a, cmp) bisearch_idx(key, a, ARRAY_COUNT(a), sizeof(a[0]), cmp) + +#define CHECK_BSEARCH_ARRAY(a, field, cmp) do { \ + static_assert_offsetof(a[0], field, 0); \ + static_assert_incompatible_types(a[0].field, const char*); \ + static_assert_incompatible_types(a[0].field, char*); \ + static_assert_compatible_types(a[0].field[0], char); \ + check_bsearch_array ( \ + a, #a, "." #field, ARRAY_COUNT(a), sizeof(a[0]), sizeof(a[0].field), cmp \ + ); \ +} while (0) + +#define CHECK_BSEARCH_STR_ARRAY(a, cmp) do { \ + static_assert_incompatible_types(a[0], const char*); \ + static_assert_incompatible_types(a[0], char*); \ + static_assert_compatible_types(a[0][0], char); \ + check_bsearch_array(a, #a, "", ARRAY_COUNT(a), sizeof(a[0]), sizeof(a[0]), cmp); \ +} while (0) + +static inline void check_bsearch_array ( + const void *array_base, + const char *array_name, + const char *name_field_name, + size_t array_length, + size_t array_element_size, + size_t name_size, + StringCompareFunction cmp +) { + BUG_ON(!array_base); + BUG_ON(!array_name); + BUG_ON(!name_field_name); + BUG_ON(name_field_name[0] != '\0' && name_field_name[0] != '.'); + BUG_ON(array_length == 0); + BUG_ON(array_element_size == 0); + BUG_ON(name_size == 0); + BUG_ON(!cmp); + +#if DEBUG >= 1 + const char *first_name = array_base; + + for (size_t i = 0; i < array_length; i++) { + const char *curr_name = first_name + (i * array_element_size); + // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) + if (curr_name[0] == '\0') { + BUG("Empty string at %s[%zu]%s", array_name, i, name_field_name); + } + // NOLINTNEXTLINE(clang-analyzer-core.UndefinedBinaryOperatorResult) + if (curr_name[name_size - 1] != '\0') { + BUG("String sentinel missing from %s[%zu]%s", array_name, i, name_field_name); + } + + // Skip sort order check for index 0; there's no prev_name to compare + if (i == 0) { + continue; + } + + const char *prev_name = curr_name - array_element_size; + if (cmp(curr_name, prev_name) <= 0) { + BUG ( + "String at %s[%zu]%s not in sorted order: \"%s\" (prev: \"%s\")", + array_name, i, name_field_name, + curr_name, prev_name + ); + } + } +#endif +} + +// Like bsearch(3), but returning the index of the element instead of +// a pointer to it (or -1 if not found) +static inline ssize_t bisearch_idx ( + const void *key, + const void *base, + size_t nmemb, + size_t size, + CompareFunction compare +) { + const char *found = bsearch(key, base, nmemb, size, compare); + if (!found) { + return -1; + } + const char *char_base = base; + return (size_t)(found - char_base) / size; +} + +#endif diff -Nru dte-1.9.1/src/util/checked-arith.h dte-1.10/src/util/checked-arith.h --- dte-1.9.1/src/util/checked-arith.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/checked-arith.h 2021-04-03 21:08:53.000000000 +0000 @@ -6,12 +6,9 @@ #include #include "macros.h" -#define HAS_GNUC5_OR_BUILTIN(b) (GNUC_AT_LEAST(5, 0) || HAS_BUILTIN(b)) - -NONNULL_ARGS static inline bool size_add_overflows(size_t a, size_t b, size_t *result) { -#if HAS_GNUC5_OR_BUILTIN(__builtin_add_overflow) +#if HAS_BUILTIN(__builtin_add_overflow) || GNUC_AT_LEAST(5, 0) return __builtin_add_overflow(a, b, result); #else if (unlikely(b > SIZE_MAX - a)) { @@ -22,10 +19,9 @@ #endif } -NONNULL_ARGS static inline bool size_multiply_overflows(size_t a, size_t b, size_t *result) { -#if HAS_GNUC5_OR_BUILTIN(__builtin_mul_overflow) +#if HAS_BUILTIN(__builtin_mul_overflow) || GNUC_AT_LEAST(5, 0) return __builtin_mul_overflow(a, b, result); #else if (unlikely(a > 0 && b > SIZE_MAX / a)) { @@ -36,10 +32,9 @@ #endif } -NONNULL_ARGS static inline bool umax_add_overflows(uintmax_t a, uintmax_t b, uintmax_t *result) { -#if HAS_GNUC5_OR_BUILTIN(__builtin_add_overflow) +#if HAS_BUILTIN(__builtin_add_overflow) || GNUC_AT_LEAST(5, 0) return __builtin_add_overflow(a, b, result); #else if (unlikely(b > UINTMAX_MAX - a)) { @@ -50,10 +45,9 @@ #endif } -NONNULL_ARGS static inline bool umax_multiply_overflows(uintmax_t a, uintmax_t b, uintmax_t *result) { -#if HAS_GNUC5_OR_BUILTIN(__builtin_mul_overflow) +#if HAS_BUILTIN(__builtin_mul_overflow) || GNUC_AT_LEAST(5, 0) return __builtin_mul_overflow(a, b, result); #else if (unlikely(a > 0 && b > UINTMAX_MAX / a)) { diff -Nru dte-1.9.1/src/util/debug.c dte-1.10/src/util/debug.c --- dte-1.9.1/src/util/debug.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/debug.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,161 @@ +#include +#include +#include +#include +#include +#include "debug.h" +#include "exitcode.h" +#include "xreadwrite.h" +#include "xsnprintf.h" + +static void no_op(void) {} +static void (*cleanup_handler)(void) = no_op; + +#ifdef ASAN_ENABLED +#include + +const char *__asan_default_options(void) +{ + return "detect_leaks=1:detect_stack_use_after_return=1"; +} + +void __asan_on_error(void) +{ + // This function is called when ASan detects an error. Unlike + // callbacks set with __sanitizer_set_death_callback(), it runs + // before the error report is printed and so allows us to clean + // up the terminal state and avoid clobbering the stderr output. + cleanup_handler(); +} +#endif + +static void print_stack_trace(void) +{ + #ifdef ASAN_ENABLED + fputs("\nStack trace:\n", stderr); + __sanitizer_print_stack_trace(); + #endif +} + +void set_fatal_error_cleanup_handler(void (*handler)(void)) +{ + cleanup_handler = handler; +} + +#if DEBUG >= 1 +noreturn +void bug(const char *file, int line, const char *func, const char *fmt, ...) +{ + cleanup_handler(); + fprintf(stderr, "\n%s:%d: **BUG** in %s() function: '", file, line, func); + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputs("'\n", stderr); + print_stack_trace(); + fflush(stderr); + abort(); +} +#endif + +#if DEBUG >= 2 +#include +#include // isatty + +static const char *dim = ""; +static const char *sgr0 = ""; +static int logfd = -1; + +void log_init(const char *varname) +{ + BUG_ON(logfd != -1); + const char *path = getenv(varname); + if (!path || path[0] == '\0') { + return; + } + + logfd = xopen(path, O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC, 0666); + if (logfd < 0) { + const char *err = strerror(errno); + fprintf(stderr, "Failed to open '%s' ($%s): %s\n", path, varname, err); + exit(EX_IOERR); + } + if (xwrite(logfd, "\n", 1) != 1) { + fprintf(stderr, "Failed to write to log: %s\n", strerror(errno)); + exit(EX_IOERR); + } + + if (isatty(logfd)) { + dim = "\033[2m"; + sgr0 = "\033[0m"; + } + + struct utsname u; + if (uname(&u) >= 0) { + DEBUG_LOG("system: %s/%s %s", u.sysname, u.machine, u.release); + } else { + DEBUG_LOG("uname() failed: %s", strerror(errno)); + } +} + +VPRINTF(3) +static void debug_logv(const char *file, int line, const char *fmt, va_list ap) +{ + if (logfd < 0) { + return; + } + char buf[4096]; + size_t write_max = ARRAY_COUNT(buf) - 1; + const size_t len1 = xsnprintf(buf, write_max, "%s%s:%d:%s ", dim, file, line, sgr0); + write_max -= len1; + const size_t len2 = xvsnprintf(buf + len1, write_max, fmt, ap); + size_t n = len1 + len2; + buf[n++] = '\n'; + xwrite(logfd, buf, n); +} + +void debug_log(const char *file, int line, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + debug_logv(file, line, fmt, ap); + va_end(ap); +} +#endif + +// Error handler for unrecoverable system errors during runtime +// (e.g. ENOMEM) +noreturn void fatal_error(const char *msg, int err) +{ + DEBUG_LOG("%s: %s", msg, strerror(err)); + cleanup_handler(); + errno = err; + perror(msg); + print_stack_trace(); + abort(); +} + +// Error handler for problems encountered during initialization +// (e.g. unsupported $TERM) +noreturn void init_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + +#if DEBUG >= 2 + va_list ap2; + va_copy(ap2, ap); + debug_logv(__FILE__, __LINE__, fmt, ap2); + va_end(ap2); +#endif + + vfprintf(stderr, fmt, ap); + va_end(ap); + putc('\n', stderr); + fflush(stderr); + + exit(1); +} diff -Nru dte-1.9.1/src/util/debug.h dte-1.10/src/util/debug.h --- dte-1.9.1/src/util/debug.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/debug.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,34 @@ +#ifndef UTIL_DEBUG_H +#define UTIL_DEBUG_H + +#include "macros.h" + +#define BUG_ON(a) do { \ + IGNORE_WARNING("-Wtautological-compare") \ + if (unlikely(a)) { \ + BUG("%s", #a); \ + } \ + UNIGNORE_WARNINGS \ +} while (0) + +#if DEBUG >= 1 + #define BUG(...) bug(__FILE__, __LINE__, __func__, __VA_ARGS__) + noreturn void bug(const char *file, int line, const char *func, const char *fmt, ...) COLD PRINTF(4); +#else + #define BUG(...) UNREACHABLE() +#endif + +#if DEBUG >= 2 + #define DEBUG_LOG(...) debug_log(__FILE__, __LINE__, __VA_ARGS__) + void debug_log(const char *file, int line, const char *fmt, ...) PRINTF(3); + void log_init(const char *varname); +#else + static inline PRINTF(1) void DEBUG_LOG(const char* UNUSED_ARG(fmt), ...) {} + static inline void log_init(const char* UNUSED_ARG(varname)) {} +#endif + +noreturn void fatal_error(const char *msg, int err) COLD NONNULL_ARGS; +noreturn void init_error(const char *fmt, ...) COLD NONNULL_ARGS PRINTF(1); +void set_fatal_error_cleanup_handler(void (*handler)(void)); + +#endif diff -Nru dte-1.9.1/src/util/exec.c dte-1.10/src/util/exec.c --- dte-1.9.1/src/util/exec.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/exec.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,47 +1,71 @@ +#include "../../build/feature.h" // Must be first include #include #include #include -#include #include #include #include #include #include "exec.h" +#include "debug.h" +#include "xreadwrite.h" -void close_on_exec(int fd) +static bool close_on_exec(int fd, bool cloexec) { - fcntl(fd, F_SETFD, FD_CLOEXEC); + int flags = fcntl(fd, F_GETFD); + if (flags < 0) { + return false; + } + int new_flags = cloexec ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC); + if (new_flags == flags) { + return true; + } + if (fcntl(fd, F_SETFD, new_flags) == -1) { + return false; + } + return true; } -int pipe_close_on_exec(int fd[2]) +bool pipe_close_on_exec(int fd[2]) { - int ret = pipe(fd); - if (ret == 0) { - close_on_exec(fd[0]); - close_on_exec(fd[1]); +#ifdef HAVE_PIPE2 + return (pipe2(fd, O_CLOEXEC) == 0); +#else + if (pipe(fd) != 0) { + return false; } - return ret; + close_on_exec(fd[0], true); + close_on_exec(fd[1], true); + return true; +#endif } -static int dup_close_on_exec(int oldfd, int newfd) +static int xdup3(int oldfd, int newfd, int flags) { - if (dup2(oldfd, newfd) < 0) { +#ifdef HAVE_DUP3 + int fd; + do { + fd = dup3(oldfd, newfd, flags); + } while (unlikely(fd < 0 && errno == EINTR)); + return fd; +#else + if (unlikely(oldfd == newfd)) { + // Replicate dup3() behaviour: + errno = EINVAL; return -1; } - close_on_exec(newfd); - return newfd; -} - -static void reset_signal_handler(int signum) -{ - struct sigaction act; - memset(&act, 0, sizeof(struct sigaction)); - sigemptyset(&act.sa_mask); - act.sa_handler = SIG_DFL; - sigaction(signum, &act, NULL); + int fd; + do { + fd = dup2(oldfd, newfd); + } while (unlikely(fd < 0 && errno == EINTR)); + if (fd >= 0 && (flags & O_CLOEXEC)) { + close_on_exec(fd, true); + } + return fd; +#endif } -static void handle_child(char **argv, int fd[3], int error_fd) +static noreturn void handle_child(char **argv, const char **env, int fd[3], int error_fd) { int error; int nr_fds = 3; @@ -60,18 +84,17 @@ if (move) { int next_free = max + 1; - if (error_fd < nr_fds) { - error_fd = dup_close_on_exec(error_fd, next_free++); + error_fd = xdup3(error_fd, next_free++, O_CLOEXEC); if (error_fd < 0) { - goto out; + goto error; } } for (int i = 0; i < nr_fds; i++) { if (fd[i] < i) { - fd[i] = dup_close_on_exec(fd[i], next_free++); + fd[i] = xdup3(fd[i], next_free++, O_CLOEXEC); if (fd[i] < 0) { - goto out; + goto error; } } } @@ -81,57 +104,80 @@ for (int i = 0; i < nr_fds; i++) { if (i == fd[i]) { // Clear FD_CLOEXEC flag - fcntl(fd[i], F_SETFD, 0); + close_on_exec(fd[i], false); } else { - if (dup2(fd[i], i) < 0) { - goto out; + if (xdup3(fd[i], i, 0) < 0) { + goto error; + } + } + } + + if (env) { + for (size_t i = 0; env[i]; i += 2) { + const char *name = env[i]; + const char *value = env[i + 1]; + int r = value ? setenv(name, value, true) : unsetenv(name); + if (unlikely(r != 0)) { + goto error; } } } - // Unignore signals (see man page exec(3p) for more information) - reset_signal_handler(SIGINT); - reset_signal_handler(SIGQUIT); + // Reset ignored signals to SIG_DFL (see exec(3p)) + struct sigaction act; + memset(&act, 0, sizeof(struct sigaction)); + sigemptyset(&act.sa_mask); + act.sa_handler = SIG_DFL; + sigaction(SIGINT, &act, NULL); + sigaction(SIGQUIT, &act, NULL); + sigaction(SIGPIPE, &act, NULL); + sigaction(SIGUSR1, &act, NULL); + sigaction(SIGUSR2, &act, NULL); execvp(argv[0], argv); -out: + +error: error = errno; error = write(error_fd, &error, sizeof(error)); exit(42); } -pid_t fork_exec(char **argv, int fd[3]) +pid_t fork_exec(char **argv, const char **env, int fd[3]) { - int error = 0; int ep[2]; - - if (pipe_close_on_exec(ep)) { + if (!pipe_close_on_exec(ep)) { return -1; } const pid_t pid = fork(); - if (pid < 0) { - error = errno; - close(ep[0]); - close(ep[1]); - errno = error; + if (unlikely(pid < 0)) { + const int saved_errno = errno; + xclose(ep[0]); + xclose(ep[1]); + errno = saved_errno; return pid; } - if (!pid) { - handle_child(argv, fd, ep[1]); + + if (pid == 0) { + // Child + handle_child(argv, env, fd, ep[1]); + BUG("handle_child() should never return"); } - close(ep[1]); + // Parent + xclose(ep[1]); + int error = 0; const ssize_t rc = read(ep[0], &error, sizeof(error)); + const int saved_errno = errno; + xclose(ep[0]); + if (rc > 0 && rc != sizeof(error)) { error = EPIPE; } if (rc < 0) { - error = errno; + error = saved_errno; } - close(ep[0]); - - if (!rc) { + if (rc == 0) { // Child exec was successful return pid; } @@ -153,19 +199,24 @@ } return -errno; } + if (WIFEXITED(status)) { return WEXITSTATUS(status) & 0xFF; } + if (WIFSIGNALED(status)) { return WTERMSIG(status) << 8; } + if (WIFSTOPPED(status)) { return WSTOPSIG(status) << 8; } + #if defined(WIFCONTINUED) if (WIFCONTINUED(status)) { return SIGCONT << 8; } #endif + return -EINVAL; } diff -Nru dte-1.9.1/src/util/exec.h dte-1.10/src/util/exec.h --- dte-1.9.1/src/util/exec.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/exec.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,12 @@ #ifndef UTIL_EXEC_H #define UTIL_EXEC_H +#include #include #include "macros.h" -void close_on_exec(int fd); -int pipe_close_on_exec(int fd[2]); -pid_t fork_exec(char **argv, int fd[3]) NONNULL_ARGS; +bool pipe_close_on_exec(int fd[2]); +pid_t fork_exec(char **argv, const char **env, int fd[3]) NONNULL_ARG(1); int wait_child(pid_t pid); #endif diff -Nru dte-1.9.1/src/util/exitcode.h dte-1.10/src/util/exitcode.h --- dte-1.9.1/src/util/exitcode.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/exitcode.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,15 @@ +#ifndef UTIL_EXITCODE_H +#define UTIL_EXITCODE_H + +// Semantic exit codes, as defined by BSD sysexits(3) +enum { + EX_OK = 0, // Exited normally + EX_USAGE = 64, // Command line usage error + EX_DATAERR = 65, // Input data error + EX_OSERR = 71, // Operating system error + EX_IOERR = 74, // Input/output error +}; + +typedef int ExitCode; + +#endif diff -Nru dte-1.9.1/src/util/hash.h dte-1.10/src/util/hash.h --- dte-1.9.1/src/util/hash.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/hash.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,57 +1,62 @@ #ifndef UTIL_HASH_H #define UTIL_HASH_H +#include #include #include #include "ascii.h" +#include "macros.h" #define FNV_32_INIT UINT32_C(2166136261) #define FNV_64_INIT UINT64_C(14695981039346656037) #define FNV_32_PRIME UINT32_C(16777619) #define FNV_64_PRIME UINT64_C(1099511628211) -static inline uint32_t fnv_1a_32_hash(const char *str, size_t len) +static inline size_t fnv_1a_init(void) { - uint32_t hash = FNV_32_INIT; - while (len--) { - uint32_t c = *str++; - hash ^= c; - hash *= FNV_32_PRIME; - } - return hash; + static_assert(8 * CHAR_BIT == 64); + return (sizeof(size_t) >= 8) ? FNV_64_INIT : FNV_32_INIT; } -static inline uint32_t fnv_1a_32_hash_icase(const char *str, size_t len) +static inline size_t fnv_1a_prime(void) { - uint32_t hash = FNV_32_INIT; - while (len--) { - uint32_t c = ascii_tolower(*str++); - hash ^= c; - hash *= FNV_32_PRIME; + return (sizeof(size_t) >= 8) ? FNV_64_PRIME : FNV_32_PRIME; +} + +static inline size_t fnv_1a_hash(const unsigned char *str, size_t n) +{ + const size_t prime = fnv_1a_prime(); + size_t hash = fnv_1a_init(); + while (n--) { + hash ^= *str++; + hash *= prime; } return hash; } -static inline uint64_t fnv_1a_64_hash(const char *str, size_t len) +static inline size_t fnv_1a_hash_icase(const unsigned char *str, size_t n) { - uint64_t hash = FNV_64_INIT; - while (len--) { - uint64_t c = *str++; - hash ^= c; - hash *= FNV_64_PRIME; + const size_t prime = fnv_1a_prime(); + size_t hash = fnv_1a_init(); + while (n--) { + hash ^= ascii_tolower(*str++); + hash *= prime; } return hash; } -static inline uint64_t fnv_1a_64_hash_icase(const char *str, size_t len) +// NOTE: returns 0 if x is greater than the largest size_t power of 2 +static inline size_t round_size_to_next_power_of_2(size_t x) { - uint64_t hash = FNV_64_INIT; - while (len--) { - uint64_t c = ascii_tolower(*str++); - hash ^= c; - hash *= FNV_64_PRIME; + if (unlikely(x == 0)) { + return 1; } - return hash; + x--; + UNROLL_LOOP(8) + for (size_t i = 1, n = sizeof(size_t) * CHAR_BIT; i < n; i <<= 1) { + x |= x >> i; + } + return x + 1; } #endif diff -Nru dte-1.9.1/src/util/hashmap.c dte-1.10/src/util/hashmap.c --- dte-1.9.1/src/util/hashmap.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/hashmap.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include "hashmap.h" +#include "debug.h" +#include "hash.h" +#include "str-util.h" + +char tombstone[16] = "TOMBSTONE"; + +enum { + MIN_SIZE = 8 +}; + +WARN_UNUSED_RESULT +static int hashmap_resize(HashMap *map, size_t size) +{ + BUG_ON(size < MIN_SIZE); + BUG_ON(size <= map->count); + BUG_ON(!IS_POWER_OF_2(size)); + + HashMapEntry *newtab = calloc(size, sizeof(*newtab)); + if (unlikely(!newtab)) { + return ENOMEM; + } + + HashMapEntry *oldtab = map->entries; + size_t oldlen = map->mask + 1; + map->entries = newtab; + map->mask = size - 1; + map->tombstones = 0; + + if (!oldtab) { + return 0; + } + + // Copy the entries to the new table + for (const HashMapEntry *e = oldtab, *end = e + oldlen; e < end; e++) { + if (!e->key || e->key == tombstone) { + continue; + } + HashMapEntry *newe; + for (size_t i = e->hash, j = 1; ; i += j++) { + newe = newtab + (i & map->mask); + if (!newe->key) { + break; + } + } + *newe = *e; + } + + free(oldtab); + return 0; +} + +WARN_UNUSED_RESULT +static int hashmap_do_init(HashMap *map, size_t size) +{ + // Accommodate the 75% load factor in the table size, to allow + // filling to the requested size without needing to resize() + size += size / 3; + + if (unlikely(size < MIN_SIZE)) { + size = MIN_SIZE; + } + + // Round up the size to the next power of 2, to allow using simple + // bitwise ops (instead of modulo) to wrap the hash value and also + // to allow quadratic probing with triangular numbers + size = round_size_to_next_power_of_2(size); + if (unlikely(size == 0)) { + return EOVERFLOW; + } + + *map = (HashMap)HASHMAP_INIT; + return hashmap_resize(map, size); +} + +void hashmap_init(HashMap *map, size_t size) +{ + int err = hashmap_do_init(map, size); + if (unlikely(err)) { + fatal_error(__func__, err); + } +} + +HashMapEntry *hashmap_find(const HashMap *map, const char *key) +{ + if (unlikely(!map->entries)) { + return NULL; + } + + size_t hash = fnv_1a_hash(key, strlen(key)); + HashMapEntry *e; + for (size_t i = hash, j = 1; ; i += j++) { + e = map->entries + (i & map->mask); + if (!e->key) { + break; + } + if (e->key == tombstone) { + continue; + } + if (e->hash == hash && streq(e->key, key)) { + break; + } + } + return e->key ? e : NULL; +} + +void *hashmap_remove(HashMap *map, const char *key) +{ + HashMapEntry *e = hashmap_find(map, key); + if (!e) { + return NULL; + } + + free(e->key); + e->key = tombstone; + map->count--; + map->tombstones++; + return e->value; +} + +WARN_UNUSED_RESULT +static int hashmap_do_insert(HashMap *map, char *key, void *value, void **old_value) +{ + int err = 0; + if (unlikely(!map->entries)) { + err = hashmap_do_init(map, 0); + if (unlikely(err)) { + return err; + } + } + + size_t hash = fnv_1a_hash(key, strlen(key)); + bool replacing_tombstone_or_existing_value = false; + HashMapEntry *e; + for (size_t i = hash, j = 1; ; i += j++) { + e = map->entries + (i & map->mask); + if (!e->key) { + break; + } + if (e->key == tombstone) { + replacing_tombstone_or_existing_value = true; + BUG_ON(map->tombstones == 0); + map->tombstones--; + break; + } + if (unlikely(e->hash == hash && streq(e->key, key))) { + replacing_tombstone_or_existing_value = true; + // When a caller passes NULL as the "old_value" return param, + // it implies there can be no existing entry with the same key + // as the one to be inserted. + BUG_ON(!old_value); + BUG_ON(!e->value); + *old_value = e->value; + free(key); + key = e->key; + map->count--; + break; + } + } + + const size_t max_load = map->mask - (map->mask / 4); + e->key = key; + e->value = value; + e->hash = hash; + map->count++; + + if (unlikely(map->count + map->tombstones > max_load)) { + BUG_ON(replacing_tombstone_or_existing_value); + size_t new_size = map->mask + 1; + if (map->count > map->tombstones || new_size <= 256) { + // Only increase the size of the table when the number of + // real entries is higher than the number of tombstones. + // If the number of real entries is lower, the table is + // most likely being filled with tombstones from repeated + // insert/remove churn; so we just rehash at the same size + // to clean out the tombstones. + new_size <<= 1; + if (unlikely(new_size == 0)) { + err = EOVERFLOW; + goto error; + } + } + err = hashmap_resize(map, new_size); + if (unlikely(err)) { + goto error; + } + } + + return 0; + +error: + map->count--; + e->key = NULL; + return err; +} + +void hashmap_insert(HashMap *map, char *key, void *value) +{ + int err = hashmap_do_insert(map, key, value, NULL); + if (unlikely(err)) { + fatal_error(__func__, err); + } +} + +void *hashmap_insert_or_replace(HashMap *map, char *key, void *value) +{ + void *replaced_value = NULL; + int err = hashmap_do_insert(map, key, value, &replaced_value); + if (unlikely(err)) { + fatal_error(__func__, err); + } + return replaced_value; +} + +// Remove all entries without freeing the table +void hashmap_clear(HashMap *map, FreeFunction free_value) +{ + if (unlikely(!map->entries)) { + return; + } + + size_t count = 0; + for (HashMapIter it = hashmap_iter(map); hashmap_next(&it); count++) { + free(it.entry->key); + if (free_value) { + free_value(it.entry->value); + } + } + + BUG_ON(count != map->count); + size_t len = map->mask + 1; + memset(map->entries, 0, len * sizeof(*map->entries)); + map->count = 0; +} + +void hashmap_free(HashMap *map, FreeFunction free_value) +{ + hashmap_clear(map, free_value); + free(map->entries); + *map = (HashMap)HASHMAP_INIT; +} diff -Nru dte-1.9.1/src/util/hashmap.h dte-1.10/src/util/hashmap.h --- dte-1.9.1/src/util/hashmap.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/hashmap.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,74 @@ +#ifndef UTIL_HASHMAP_H +#define UTIL_HASHMAP_H + +#include +#include +#include "macros.h" + +typedef void (*FreeFunction)(void *ptr); + +typedef struct { + char *key; + void *value; + size_t hash; +} HashMapEntry; + +typedef struct { + HashMapEntry *entries; + size_t mask; // Length of entries (which is always a power of 2) minus 1 + size_t count; // Number of active entries + size_t tombstones; // Number of tombstones +} HashMap; + +typedef struct { + const HashMap *map; + HashMapEntry *entry; + size_t idx; +} HashMapIter; + +#define HASHMAP_INIT { \ + .entries = NULL, \ + .mask = 0, \ + .count = 0, \ + .tombstones = 0 \ +} + +static inline HashMapIter hashmap_iter(const HashMap *map) +{ + return (HashMapIter){.map = map}; +} + +static inline bool hashmap_next(HashMapIter *iter) +{ + const HashMap *map = iter->map; + if (unlikely(!map->entries)) { + return false; + } + + extern char tombstone[16]; + for (size_t i = iter->idx, n = map->mask + 1; i < n; i++) { + HashMapEntry *e = map->entries + i; + if (e->key && e->key != tombstone) { + iter->entry = e; + iter->idx = i + 1; + return true; + } + } + return false; +} + +void hashmap_init(HashMap *map, size_t capacity) NONNULL_ARGS; +void hashmap_insert(HashMap *map, char *key, void *value) NONNULL_ARGS; +void *hashmap_insert_or_replace(HashMap *map, char *key, void *value) NONNULL_ARGS; +void *hashmap_remove(HashMap *map, const char *key) NONNULL_ARGS; +void hashmap_clear(HashMap *map, FreeFunction free_value) NONNULL_ARG(1); +void hashmap_free(HashMap *map, FreeFunction free_value) NONNULL_ARG(1); +HashMapEntry *hashmap_find(const HashMap *map, const char *key) NONNULL_ARGS WARN_UNUSED_RESULT; + +static inline void *hashmap_get(const HashMap *map, const char *key) +{ + HashMapEntry *e = hashmap_find(map, key); + return e ? e->value : NULL; +} + +#endif diff -Nru dte-1.9.1/src/util/hashset.c dte-1.10/src/util/hashset.c --- dte-1.9.1/src/util/hashset.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/hashset.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,12 +1,17 @@ +#include #include #include #include "hashset.h" +#include "ascii.h" +#include "debug.h" #include "hash.h" #include "str-util.h" #include "xmalloc.h" static void alloc_table(HashSet *set, size_t size) { + BUG_ON(size < 8); + BUG_ON(!IS_POWER_OF_2(size)); set->table_size = size; set->table = xnew0(HashSetEntry*, size); set->grow_at = size - (size / 4); // 75% load factor (size * 0.75) @@ -18,22 +23,25 @@ size = 8; } - // Accomodate the 75% load factor in the table size, to allow filling + // Accommodate the 75% load factor in the table size, to allow filling // the set to the requested size without needing to rehash() size += size / 3; // Round up the allocation to the next power of 2, to allow using // simple bitwise ops (instead of modulo) in get_slot() size = round_size_to_next_power_of_2(size); + if (unlikely(size == 0)) { + fatal_error(__func__, EOVERFLOW); + } alloc_table(set, size); set->nr_entries = 0; if (icase) { - set->hash = fnv_1a_32_hash_icase; + set->hash = fnv_1a_hash_icase; set->equal = mem_equal_icase; } else { - set->hash = fnv_1a_32_hash; + set->hash = fnv_1a_hash; set->equal = mem_equal; } } @@ -53,8 +61,8 @@ static size_t get_slot(const HashSet *set, const char *str, size_t str_len) { - const uint32_t hash = set->hash(str, str_len); - return (size_t)hash & (set->table_size - 1); + const size_t hash = set->hash(str, str_len); + return hash & (set->table_size - 1); } HashSetEntry *hashset_get(const HashSet *set, const char *str, size_t str_len) @@ -104,25 +112,20 @@ set->table[slot] = h; if (++set->nr_entries > set->grow_at) { - rehash(set, set->table_size << 1); + size_t new_size = set->table_size << 1; + if (unlikely(new_size == 0)) { + fatal_error(__func__, EOVERFLOW); + } + rehash(set, new_size); } return h; } -void hashset_add_many(HashSet *set, char **strings, size_t nstrings) -{ - for (size_t i = 0; i < nstrings; i++) { - const char *str = strings[i]; - const size_t str_len = strlen(str); - hashset_add(set, str, str_len); - } -} - const void *mem_intern(const void *data, size_t len) { static HashSet pool; - if (!pool.table_size) { + if (unlikely(pool.table_size == 0)) { hashset_init(&pool, 32, false); } diff -Nru dte-1.9.1/src/util/hashset.h dte-1.10/src/util/hashset.h --- dte-1.9.1/src/util/hashset.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/hashset.h 2021-04-03 21:08:53.000000000 +0000 @@ -3,9 +3,9 @@ #include #include -#include #include #include "macros.h" +#include "string-view.h" // This is a container type for holding a set of related strings. // It uses hashing for primary lookups and separate chaining for @@ -15,7 +15,7 @@ size_t table_size; size_t nr_entries; size_t grow_at; - uint32_t (*hash)(const char *str, size_t len); + size_t (*hash)(const unsigned char *str, size_t len); bool (*equal)(const void *s1, const void *s2, size_t n); } HashSet; @@ -29,7 +29,6 @@ void hashset_free(HashSet *set); HashSetEntry *hashset_get(const HashSet *set, const char *str, size_t str_len); HashSetEntry *hashset_add(HashSet *set, const char *str, size_t str_len); -void hashset_add_many(HashSet *set, char **strings, size_t nstrings); const void *mem_intern(const void *data, size_t len) NONNULL_ARGS_AND_RETURN; @@ -38,4 +37,10 @@ return mem_intern(str, strlen(str)); } +static inline StringView strview_intern(const char *str) +{ + size_t len = strlen(str); + return string_view(mem_intern(str, len), len); +} + #endif diff -Nru dte-1.9.1/src/util/list.h dte-1.10/src/util/list.h --- dte-1.9.1/src/util/list.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/list.h 2021-04-03 21:08:53.000000000 +0000 @@ -41,7 +41,7 @@ entry->prev = (void*)0x00200200; } -static inline bool list_empty(const ListHead *const head) +static inline bool list_empty(const ListHead *head) { return head->next == head; } diff -Nru dte-1.9.1/src/util/macros.h dte-1.10/src/util/macros.h --- dte-1.9.1/src/util/macros.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/macros.h 2021-04-03 21:08:53.000000000 +0000 @@ -6,10 +6,15 @@ #endif #define STRLEN(x) (sizeof("" x "") - 1) +#define STRN(x) x,STRLEN(x) #define PASTE(a, b) a##b #define XPASTE(a, b) PASTE(a, b) #define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MAX3(a, b, c) MAX(a, MAX(b, c)) +#define MAX4(a, b, c, d) MAX(a, MAX3(b, c, d)) #define IS_POWER_OF_2(x) (((x) > 0) && (((x) & ((x) - 1)) == 0)) +#define DECIMAL_STR_MAX(T) ((sizeof(T) * 3) + 2) #define DO_PRAGMA(x) _Pragma(#x) // Calculate the number of elements in an array. @@ -21,20 +26,18 @@ / ((size_t)(!(sizeof(x) % sizeof((x)[0])))) \ ) -#ifdef __GNUC__ - #define GNUC_AT_LEAST(major, minor) ( \ - (__GNUC__ > major) \ - || ((__GNUC__ == major) && (__GNUC_MINOR__ >= minor)) ) +#define VERCMP(x, y, cx, cy) ((cx > x) || ((cx == x) && (cy >= y))) + +#if defined(__GNUC__) && defined(__GNUC_MINOR__) + #define GNUC_AT_LEAST(x, y) VERCMP(x, y, __GNUC__, __GNUC_MINOR__) #else - #define GNUC_AT_LEAST(major, minor) 0 + #define GNUC_AT_LEAST(x, y) 0 #endif -// __has_extension is a Clang macro used to determine if a feature is -// available even if not standardized in the current "-std" mode. -#ifdef __has_extension - #define HAS_EXTENSION(x) __has_extension(x) +#if defined(__clang_major__) && defined(__clang_minor__) + #define CLANG_AT_LEAST(x, y) VERCMP(x, y, __clang_major__, __clang_minor__) #else - #define HAS_EXTENSION(x) 0 + #define CLANG_AT_LEAST(x, y) 0 #endif #ifdef __has_attribute @@ -61,55 +64,68 @@ #define HAS_FEATURE(x) 0 #endif +// __has_extension() is a Clang macro used to determine if a feature is +// available even if not standardized in the current "-std" mode. +#ifdef __has_extension + #define HAS_EXTENSION(x) __has_extension(x) +#else + // Clang versions prior to 3.0 only supported __has_feature() + #define HAS_EXTENSION(x) HAS_FEATURE(x) +#endif + #if defined(__SANITIZE_ADDRESS__) || HAS_FEATURE(address_sanitizer) #define ASAN_ENABLED 1 #endif -#if defined(__clang__) && HAS_FEATURE(address_sanitizer) - #define CLANG_ASAN_ENABLED 1 +#if defined(__SANITIZE_MEMORY__) || HAS_FEATURE(memory_sanitizer) + #define MSAN_ENABLED 1 #endif -#if GNUC_AT_LEAST(3, 0) || defined(__TINYC__) +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(unused) || defined(__TINYC__) #define UNUSED __attribute__((__unused__)) #else #define UNUSED #endif -#if GNUC_AT_LEAST(3, 0) +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(aligned) #define ALIGNED(x) __attribute__((__aligned__(x))) - #define MALLOC __attribute__((__malloc__)) - #define PRINTF(x) __attribute__((__format__(__printf__, (x), (x + 1)))) - #define VPRINTF(x) __attribute__((__format__(__printf__, (x), 0))) - #define PURE __attribute__((__pure__)) - #define CONST_FN __attribute__((__const__)) - #define CONSTRUCTOR __attribute__((__constructor__)) - #define DESTRUCTOR __attribute__((__destructor__)) #else #define ALIGNED(x) +#endif + +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(malloc) + #define MALLOC __attribute__((__malloc__)) +#else #define MALLOC - #define PRINTF(x) - #define VPRINTF(x) +#endif + +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(pure) + #define PURE __attribute__((__pure__)) +#else #define PURE - #define CONST_FN - #define CONSTRUCTOR UNUSED - #define DESTRUCTOR UNUSED #endif -#define UNUSED_ARG(x) unused__ ## x UNUSED +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(const) + #define CONST_FN __attribute__((__const__)) +#else + #define CONST_FN +#endif -#ifdef __COUNTER__ // Supported by GCC 4.3+ and Clang - #define COUNTER_ __COUNTER__ +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(constructor) + #define CONSTRUCTOR __attribute__((__constructor__)) #else - #define COUNTER_ __LINE__ + #define CONSTRUCTOR UNUSED #endif -#if defined(DEBUG) && (DEBUG > 0) - #define UNITTEST static void CONSTRUCTOR XPASTE(unittest_, COUNTER_)(void) +#if GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(format) + #define PRINTF(x) __attribute__((__format__(__printf__, (x), (x + 1)))) + #define VPRINTF(x) __attribute__((__format__(__printf__, (x), 0))) #else - #define UNITTEST static void UNUSED XPASTE(unittest_, COUNTER_)(void) + #define PRINTF(x) + #define VPRINTF(x) #endif -#if GNUC_AT_LEAST(3, 0) && defined(__OPTIMIZE__) +#if (GNUC_AT_LEAST(3, 0) || HAS_BUILTIN(__builtin_expect)) && defined(__OPTIMIZE__) #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #else @@ -117,18 +133,40 @@ #define unlikely(x) (x) #endif -#if GNUC_AT_LEAST(3, 0) && defined(__ELF__) +#if GNUC_AT_LEAST(3, 0) || HAS_BUILTIN(__builtin_constant_p) || defined(__TINYC__) + #define IS_CT_CONSTANT(x) __builtin_constant_p(x) +#else + #define IS_CT_CONSTANT(x) 0 +#endif + +#if (GNUC_AT_LEAST(3, 0) || HAS_ATTRIBUTE(section)) && defined(__ELF__) #define SECTION(x) __attribute__((__section__(x))) #else #define SECTION(x) #endif +#if GNUC_AT_LEAST(3, 0) || HAS_EXTENSION(typeof) || defined(__TINYC__) + #define HAS_TYPEOF 1 +#endif + #if GNUC_AT_LEAST(3, 1) || HAS_BUILTIN(__builtin_prefetch) #define PREFETCH(addr, ...) __builtin_prefetch(addr, __VA_ARGS__) #else #define PREFETCH(addr, ...) #endif +#if GNUC_AT_LEAST(3, 1) || HAS_ATTRIBUTE(noinline) + #define NOINLINE __attribute__((__noinline__)) +#else + #define NOINLINE +#endif + +#if GNUC_AT_LEAST(3, 1) || HAS_ATTRIBUTE(always_inline) + #define ALWAYS_INLINE __attribute__((__always_inline__)) +#else + #define ALWAYS_INLINE +#endif + #if GNUC_AT_LEAST(3, 3) || HAS_ATTRIBUTE(nonnull) #define NONNULL_ARGS __attribute__((__nonnull__)) #define NONNULL_ARG(...) __attribute__((__nonnull__(__VA_ARGS__))) @@ -143,6 +181,18 @@ #define WARN_UNUSED_RESULT #endif +#if GNUC_AT_LEAST(4, 0) || HAS_ATTRIBUTE(sentinel) + #define SENTINEL __attribute__((__sentinel__)) +#else + #define SENTINEL +#endif + +#if GNUC_AT_LEAST(4, 1) || HAS_ATTRIBUTE(flatten) + #define FLATTEN __attribute__((__flatten__)) +#else + #define FLATTEN +#endif + #if GNUC_AT_LEAST(4, 3) || HAS_ATTRIBUTE(alloc_size) #define ALLOC_SIZE(...) __attribute__((__alloc_size__(__VA_ARGS__))) #else @@ -173,12 +223,6 @@ #define RETURNS_NONNULL #endif -#if GNUC_AT_LEAST(6, 0) || HAS_ATTRIBUTE(target_clones) - #define TARGET_CLONES(x) __attribute__((__target_clones__(x))) -#else - #define TARGET_CLONES(x) -#endif - #if GNUC_AT_LEAST(8, 0) || HAS_ATTRIBUTE(nonstring) #define NONSTRING __attribute__((__nonstring__)) #else @@ -191,47 +235,92 @@ #define DIAGNOSE_IF(x) #endif -#if defined(__x86_64__) && !defined(__SSE4_2__) - #define FMV_SSE42 TARGET_CLONES("sse4.2,default") -#else - #define FMV_SSE42 -#endif - -#define XSTRDUP MALLOC RETURNS_NONNULL NONNULL_ARGS -#define XMALLOC MALLOC RETURNS_NONNULL +#define UNUSED_ARG(x) unused__ ## x UNUSED +#define XMALLOC MALLOC RETURNS_NONNULL WARN_UNUSED_RESULT +#define XSTRDUP XMALLOC NONNULL_ARGS #define NONNULL_ARGS_AND_RETURN RETURNS_NONNULL NONNULL_ARGS #if __STDC_VERSION__ >= 201112L #define alignof(t) _Alignof(t) - #define NORETURN _Noreturn + #define noreturn _Noreturn #elif GNUC_AT_LEAST(3, 0) #define alignof(t) __alignof__(t) - #define NORETURN __attribute__((__noreturn__)) + #define noreturn __attribute__((__noreturn__)) #else #define alignof(t) MIN(sizeof(t), offsetof(struct{char c; t x;}, x)) - #define NORETURN + #define noreturn #endif -#if (__STDC_VERSION__ >= 201112L) || HAS_EXTENSION(c_static_assert) +#if __STDC_VERSION__ >= 201112L #define static_assert(x) _Static_assert((x), #x) + #define HAS_STATIC_ASSERT 1 +#elif GNUC_AT_LEAST(4, 6) || HAS_EXTENSION(c_static_assert) + #define static_assert(x) __extension__ _Static_assert((x), #x) + #define HAS_STATIC_ASSERT 1 #else #define static_assert(x) #endif +#if GNUC_AT_LEAST(3, 1) || HAS_BUILTIN(__builtin_types_compatible_p) + #define HAS_BUILTIN_TYPES_COMPATIBLE_P 1 +#endif + +#if defined(HAS_STATIC_ASSERT) && defined(HAS_BUILTIN_TYPES_COMPATIBLE_P) + #define static_assert_compatible_types(a, b) static_assert ( \ + __builtin_types_compatible_p(__typeof__(a), __typeof__(b)) \ + ) + #define static_assert_incompatible_types(a, b) static_assert ( \ + !__builtin_types_compatible_p(__typeof__(a), __typeof__(b)) \ + ) +#else + #define static_assert_compatible_types(a, b) + #define static_assert_incompatible_types(a, b) +#endif + +#if defined(HAS_STATIC_ASSERT) && defined(HAS_TYPEOF) + #define static_assert_offsetof(obj, field, offset) \ + static_assert(offsetof(__typeof__(obj), field) == offset) +#else + #define static_assert_offsetof(obj, field, offset) +#endif + #if GNUC_AT_LEAST(4, 2) || defined(__clang__) #define DISABLE_WARNING(wflag) DO_PRAGMA(GCC diagnostic ignored wflag) #else #define DISABLE_WARNING(wflag) #endif +#if CLANG_AT_LEAST(3, 6) + #define UNROLL_LOOP(n) DO_PRAGMA(clang loop unroll_count(n)) +#elif GNUC_AT_LEAST(8, 0) + #define UNROLL_LOOP(n) DO_PRAGMA(GCC unroll (n)) +#else + #define UNROLL_LOOP(n) +#endif + +#ifdef __COUNTER__ // Supported by GCC 4.3+ and Clang + #define COUNTER_ __COUNTER__ +#else + #define COUNTER_ __LINE__ +#endif + +#if defined(DEBUG) && (DEBUG > 0) + #define UNITTEST static void CONSTRUCTOR XPASTE(unittest_, COUNTER_)(void) +#else + #define UNITTEST static void UNUSED XPASTE(unittest_, COUNTER_)(void) +#endif + #ifdef __clang__ #define IGNORE_WARNING(wflag) \ DO_PRAGMA(clang diagnostic push) \ + DO_PRAGMA(clang diagnostic ignored "-Wunknown-pragmas") \ + DO_PRAGMA(clang diagnostic ignored "-Wunknown-warning-option") \ DO_PRAGMA(clang diagnostic ignored wflag) #define UNIGNORE_WARNINGS DO_PRAGMA(clang diagnostic pop) #elif GNUC_AT_LEAST(4, 6) #define IGNORE_WARNING(wflag) \ DO_PRAGMA(GCC diagnostic push) \ + DO_PRAGMA(GCC diagnostic ignored "-Wpragmas") \ DO_PRAGMA(GCC diagnostic ignored wflag) #define UNIGNORE_WARNINGS DO_PRAGMA(GCC diagnostic pop) #else diff -Nru dte-1.9.1/src/util/numtostr.c dte-1.10/src/util/numtostr.c --- dte-1.9.1/src/util/numtostr.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/numtostr.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,62 @@ +#include "numtostr.h" + +static size_t umax_count_base10_digits(uintmax_t x) +{ + size_t digits = 0; + do { + x /= 10; + digits++; + } while (x); + return digits; +} + +// Writes the decimal string representation of `x` into `buf`, +// which must have at least `DECIMAL_STR_MAX(x)` bytes available. +// Returns the number of bytes (digits) written. +size_t buf_umax_to_str(uintmax_t x, char *buf) +{ + const size_t ndigits = umax_count_base10_digits(x); + size_t i = ndigits; + buf[i--] = '\0'; + do { + unsigned char digit = x % 10; + buf[i--] = '0' + digit; + x -= digit; + } while (x /= 10); + return ndigits; +} + +// Writes the decimal string representation of `x` into a static +// buffer and returns a pointer to it. Unlike buf_umax_to_str(), +// this can be done without counting the number of digits first, +// by beginning at the end of the buffer and returning a pointer +// to the "first" (last written) byte offset. +const char *umax_to_str(uintmax_t x) +{ + static char buf[DECIMAL_STR_MAX(x)]; + size_t i = sizeof(buf) - 2; + do { + buf[i--] = (x % 10) + '0'; + } while (x /= 10); + return &buf[i + 1]; +} + +const char *uint_to_str(unsigned int x) +{ + return umax_to_str(x); +} + +const char *ulong_to_str(unsigned long x) +{ + return umax_to_str(x); +} + +size_t buf_ulong_to_str(unsigned long x, char *buf) +{ + return buf_umax_to_str(x, buf); +} + +size_t buf_uint_to_str(unsigned int x, char *buf) +{ + return buf_umax_to_str(x, buf); +} diff -Nru dte-1.9.1/src/util/numtostr.h dte-1.10/src/util/numtostr.h --- dte-1.9.1/src/util/numtostr.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/numtostr.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,16 @@ +#ifndef UTIL_NUMTOSTR_H +#define UTIL_NUMTOSTR_H + +#include +#include +#include "macros.h" + +size_t buf_umax_to_str(uintmax_t x, char *buf) NONNULL_ARGS; +size_t buf_uint_to_str(unsigned int x, char *buf) NONNULL_ARGS; +size_t buf_ulong_to_str(unsigned long x, char *buf) NONNULL_ARGS; + +const char *umax_to_str(uintmax_t x) RETURNS_NONNULL; +const char *uint_to_str(unsigned int x) RETURNS_NONNULL; +const char *ulong_to_str(unsigned long x) RETURNS_NONNULL; + +#endif diff -Nru dte-1.9.1/src/util/path.c dte-1.10/src/util/path.c --- dte-1.9.1/src/util/path.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/path.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,184 +1,35 @@ +#define _XOPEN_SOURCE 700 #include -#include -#include +#include #include "path.h" -static bool make_absolute(char *dst, size_t size, const char *src) +char *path_absolute(const char *path) { - size_t pos = 0; - if (!path_is_absolute(src)) { - if (!getcwd(dst, size)) { - return false; - } - pos = strlen(dst); - dst[pos++] = '/'; - } - - const size_t len = strlen(src); - if (pos + len + 1 > size) { - errno = ENAMETOOLONG; - return false; + if (unlikely(path[0] == '\0')) { + errno = EINVAL; + return NULL; } - memcpy(dst + pos, src, len + 1); - return true; -} - -static size_t remove_double_slashes(char *str) -{ - size_t d = 0; - for (size_t s = 0; str[s]; s++) { - if (str[s] != '/' || str[s + 1] != '/') { - str[d++] = str[s]; - } + char *abs = realpath(path, NULL); + if (abs || errno != ENOENT) { + return abs; } - str[d] = '\0'; - return d; -} - -/* - * Canonicalizes path name - * - * - Replaces double-slashes with one slash - * - Removes any "." or ".." path components - * - Makes path absolute - * - Expands symbolic links - * - Checks that all but the last expanded path component are directories - * - Last path component is allowed to not exist - */ -char *path_absolute(const char *filename) -{ - unsigned int depth = 0; - char buf[8192]; - if (!make_absolute(buf, sizeof(buf), filename)) { + const char *base = path_basename(path); + char *dir_relative = path_dirname(path); + char *dir = realpath(dir_relative, NULL); + free(dir_relative); + if (!dir) { + errno = ENOENT; return NULL; } - remove_double_slashes(buf); - - // For each component: - // * Remove "." - // * Remove ".." and previous component - // * If symlink then replace with link destination and start over - - char *sp = buf + 1; - while (*sp) { - char *ep = strchr(sp, '/'); - bool last = !ep; - - if (ep) { - *ep = 0; - } - if (sp[0] == '.' && sp[1] == '\0') { - if (last) { - *sp = 0; - break; - } - memmove(sp, ep + 1, strlen(ep + 1) + 1); - continue; - } - if (sp[0] == '.' && sp[1] == '.' && sp[2] == '\0') { - if (sp != buf + 1) { - // Not first component, remove previous component - sp--; - while (sp[-1] != '/') { - sp--; - } - } - - if (last) { - *sp = 0; - break; - } - memmove(sp, ep + 1, strlen(ep + 1) + 1); - continue; - } - - struct stat st; - int rc = lstat(buf, &st); - if (rc) { - if (last && errno == ENOENT) { - break; - } - return NULL; - } - - if (S_ISLNK(st.st_mode)) { - char target[8192]; - char tmp[8192]; - size_t total_len = 0; - size_t buf_len = sp - 1 - buf; - size_t rest_len = 0; - size_t pos = 0; - const char *rest = NULL; - - if (!last) { - rest = ep + 1; - rest_len = strlen(rest); - } - if (++depth > 8) { - errno = ELOOP; - return NULL; - } - ssize_t target_len = readlink(buf, target, sizeof(target)); - if (target_len < 0) { - return NULL; - } - if (target_len == sizeof(target)) { - errno = ENAMETOOLONG; - return NULL; - } - target[target_len] = '\0'; - - // Calculate length - if (target[0] != '/') { - total_len = buf_len + 1; - } - total_len += target_len; - if (rest) { - total_len += 1 + rest_len; - } - if (total_len + 1 > sizeof(tmp)) { - errno = ENAMETOOLONG; - return NULL; - } - - // Build new path - if (target[0] != '/') { - memcpy(tmp, buf, buf_len); - pos += buf_len; - tmp[pos++] = '/'; - } - memcpy(tmp + pos, target, target_len); - pos += target_len; - if (rest) { - tmp[pos++] = '/'; - memcpy(tmp + pos, rest, rest_len); - pos += rest_len; - } - tmp[pos] = '\0'; - pos = remove_double_slashes(tmp); - - // Restart - memcpy(buf, tmp, pos + 1); - sp = buf + 1; - continue; - } - - if (last) { - break; - } - - if (!S_ISDIR(st.st_mode)) { - errno = ENOTDIR; - return NULL; - } - - *ep = '/'; - sp = ep + 1; - } - return xstrdup(buf); + // If the full path doesn't exist but the directory part does, + // return the concatenation of the real directory and the + // non-existent filename + abs = path_join(dir, base); + free(dir); + return abs; } static bool path_component(const char *path, size_t pos) diff -Nru dte-1.9.1/src/util/path.h dte-1.10/src/util/path.h --- dte-1.9.1/src/util/path.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/path.h 2021-04-03 21:08:53.000000000 +0000 @@ -4,10 +4,10 @@ #include #include #include +#include "debug.h" #include "macros.h" #include "string-view.h" #include "xmalloc.h" -#include "../debug.h" NONNULL_ARGS static inline bool path_is_absolute(const char *path) @@ -26,8 +26,8 @@ NONNULL_ARGS static inline StringView path_slice_dirname(const char *filename) { - const char *const slash = strrchr(filename, '/'); - if (slash == NULL) { + const char *slash = strrchr(filename, '/'); + if (!slash) { return string_view(".", 1); } bool slash_is_root_dir = (slash == filename); @@ -42,6 +42,21 @@ return xstrcut(dir.data, dir.length); } +XSTRDUP +static inline char *path_join(const char *s1, const char *s2) +{ + size_t n1 = strlen(s1); + size_t n2 = strlen(s2); + char *path = xmalloc(n1 + n2 + 2); + memcpy(path, s1, n1); + char *ptr = path + n1; + if (n1 && n2 && s1[n1 - 1] != '/') { + *ptr++ = '/'; + } + memcpy(ptr, s2, n2 + 1); + return path; +} + // If path is the root directory, return false. Otherwise, mutate // the path argument to become its parent directory and return true. // Note: path *must* be canonical (i.e. as returned by path_absolute()). @@ -62,7 +77,7 @@ BUG_ON(data[length - 1] == '/'); } - const char *slash = string_view_memrchr(path, '/'); + const char *slash = strview_memrchr(path, '/'); BUG_ON(slash == NULL); length = (size_t)(slash - data); diff -Nru dte-1.9.1/src/util/ptr-array.c dte-1.10/src/util/ptr-array.c --- dte-1.9.1/src/util/ptr-array.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/ptr-array.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,11 @@ #include #include "ptr-array.h" +#include "debug.h" -void ptr_array_add(PointerArray *array, void *ptr) +void ptr_array_append(PointerArray *array, void *ptr) { size_t alloc = array->alloc; - if (alloc == array->count) { + if (unlikely(alloc == array->count)) { // NOTE: if alloc was 1 then new alloc would be 1*3/2 = 1! alloc *= 3; alloc /= 2; @@ -19,15 +20,47 @@ void ptr_array_insert(PointerArray *array, void *ptr, size_t pos) { + BUG_ON(pos > array->count); size_t count = array->count - pos; - ptr_array_add(array, NULL); + ptr_array_append(array, NULL); memmove(array->ptrs + pos + 1, array->ptrs + pos, count * sizeof(void *)); array->ptrs[pos] = ptr; } +// Move a pointer from one index to another +void ptr_array_move(PointerArray *array, size_t from, size_t to) +{ + BUG_ON(from >= array->count); + BUG_ON(to >= array->count); + if (unlikely(from == to)) { + return; + } + + void **p = array->ptrs; + void *tmp = p[from]; + size_t difference = (from < to) ? (to - from) : (from - to); + if (difference == 1) { + // Adjacent pointers can be moved with a simple swap + p[from] = p[to]; + p[to] = tmp; + return; + } + + void *dest, *src; + if (from < to) { + dest = p + from; + src = p + from + 1; + } else { + dest = p + to + 1; + src = p + to; + } + memmove(dest, src, difference * sizeof(void*)); + p[to] = tmp; +} + void ptr_array_free_cb(PointerArray *array, FreeFunction free_ptr) { - for (size_t i = 0; i < array->count; i++) { + for (size_t i = 0, n = array->count; i < n; i++) { free_ptr(array->ptrs[i]); array->ptrs[i] = NULL; } @@ -42,6 +75,7 @@ void *ptr_array_remove_idx(PointerArray *array, size_t pos) { + BUG_ON(pos >= array->count); void **ptrs = array->ptrs; void *removed = ptrs[pos]; array->count--; @@ -59,12 +93,6 @@ return -1; } -void *ptr_array_rel(const PointerArray *array, const void *ptr, size_t offset) -{ - size_t i = ptr_array_idx(array, ptr); - return array->ptrs[(i + offset + array->count) % array->count]; -} - // Trim all leading NULLs and all but one trailing NULL (if any) void ptr_array_trim_nulls(PointerArray *array) { diff -Nru dte-1.9.1/src/util/ptr-array.h dte-1.10/src/util/ptr-array.h --- dte-1.9.1/src/util/ptr-array.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/ptr-array.h 2021-04-03 21:08:53.000000000 +0000 @@ -21,36 +21,24 @@ typedef void (*FreeFunction)(void *ptr); #define FREE_FUNC(f) (FreeFunction)f -void ptr_array_add(PointerArray *array, void *ptr) NONNULL_ARG(1); +void ptr_array_append(PointerArray *array, void *ptr) NONNULL_ARG(1); void ptr_array_insert(PointerArray *array, void *ptr, size_t pos) NONNULL_ARG(1); +void ptr_array_move(PointerArray *array, size_t from, size_t to) NONNULL_ARGS; void ptr_array_free_cb(PointerArray *array, FreeFunction free_ptr) NONNULL_ARGS; void ptr_array_remove(PointerArray *array, void *ptr) NONNULL_ARG(1); void *ptr_array_remove_idx(PointerArray *array, size_t pos) NONNULL_ARG(1); size_t ptr_array_idx(const PointerArray *array, const void *ptr) NONNULL_ARG(1); -void *ptr_array_rel(const PointerArray *array, const void *ptr, size_t offset) NONNULL_ARG(1); void ptr_array_trim_nulls(PointerArray *array) NONNULL_ARGS; NONNULL_ARGS static inline void ptr_array_init(PointerArray *array, size_t capacity) { - capacity = ROUND_UP(capacity, 8); + capacity = round_size_to_next_multiple(capacity, 8); array->count = 0; array->ptrs = capacity ? xnew(array->ptrs, capacity) : NULL; array->alloc = capacity; } -NONNULL_ARG(1) -static inline void *ptr_array_next(const PointerArray *array, const void *ptr) -{ - return ptr_array_rel(array, ptr, 1); -} - -NONNULL_ARG(1) -static inline void *ptr_array_prev(const PointerArray *array, const void *ptr) -{ - return ptr_array_rel(array, ptr, -1); -} - // Free each pointer and then free the array. NONNULL_ARGS static inline void ptr_array_free(PointerArray *array) diff -Nru dte-1.9.1/src/util/readfile.c dte-1.10/src/util/readfile.c --- dte-1.9.1/src/util/readfile.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/readfile.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,31 +1,37 @@ -#include +#include #include -#include +#include #include #include "readfile.h" -#include "xmalloc.h" #include "xreadwrite.h" -#include "../debug.h" -ssize_t stat_read_file(const char *filename, char **bufp, struct stat *st) +ssize_t read_file(const char *filename, char **bufp) { - int fd = open(filename, O_RDONLY); *bufp = NULL; + int fd = xopen(filename, O_RDONLY | O_CLOEXEC, 0); if (fd == -1) { return -1; } - if (fstat(fd, st) == -1) { - close(fd); + + struct stat st; + if (fstat(fd, &st) == -1) { + xclose(fd); return -1; } - if (S_ISDIR(st->st_mode)) { - close(fd); + if (S_ISDIR(st.st_mode)) { + xclose(fd); errno = EISDIR; return -1; } - char *buf = xmalloc(st->st_size + 1); - ssize_t r = xread(fd, buf, st->st_size); - close(fd); + + char *buf = malloc(st.st_size + 1); + if (unlikely(!buf)) { + xclose(fd); + return -1; + } + + ssize_t r = xread(fd, buf, st.st_size); + xclose(fd); if (r > 0) { buf[r] = '\0'; *bufp = buf; @@ -34,32 +40,3 @@ } return r; } - -char *buf_next_line(char *buf, size_t *posp, size_t size) -{ - size_t pos = *posp; - BUG_ON(pos >= size); - size_t avail = size - pos; - char *line = buf + pos; - char *nl = memchr(line, '\n', avail); - if (nl) { - *nl = '\0'; - *posp += nl - line + 1; - } else { - line[avail] = '\0'; - *posp += avail; - } - return line; -} - -StringView buf_slice_next_line(const char *buf, size_t *posp, size_t size) -{ - size_t pos = *posp; - BUG_ON(pos >= size); - size_t avail = size - pos; - const char *line = buf + pos; - const char *nl = memchr(line, '\n', avail); - size_t line_length = nl ? (nl - line + 1) : avail; - *posp += line_length; - return string_view(line, line_length); -} diff -Nru dte-1.9.1/src/util/readfile.h dte-1.10/src/util/readfile.h --- dte-1.9.1/src/util/readfile.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/readfile.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,23 +1,11 @@ #ifndef UTIL_READFILE_H #define UTIL_READFILE_H -#include -#include #include #include "macros.h" -#include "string-view.h" - -ssize_t stat_read_file(const char *filename, char **bufp, struct stat *st); // Returns size of file or -1 on error. // For empty files *bufp is NULL, otherwise *bufp is NUL-terminated. -static inline ssize_t read_file(const char *filename, char **bufp) -{ - struct stat st; - return stat_read_file(filename, bufp, &st); -} - -char *buf_next_line(char *buf, size_t *posp, size_t size); -StringView buf_slice_next_line(const char *buf, size_t *posp, size_t size); +ssize_t read_file(const char *filename, char **bufp) NONNULL_ARG(1); #endif diff -Nru dte-1.9.1/src/util/string.c dte-1.10/src/util/string.c --- dte-1.9.1/src/util/string.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/string.c 2021-04-03 21:08:53.000000000 +0000 @@ -5,19 +5,20 @@ #include "string.h" #include "utf8.h" #include "xmalloc.h" -#include "../debug.h" +#include "debug.h" static void string_grow(String *s, size_t more) { + BUG_ON(more == 0); const size_t len = s->len + more; size_t alloc = s->alloc; - if (alloc >= len) { + if (likely(alloc >= len)) { return; } while (alloc < len) { alloc = (alloc * 3 + 2) / 2; } - alloc = ROUND_UP(alloc, 16); + alloc = round_size_to_next_multiple(alloc, 16); xrenew(s->buffer, alloc); s->alloc = alloc; } @@ -25,16 +26,16 @@ void string_free(String *s) { free(s->buffer); - string_init(s); + *s = (String) STRING_INIT; } -void string_add_byte(String *s, unsigned char byte) +void string_append_byte(String *s, unsigned char byte) { string_grow(s, 1); s->buffer[s->len++] = byte; } -size_t string_add_ch(String *s, CodePoint u) +size_t string_append_codepoint(String *s, CodePoint u) { size_t len = u_char_size(u); string_grow(s, len); @@ -42,6 +43,15 @@ return len; } +static void string_make_space(String *s, size_t pos, size_t len) +{ + BUG_ON(pos > s->len); + BUG_ON(len == 0); + string_grow(s, len); + memmove(s->buffer + pos + len, s->buffer + pos, s->len - pos); + s->len += len; +} + size_t string_insert_ch(String *s, size_t pos, CodePoint u) { size_t len = u_char_size(u); @@ -50,17 +60,31 @@ return len; } -void string_add_str(String *s, const char *str) +void string_insert_buf(String *s, size_t pos, const char *buf, size_t len) +{ + if (len == 0) { + return; + } + string_make_space(s, pos, len); + memcpy(s->buffer + pos, buf, len); +} + +void string_append_cstring(String *s, const char *cstr) +{ + string_append_buf(s, cstr, strlen(cstr)); +} + +void string_append_string(String *s1, const String *s2) { - string_add_buf(s, str, strlen(str)); + string_append_buf(s1, s2->buffer, s2->len); } -void string_add_string_view(String *s, const StringView *sv) +void string_append_strview(String *s, const StringView *sv) { - string_add_buf(s, sv->data, sv->length); + string_append_buf(s, sv->data, sv->length); } -void string_add_buf(String *s, const char *ptr, size_t len) +void string_append_buf(String *s, const char *ptr, size_t len) { if (!len) { return; @@ -93,19 +117,17 @@ va_end(ap); } -char *string_steal(String *s, size_t *len) +static void null_terminate(String *s) { - char *b = s->buffer; - *len = s->len; - string_init(s); - return b; + string_grow(s, 1); + s->buffer[s->len] = '\0'; } char *string_steal_cstring(String *s) { - string_add_byte(s, '\0'); + null_terminate(s); char *b = s->buffer; - string_init(s); + *s = (String) STRING_INIT; return b; } @@ -121,12 +143,6 @@ return b; } -void string_ensure_null_terminated(String *s) -{ - string_grow(s, 1); - s->buffer[s->len] = '\0'; -} - /* * This method first ensures that the String buffer is null-terminated * and then returns a const pointer to it, without doing any copying. @@ -139,18 +155,10 @@ */ const char *string_borrow_cstring(String *s) { - string_ensure_null_terminated(s); + null_terminate(s); return s->buffer; } -void string_make_space(String *s, size_t pos, size_t len) -{ - BUG_ON(pos > s->len); - string_grow(s, len); - memmove(s->buffer + pos + len, s->buffer + pos, s->len - pos); - s->len += len; -} - void string_remove(String *s, size_t pos, size_t len) { BUG_ON(pos + len > s->len); diff -Nru dte-1.9.1/src/util/string.h dte-1.10/src/util/string.h --- dte-1.9.1/src/util/string.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/string.h 2021-04-03 21:08:53.000000000 +0000 @@ -19,18 +19,11 @@ .len = 0 \ } -#define string_add_literal(s, l) ( \ - string_add_buf(s, l, STRLEN(l)) \ -) - -static inline NONNULL_ARGS void string_init(String *s) -{ - *s = (String) STRING_INIT; -} +#define string_append_literal(s, x) string_append_buf(s, x, STRLEN(x)) static inline String string_new(size_t size) { - size = ROUND_UP(size, 16); + size = round_size_to_next_multiple(size, 16); return (String) { .buffer = size ? xmalloc(size) : NULL, .alloc = size, @@ -43,20 +36,24 @@ s->len = 0; } +static inline StringView strview_from_string(const String *s) +{ + return string_view(s->buffer, s->len); +} + void string_free(String *s) NONNULL_ARGS; -void string_add_byte(String *s, unsigned char byte) NONNULL_ARGS; -size_t string_add_ch(String *s, CodePoint u) NONNULL_ARGS; -void string_add_str(String *s, const char *str) NONNULL_ARGS; -void string_add_string_view(String *s, const StringView *sv) NONNULL_ARGS; -void string_add_buf(String *s, const char *ptr, size_t len) NONNULL_ARGS; +void string_append_byte(String *s, unsigned char byte) NONNULL_ARGS; +size_t string_append_codepoint(String *s, CodePoint u) NONNULL_ARGS; +void string_append_cstring(String *s, const char *cstr) NONNULL_ARGS; +void string_append_string(String *s1, const String *s2) NONNULL_ARGS; +void string_append_strview(String *s, const StringView *sv) NONNULL_ARGS; +void string_append_buf(String *s, const char *ptr, size_t len) NONNULL_ARG(1); size_t string_insert_ch(String *s, size_t pos, CodePoint u) NONNULL_ARGS; +void string_insert_buf(String *s, size_t pos, const char *buf, size_t len) NONNULL_ARG(1); void string_sprintf(String *s, const char *fmt, ...) PRINTF(2) NONNULL_ARGS; -char *string_steal(String *s, size_t *len) NONNULL_ARGS; char *string_steal_cstring(String *s) NONNULL_ARGS_AND_RETURN; char *string_clone_cstring(const String *s) XSTRDUP; const char *string_borrow_cstring(String *s) NONNULL_ARGS_AND_RETURN; -void string_ensure_null_terminated(String *s) NONNULL_ARGS; -void string_make_space(String *s, size_t pos, size_t len) NONNULL_ARGS; void string_remove(String *s, size_t pos, size_t len) NONNULL_ARGS; #endif diff -Nru dte-1.9.1/src/util/string-view.h dte-1.10/src/util/string-view.h --- dte-1.9.1/src/util/string-view.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/string-view.h 2021-04-03 21:08:53.000000000 +0000 @@ -5,13 +5,14 @@ #include #include #include "ascii.h" +#include "debug.h" #include "macros.h" // A non-owning, length-bounded "view" into another string, similar to // the C++17 string_view class. The .data member will usually *not* be // null-terminated and the underlying string *must* outlive the view. typedef struct { - const char NONSTRING *data; + const unsigned char NONSTRING *data; size_t length; } StringView; @@ -25,22 +26,6 @@ .length = STRLEN(s) \ } -#define string_view_equal_literal(sv, str) ( \ - string_view_equal_strn((sv), (str), STRLEN(str)) \ -) - -#define string_view_equal_literal_icase(sv, str) ( \ - string_view_equal_strn_icase((sv), (str), STRLEN(str)) \ -) - -#define string_view_has_literal_prefix(sv, prefix) ( \ - string_view_has_prefix((sv), (prefix), STRLEN(prefix)) \ -) - -#define string_view_has_literal_prefix_icase(sv, prefix) ( \ - string_view_has_prefix_icase((sv), (prefix), STRLEN(prefix)) \ -) - static inline StringView string_view(const char *str, size_t length) { return (StringView) { @@ -49,22 +34,19 @@ }; } -static inline StringView string_view_from_cstring(const char *str) +static inline StringView strview_from_cstring(const char *str) { - return (StringView) { - .data = str, - .length = str ? strlen(str) : 0 - }; + return string_view(str, str ? strlen(str) : 0); } NONNULL_ARGS -static inline bool string_view_equal(const StringView *a, const StringView *b) +static inline bool strview_equal(const StringView *a, const StringView *b) { return a->length == b->length && memcmp(a->data, b->data, a->length) == 0; } NONNULL_ARGS -static inline bool string_view_equal_strn ( +static inline bool strview_equal_strn ( const StringView *sv, const char *str, size_t len @@ -73,7 +55,7 @@ } NONNULL_ARGS -static inline bool string_view_equal_strn_icase ( +static inline bool strview_equal_strn_icase ( const StringView *sv, const char *str, size_t len @@ -82,60 +64,103 @@ } NONNULL_ARGS -static inline bool string_view_equal_cstr(const StringView *sv, const char *str) +static inline bool strview_equal_cstring(const StringView *sv, const char *str) { - return string_view_equal_strn(sv, str, strlen(str)); + return strview_equal_strn(sv, str, strlen(str)); } NONNULL_ARGS -static inline bool string_view_has_prefix ( - const StringView *sv, - const char *str, - size_t length -) { - return sv->length >= length && memcmp(sv->data, str, length) == 0; +static inline bool strview_equal_cstring_icase(const StringView *sv, const char *str) +{ + return strview_equal_strn_icase(sv, str, strlen(str)); } NONNULL_ARGS -static inline bool string_view_has_prefix_icase ( - const StringView *sv, - const char *str, - size_t length -) { - return sv->length >= length && mem_equal_icase(sv->data, str, length); +static inline bool strview_has_prefix(const StringView *sv, const char *p) +{ + size_t length = strlen(p); + return sv->length >= length && memcmp(sv->data, p, length) == 0; +} + +NONNULL_ARGS +static inline bool strview_has_prefix_icase(const StringView *sv, const char *p) +{ + size_t length = strlen(p); + return sv->length >= length && mem_equal_icase(sv->data, p, length); +} + +NONNULL_ARGS +static inline bool strview_isblank(const StringView *sv) +{ + const unsigned char *data = sv->data; + const size_t len = sv->length; + size_t i = 0; + while (i < len && ascii_isblank(data[i])) { + i++; + } + return (i == len); +} + +NONNULL_ARGS +static inline bool strview_contains_char_type(const StringView *sv, AsciiCharType mask) +{ + const unsigned char *data = sv->data; + for (size_t i = 0, n = sv->length; i < n; i++) { + if (ascii_test(data[i], mask)) { + return true; + } + } + return false; } NONNULL_ARGS -static inline void *string_view_memchr(const StringView *sv, int c) +static inline const unsigned char *strview_memchr(const StringView *sv, int c) { return memchr(sv->data, c, sv->length); } NONNULL_ARGS -static inline void *string_view_memrchr(const StringView *sv, int c) +static inline const unsigned char *strview_memrchr(const StringView *sv, int c) { const unsigned char *s = sv->data; size_t n = sv->length; c = (int)(unsigned char)c; while (n--) { if (s[n] == c) { - return (void*)(s + n); + return (s + n); } } return NULL; } +static inline void strview_remove_prefix(StringView *sv, size_t len) +{ + BUG_ON(len > sv->length); + sv->data += len; + sv->length -= len; +} + NONNULL_ARGS -static inline void string_view_trim_left(StringView *sv) +static inline void strview_trim_left(StringView *sv) { - const char *data = sv->data; + const unsigned char *data = sv->data; const size_t len = sv->length; size_t i = 0; while (i < len && ascii_isblank(data[i])) { i++; } - sv->data = data + i; - sv->length = len - i; + strview_remove_prefix(sv, i); +} + +NONNULL_ARGS +static inline void strview_trim_right(StringView *sv) +{ + const unsigned char *data = sv->data; + size_t n = sv->length; + while (n && ascii_isblank(data[n - 1])) { + n--; + } + sv->length = n; } #endif diff -Nru dte-1.9.1/src/util/strtonum.c dte-1.10/src/util/strtonum.c --- dte-1.9.1/src/util/strtonum.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/strtonum.c 2021-04-03 21:08:53.000000000 +0000 @@ -4,24 +4,32 @@ #include "ascii.h" #include "checked-arith.h" -int number_width(long n) -{ - int width = 0; - - if (n < 0) { - n *= -1; - width++; - } - do { - n /= 10; - width++; - } while (n); - return width; -} +enum { + I = HEX_INVALID +}; + +const uint8_t hex_table[256] = { + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, I, I, I, I, I, I, + I, 10, 11, 12, 13, 14, 15, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, 10, 11, 12, 13, 14, 15, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, + I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I +}; size_t buf_parse_uintmax(const char *str, size_t size, uintmax_t *valp) { - if (size == 0 || !ascii_isdigit(str[0])) { + if (unlikely(size == 0 || !ascii_isdigit(str[0]))) { return 0; } @@ -29,10 +37,10 @@ size_t i = 1; while (i < size && ascii_isdigit(str[i])) { - if (umax_multiply_overflows(val, 10, &val)) { + if (unlikely(umax_multiply_overflows(val, 10, &val))) { return 0; } - if (umax_add_overflows(val, str[i++] - '0', &val)) { + if (unlikely(umax_add_overflows(val, str[i++] - '0', &val))) { return 0; } } @@ -65,21 +73,21 @@ static size_t buf_parse_long(const char *str, size_t size, long *valp) { + if (unlikely(size == 0)) { + return 0; + } + bool negative = false; size_t skipped = 0; - if (size == 0) { - return 0; - } else { - switch (str[0]) { - case '-': - negative = true; - // Fallthrough - case '+': - skipped = 1; - str++; - size--; - break; - } + switch (str[0]) { + case '-': + negative = true; + // Fallthrough + case '+': + skipped = 1; + str++; + size--; + break; } uintmax_t val; @@ -100,7 +108,7 @@ bool str_to_int(const char *str, int *valp) { const size_t len = strlen(str); - if (len == 0) { + if (unlikely(len == 0)) { return false; } long val; @@ -115,7 +123,7 @@ bool str_to_uint(const char *str, unsigned int *valp) { const size_t len = strlen(str); - if (len == 0) { + if (unlikely(len == 0)) { return false; } uintmax_t val; @@ -130,7 +138,7 @@ bool str_to_size(const char *str, size_t *valp) { const size_t len = strlen(str); - if (len == 0) { + if (unlikely(len == 0)) { return false; } uintmax_t val; @@ -145,7 +153,7 @@ bool str_to_ulong(const char *str, unsigned long *valp) { const size_t len = strlen(str); - if (len == 0) { + if (unlikely(len == 0)) { return false; } unsigned long val; @@ -156,3 +164,13 @@ *valp = val; return true; } + +size_t size_str_width(size_t x) +{ + size_t width = 0; + do { + x /= 10; + width++; + } while (x); + return width; +} diff -Nru dte-1.9.1/src/util/strtonum.h dte-1.10/src/util/strtonum.h --- dte-1.9.1/src/util/strtonum.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/strtonum.h 2021-04-03 21:08:53.000000000 +0000 @@ -6,7 +6,19 @@ #include #include "macros.h" -int number_width(long n) CONST_FN; +enum { + HEX_INVALID = 0xF0, +}; + +// Decodes a single, hexadecimal digit and returns a numerical value +// between 0-15, or HEX_INVALID for invalid digits +static inline unsigned int hex_decode(unsigned char c) +{ + extern const uint8_t hex_table[256]; + return hex_table[c]; +} + +size_t size_str_width(size_t x) CONST_FN; size_t buf_parse_uintmax(const char *str, size_t size, uintmax_t *valp) NONNULL_ARG(1); size_t buf_parse_ulong(const char *str, size_t size, unsigned long *valp) NONNULL_ARG(1); size_t buf_parse_uint(const char *str, size_t size, unsigned int *valp) NONNULL_ARG(1); diff -Nru dte-1.9.1/src/util/str-util.h dte-1.10/src/util/str-util.h --- dte-1.9.1/src/util/str-util.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/str-util.h 2021-04-03 21:08:53.000000000 +0000 @@ -5,7 +5,9 @@ #include #include #include +#include "debug.h" #include "macros.h" +#include "string-view.h" #include "xmalloc.h" #define MEMZERO(ptr) memset((ptr), 0, sizeof(*(ptr))) @@ -18,12 +20,13 @@ static inline bool xstreq(const char *a, const char *b) { - if (a == b) { - return true; - } else if (a == NULL || b == NULL) { - return false; - } - return streq(a, b); + return (a == b) || (a && b && streq(a, b)); +} + +NONNULL_ARGS +static inline bool mem_equal(const void *s1, const void *s2, size_t n) +{ + return memcmp(s1, s2, n) == 0; } NONNULL_ARGS @@ -40,13 +43,54 @@ if (l2 > l1) { return false; } - return memcmp(str + l1 - l2, suffix, l2) == 0; + return mem_equal(str + l1 - l2, suffix, l2); } NONNULL_ARGS -static inline bool mem_equal(const void *s1, const void *s2, size_t n) +static inline StringView get_delim(const char *buf, size_t *posp, size_t size, int delim) { - return memcmp(s1, s2, n) == 0; + size_t pos = *posp; + BUG_ON(pos >= size); + size_t len = size - pos; + size_t delim_len = 0; + const char *ptr = buf + pos; + const char *found = memchr(ptr, delim, len); + if (found) { + len = (size_t)(found - ptr); + delim_len = 1; + } + *posp += len + delim_len; + return string_view(ptr, len); +} + +NONNULL_ARGS +static inline char *get_delim_str(char *buf, size_t *posp, size_t size, int delim) +{ + size_t pos = *posp; + BUG_ON(pos >= size); + size_t len = size - pos; + char *start = buf + pos; + char *found = memchr(start, delim, len); + if (found) { + *found = '\0'; + *posp += (size_t)(found - start) + 1; + } else { + start[len] = '\0'; + *posp += len; + } + return start; +} + +NONNULL_ARGS +static inline StringView buf_slice_next_line(const char *buf, size_t *posp, size_t size) +{ + return get_delim(buf, posp, size, '\n'); +} + +NONNULL_ARGS +static inline char *buf_next_line(char *buf, size_t *posp, size_t size) +{ + return get_delim_str(buf, posp, size, '\n'); } NONNULL_ARGS @@ -65,6 +109,25 @@ return nl; } +static inline size_t string_array_length(char **strings) +{ + size_t n = 0; + while (strings[n]) { + n++; + } + return n; +} + +static inline bool string_array_contains_prefix(char **strs, const char *prefix) +{ + for (size_t i = 0; strs[i]; i++) { + if (str_has_prefix(strs[i], prefix)) { + return true; + } + } + return false; +} + static inline char **copy_string_array(char **src, size_t count) { char **dst = xnew(char*, count + 1); diff -Nru dte-1.9.1/src/util/unicode.c dte-1.10/src/util/unicode.c --- dte-1.9.1/src/util/unicode.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/unicode.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,21 +1,13 @@ #include #include "unicode.h" +#include "unidata.h" #include "ascii.h" -#include "../editor.h" +#include "editor.h" #define BISEARCH(u, arr) bisearch((u), (arr), ARRAY_COUNT(arr) - 1) -typedef struct { - CodePoint first, last; -} CodepointRange; - -#include "wcwidth.c" - -static bool bisearch ( - CodePoint u, - const CodepointRange *const range, - size_t max -) { +static bool bisearch(CodePoint u, const CodepointRange *range, size_t max) +{ if (u < range[0].first || u > range[max].last) { return false; } @@ -47,6 +39,7 @@ case '\f': case '\r': case ' ': + case 0x1680: // Ogham space mark case 0x2000: // En quad case 0x2001: // Em quad case 0x2002: // En space @@ -54,8 +47,11 @@ case 0x2004: // 3-per-em space case 0x2005: // 4-per-em space case 0x2006: // 6-per-em space + case 0x2008: // Punctuation space case 0x2009: // Thin space - case 0x200a: // Hair space + case 0x200A: // Hair space + case 0x200B: // Zero width space + case 0x205F: // Medium mathematical space case 0x3000: // Ideographic space return true; } @@ -111,8 +107,8 @@ unsigned int u_char_width(CodePoint u) { - if (u < 0x80) { - if (ascii_iscntrl(u)) { + if (likely(u < 0x80)) { + if (unlikely(ascii_iscntrl(u))) { return 2; // Rendered in caret notation (e.g. ^@) } return 1; @@ -120,7 +116,7 @@ return 0; } else if (u_is_unprintable(u)) { return 4; // Rendered as - } else if (u < 0x1100U) { + } else if (u < 0x1100) { return 1; } else if (u_is_double_width(u)) { return 2; diff -Nru dte-1.9.1/src/util/unicode.h dte-1.10/src/util/unicode.h --- dte-1.9.1/src/util/unicode.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/unicode.h 2021-04-03 21:08:53.000000000 +0000 @@ -5,7 +5,11 @@ #include #include "macros.h" -#define UNICODE_MAX_VALID_CODEPOINT UINT32_C(0x10ffff) +#define UNICODE_MAX_VALID_CODEPOINT UINT32_C(0x10FFFF) + +#if defined(WINT_MAX) && (WINT_MAX >= 0x10FFFF) && defined(__STDC_ISO_10646__) + #define SANE_WCTYPE 1 +#endif typedef uint32_t CodePoint; @@ -19,19 +23,53 @@ return (u < 0x20) || (u >= 0x7F && u <= 0x9F); } -static inline bool u_is_upper(CodePoint u) -{ - return (u - 'A') < 26; -} +#ifdef SANE_WCTYPE + #include -static inline CodePoint u_to_lower(CodePoint u) -{ - return u_is_upper(u) ? u + 32 : u; -} + static inline bool u_is_lower(CodePoint u) + { + return iswlower((wint_t)u); + } + + static inline bool u_is_upper(CodePoint u) + { + return iswupper((wint_t)u); + } + + static inline CodePoint u_to_lower(CodePoint u) + { + return towlower((wint_t)u); + } + + static inline CodePoint u_to_upper(CodePoint u) + { + return towupper((wint_t)u); + } +#else + static inline bool u_is_lower(CodePoint u) + { + return (u - 'a') < 26; + } + + static inline bool u_is_upper(CodePoint u) + { + return (u - 'A') < 26; + } + + static inline CodePoint u_to_lower(CodePoint u) + { + return u_is_upper(u) ? u + 32 : u; + } + + static inline CodePoint u_to_upper(CodePoint u) + { + return u_is_lower(u) ? u - 32 : u; + } +#endif // SANE_WCTYPE bool u_is_breakable_whitespace(CodePoint u) CONST_FN; bool u_is_word_char(CodePoint u) CONST_FN; -bool u_is_unprintable(CodePoint u) CONST_FN; +bool u_is_unprintable(CodePoint u); bool u_is_special_whitespace(CodePoint u) CONST_FN; bool u_is_zero_width(CodePoint u); unsigned int u_char_width(CodePoint uch) CONST_FN; diff -Nru dte-1.9.1/src/util/unidata.h dte-1.10/src/util/unidata.h --- dte-1.9.1/src/util/unidata.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/unidata.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,1155 @@ +typedef struct {CodePoint first, last;} CodepointRange; + +static const CodepointRange special_whitespace[] = { + {0x00a0, 0x00a0}, + {0x00ad, 0x00ad}, + {0x2000, 0x200a}, + {0x2028, 0x2029}, + {0x202f, 0x202f}, + {0x205f, 0x205f}, +}; + +static const CodepointRange default_ignorable[] = { + {0x034f, 0x034f}, + {0x061c, 0x061c}, + {0x115f, 0x1160}, + {0x17b4, 0x17b5}, + {0x180b, 0x180e}, + {0x200b, 0x200f}, + {0x202a, 0x202e}, + {0x2060, 0x206f}, + {0x3164, 0x3164}, + {0xfe00, 0xfe0f}, + {0xfeff, 0xfeff}, + {0xffa0, 0xffa0}, + {0xfff0, 0xfff8}, + {0x1bca0, 0x1bca3}, + {0x1d173, 0x1d17a}, + {0xe0000, 0xe0fff}, +}; + +static const CodepointRange nonspacing_mark[] = { + {0x0300, 0x036f}, + {0x0483, 0x0489}, + {0x0591, 0x05bd}, + {0x05bf, 0x05bf}, + {0x05c1, 0x05c2}, + {0x05c4, 0x05c5}, + {0x05c7, 0x05c7}, + {0x0610, 0x061a}, + {0x064b, 0x065f}, + {0x0670, 0x0670}, + {0x06d6, 0x06dc}, + {0x06df, 0x06e4}, + {0x06e7, 0x06e8}, + {0x06ea, 0x06ed}, + {0x0711, 0x0711}, + {0x0730, 0x074a}, + {0x07a6, 0x07b0}, + {0x07eb, 0x07f3}, + {0x07fd, 0x07fd}, + {0x0816, 0x0819}, + {0x081b, 0x0823}, + {0x0825, 0x0827}, + {0x0829, 0x082d}, + {0x0859, 0x085b}, + {0x08d3, 0x08e1}, + {0x08e3, 0x0902}, + {0x093a, 0x093a}, + {0x093c, 0x093c}, + {0x0941, 0x0948}, + {0x094d, 0x094d}, + {0x0951, 0x0957}, + {0x0962, 0x0963}, + {0x0981, 0x0981}, + {0x09bc, 0x09bc}, + {0x09c1, 0x09c4}, + {0x09cd, 0x09cd}, + {0x09e2, 0x09e3}, + {0x09fe, 0x09fe}, + {0x0a01, 0x0a02}, + {0x0a3c, 0x0a3c}, + {0x0a41, 0x0a42}, + {0x0a47, 0x0a48}, + {0x0a4b, 0x0a4d}, + {0x0a51, 0x0a51}, + {0x0a70, 0x0a71}, + {0x0a75, 0x0a75}, + {0x0a81, 0x0a82}, + {0x0abc, 0x0abc}, + {0x0ac1, 0x0ac5}, + {0x0ac7, 0x0ac8}, + {0x0acd, 0x0acd}, + {0x0ae2, 0x0ae3}, + {0x0afa, 0x0aff}, + {0x0b01, 0x0b01}, + {0x0b3c, 0x0b3c}, + {0x0b3f, 0x0b3f}, + {0x0b41, 0x0b44}, + {0x0b4d, 0x0b4d}, + {0x0b55, 0x0b56}, + {0x0b62, 0x0b63}, + {0x0b82, 0x0b82}, + {0x0bc0, 0x0bc0}, + {0x0bcd, 0x0bcd}, + {0x0c00, 0x0c00}, + {0x0c04, 0x0c04}, + {0x0c3e, 0x0c40}, + {0x0c46, 0x0c48}, + {0x0c4a, 0x0c4d}, + {0x0c55, 0x0c56}, + {0x0c62, 0x0c63}, + {0x0c81, 0x0c81}, + {0x0cbc, 0x0cbc}, + {0x0cbf, 0x0cbf}, + {0x0cc6, 0x0cc6}, + {0x0ccc, 0x0ccd}, + {0x0ce2, 0x0ce3}, + {0x0d00, 0x0d01}, + {0x0d3b, 0x0d3c}, + {0x0d41, 0x0d44}, + {0x0d4d, 0x0d4d}, + {0x0d62, 0x0d63}, + {0x0d81, 0x0d81}, + {0x0dca, 0x0dca}, + {0x0dd2, 0x0dd4}, + {0x0dd6, 0x0dd6}, + {0x0e31, 0x0e31}, + {0x0e34, 0x0e3a}, + {0x0e47, 0x0e4e}, + {0x0eb1, 0x0eb1}, + {0x0eb4, 0x0ebc}, + {0x0ec8, 0x0ecd}, + {0x0f18, 0x0f19}, + {0x0f35, 0x0f35}, + {0x0f37, 0x0f37}, + {0x0f39, 0x0f39}, + {0x0f71, 0x0f7e}, + {0x0f80, 0x0f84}, + {0x0f86, 0x0f87}, + {0x0f8d, 0x0f97}, + {0x0f99, 0x0fbc}, + {0x0fc6, 0x0fc6}, + {0x102d, 0x1030}, + {0x1032, 0x1037}, + {0x1039, 0x103a}, + {0x103d, 0x103e}, + {0x1058, 0x1059}, + {0x105e, 0x1060}, + {0x1071, 0x1074}, + {0x1082, 0x1082}, + {0x1085, 0x1086}, + {0x108d, 0x108d}, + {0x109d, 0x109d}, + {0x135d, 0x135f}, + {0x1712, 0x1714}, + {0x1732, 0x1734}, + {0x1752, 0x1753}, + {0x1772, 0x1773}, + {0x17b4, 0x17b5}, + {0x17b7, 0x17bd}, + {0x17c6, 0x17c6}, + {0x17c9, 0x17d3}, + {0x17dd, 0x17dd}, + {0x180b, 0x180d}, + {0x1885, 0x1886}, + {0x18a9, 0x18a9}, + {0x1920, 0x1922}, + {0x1927, 0x1928}, + {0x1932, 0x1932}, + {0x1939, 0x193b}, + {0x1a17, 0x1a18}, + {0x1a1b, 0x1a1b}, + {0x1a56, 0x1a56}, + {0x1a58, 0x1a5e}, + {0x1a60, 0x1a60}, + {0x1a62, 0x1a62}, + {0x1a65, 0x1a6c}, + {0x1a73, 0x1a7c}, + {0x1a7f, 0x1a7f}, + {0x1ab0, 0x1ac0}, + {0x1b00, 0x1b03}, + {0x1b34, 0x1b34}, + {0x1b36, 0x1b3a}, + {0x1b3c, 0x1b3c}, + {0x1b42, 0x1b42}, + {0x1b6b, 0x1b73}, + {0x1b80, 0x1b81}, + {0x1ba2, 0x1ba5}, + {0x1ba8, 0x1ba9}, + {0x1bab, 0x1bad}, + {0x1be6, 0x1be6}, + {0x1be8, 0x1be9}, + {0x1bed, 0x1bed}, + {0x1bef, 0x1bf1}, + {0x1c2c, 0x1c33}, + {0x1c36, 0x1c37}, + {0x1cd0, 0x1cd2}, + {0x1cd4, 0x1ce0}, + {0x1ce2, 0x1ce8}, + {0x1ced, 0x1ced}, + {0x1cf4, 0x1cf4}, + {0x1cf8, 0x1cf9}, + {0x1dc0, 0x1df9}, + {0x1dfb, 0x1dff}, + {0x20d0, 0x20f0}, + {0x2cef, 0x2cf1}, + {0x2d7f, 0x2d7f}, + {0x2de0, 0x2dff}, + {0x302a, 0x302d}, + {0x3099, 0x309a}, + {0xa66f, 0xa672}, + {0xa674, 0xa67d}, + {0xa69e, 0xa69f}, + {0xa6f0, 0xa6f1}, + {0xa802, 0xa802}, + {0xa806, 0xa806}, + {0xa80b, 0xa80b}, + {0xa825, 0xa826}, + {0xa82c, 0xa82c}, + {0xa8c4, 0xa8c5}, + {0xa8e0, 0xa8f1}, + {0xa8ff, 0xa8ff}, + {0xa926, 0xa92d}, + {0xa947, 0xa951}, + {0xa980, 0xa982}, + {0xa9b3, 0xa9b3}, + {0xa9b6, 0xa9b9}, + {0xa9bc, 0xa9bd}, + {0xa9e5, 0xa9e5}, + {0xaa29, 0xaa2e}, + {0xaa31, 0xaa32}, + {0xaa35, 0xaa36}, + {0xaa43, 0xaa43}, + {0xaa4c, 0xaa4c}, + {0xaa7c, 0xaa7c}, + {0xaab0, 0xaab0}, + {0xaab2, 0xaab4}, + {0xaab7, 0xaab8}, + {0xaabe, 0xaabf}, + {0xaac1, 0xaac1}, + {0xaaec, 0xaaed}, + {0xaaf6, 0xaaf6}, + {0xabe5, 0xabe5}, + {0xabe8, 0xabe8}, + {0xabed, 0xabed}, + {0xfb1e, 0xfb1e}, + {0xfe00, 0xfe0f}, + {0xfe20, 0xfe2f}, + {0x101fd, 0x101fd}, + {0x102e0, 0x102e0}, + {0x10376, 0x1037a}, + {0x10a01, 0x10a03}, + {0x10a05, 0x10a06}, + {0x10a0c, 0x10a0f}, + {0x10a38, 0x10a3a}, + {0x10a3f, 0x10a3f}, + {0x10ae5, 0x10ae6}, + {0x10d24, 0x10d27}, + {0x10eab, 0x10eac}, + {0x10f46, 0x10f50}, + {0x11001, 0x11001}, + {0x11038, 0x11046}, + {0x1107f, 0x11081}, + {0x110b3, 0x110b6}, + {0x110b9, 0x110ba}, + {0x11100, 0x11102}, + {0x11127, 0x1112b}, + {0x1112d, 0x11134}, + {0x11173, 0x11173}, + {0x11180, 0x11181}, + {0x111b6, 0x111be}, + {0x111c9, 0x111cc}, + {0x111cf, 0x111cf}, + {0x1122f, 0x11231}, + {0x11234, 0x11234}, + {0x11236, 0x11237}, + {0x1123e, 0x1123e}, + {0x112df, 0x112df}, + {0x112e3, 0x112ea}, + {0x11300, 0x11301}, + {0x1133b, 0x1133c}, + {0x11340, 0x11340}, + {0x11366, 0x1136c}, + {0x11370, 0x11374}, + {0x11438, 0x1143f}, + {0x11442, 0x11444}, + {0x11446, 0x11446}, + {0x1145e, 0x1145e}, + {0x114b3, 0x114b8}, + {0x114ba, 0x114ba}, + {0x114bf, 0x114c0}, + {0x114c2, 0x114c3}, + {0x115b2, 0x115b5}, + {0x115bc, 0x115bd}, + {0x115bf, 0x115c0}, + {0x115dc, 0x115dd}, + {0x11633, 0x1163a}, + {0x1163d, 0x1163d}, + {0x1163f, 0x11640}, + {0x116ab, 0x116ab}, + {0x116ad, 0x116ad}, + {0x116b0, 0x116b5}, + {0x116b7, 0x116b7}, + {0x1171d, 0x1171f}, + {0x11722, 0x11725}, + {0x11727, 0x1172b}, + {0x1182f, 0x11837}, + {0x11839, 0x1183a}, + {0x1193b, 0x1193c}, + {0x1193e, 0x1193e}, + {0x11943, 0x11943}, + {0x119d4, 0x119d7}, + {0x119da, 0x119db}, + {0x119e0, 0x119e0}, + {0x11a01, 0x11a0a}, + {0x11a33, 0x11a38}, + {0x11a3b, 0x11a3e}, + {0x11a47, 0x11a47}, + {0x11a51, 0x11a56}, + {0x11a59, 0x11a5b}, + {0x11a8a, 0x11a96}, + {0x11a98, 0x11a99}, + {0x11c30, 0x11c36}, + {0x11c38, 0x11c3d}, + {0x11c3f, 0x11c3f}, + {0x11c92, 0x11ca7}, + {0x11caa, 0x11cb0}, + {0x11cb2, 0x11cb3}, + {0x11cb5, 0x11cb6}, + {0x11d31, 0x11d36}, + {0x11d3a, 0x11d3a}, + {0x11d3c, 0x11d3d}, + {0x11d3f, 0x11d45}, + {0x11d47, 0x11d47}, + {0x11d90, 0x11d91}, + {0x11d95, 0x11d95}, + {0x11d97, 0x11d97}, + {0x11ef3, 0x11ef4}, + {0x16af0, 0x16af4}, + {0x16b30, 0x16b36}, + {0x16f4f, 0x16f4f}, + {0x16f8f, 0x16f92}, + {0x16fe4, 0x16fe4}, + {0x1bc9d, 0x1bc9e}, + {0x1d167, 0x1d169}, + {0x1d17b, 0x1d182}, + {0x1d185, 0x1d18b}, + {0x1d1aa, 0x1d1ad}, + {0x1d242, 0x1d244}, + {0x1da00, 0x1da36}, + {0x1da3b, 0x1da6c}, + {0x1da75, 0x1da75}, + {0x1da84, 0x1da84}, + {0x1da9b, 0x1da9f}, + {0x1daa1, 0x1daaf}, + {0x1e000, 0x1e006}, + {0x1e008, 0x1e018}, + {0x1e01b, 0x1e021}, + {0x1e023, 0x1e024}, + {0x1e026, 0x1e02a}, + {0x1e130, 0x1e136}, + {0x1e2ec, 0x1e2ef}, + {0x1e8d0, 0x1e8d6}, + {0x1e944, 0x1e94a}, + {0xe0100, 0xe01ef}, +}; + +static const CodepointRange double_width[] = { + {0x1100, 0x115f}, + {0x231a, 0x231b}, + {0x2329, 0x232a}, + {0x23e9, 0x23ec}, + {0x23f0, 0x23f0}, + {0x23f3, 0x23f3}, + {0x25fd, 0x25fe}, + {0x2614, 0x2615}, + {0x2648, 0x2653}, + {0x267f, 0x267f}, + {0x2693, 0x2693}, + {0x26a1, 0x26a1}, + {0x26aa, 0x26ab}, + {0x26bd, 0x26be}, + {0x26c4, 0x26c5}, + {0x26ce, 0x26ce}, + {0x26d4, 0x26d4}, + {0x26ea, 0x26ea}, + {0x26f2, 0x26f3}, + {0x26f5, 0x26f5}, + {0x26fa, 0x26fa}, + {0x26fd, 0x26fd}, + {0x2705, 0x2705}, + {0x270a, 0x270b}, + {0x2728, 0x2728}, + {0x274c, 0x274c}, + {0x274e, 0x274e}, + {0x2753, 0x2755}, + {0x2757, 0x2757}, + {0x2795, 0x2797}, + {0x27b0, 0x27b0}, + {0x27bf, 0x27bf}, + {0x2b1b, 0x2b1c}, + {0x2b50, 0x2b50}, + {0x2b55, 0x2b55}, + {0x2e80, 0x2e99}, + {0x2e9b, 0x2ef3}, + {0x2f00, 0x2fd5}, + {0x2ff0, 0x2ffb}, + {0x3000, 0x303e}, + {0x3041, 0x3096}, + {0x3099, 0x30ff}, + {0x3105, 0x312f}, + {0x3131, 0x318e}, + {0x3190, 0x31e3}, + {0x31f0, 0x321e}, + {0x3220, 0x3247}, + {0x3250, 0x4dbf}, + {0x4e00, 0xa48c}, + {0xa490, 0xa4c6}, + {0xa960, 0xa97c}, + {0xac00, 0xd7a3}, + {0xf900, 0xfaff}, + {0xfe10, 0xfe19}, + {0xfe30, 0xfe52}, + {0xfe54, 0xfe66}, + {0xfe68, 0xfe6b}, + {0xff01, 0xff60}, + {0xffe0, 0xffe6}, + {0x16fe0, 0x16fe4}, + {0x16ff0, 0x16ff1}, + {0x17000, 0x187f7}, + {0x18800, 0x18cd5}, + {0x18d00, 0x18d08}, + {0x1b000, 0x1b11e}, + {0x1b150, 0x1b152}, + {0x1b164, 0x1b167}, + {0x1b170, 0x1b2fb}, + {0x1f004, 0x1f004}, + {0x1f0cf, 0x1f0cf}, + {0x1f18e, 0x1f18e}, + {0x1f191, 0x1f19a}, + {0x1f200, 0x1f202}, + {0x1f210, 0x1f23b}, + {0x1f240, 0x1f248}, + {0x1f250, 0x1f251}, + {0x1f260, 0x1f265}, + {0x1f300, 0x1f320}, + {0x1f32d, 0x1f335}, + {0x1f337, 0x1f37c}, + {0x1f37e, 0x1f393}, + {0x1f3a0, 0x1f3ca}, + {0x1f3cf, 0x1f3d3}, + {0x1f3e0, 0x1f3f0}, + {0x1f3f4, 0x1f3f4}, + {0x1f3f8, 0x1f43e}, + {0x1f440, 0x1f440}, + {0x1f442, 0x1f4fc}, + {0x1f4ff, 0x1f53d}, + {0x1f54b, 0x1f54e}, + {0x1f550, 0x1f567}, + {0x1f57a, 0x1f57a}, + {0x1f595, 0x1f596}, + {0x1f5a4, 0x1f5a4}, + {0x1f5fb, 0x1f64f}, + {0x1f680, 0x1f6c5}, + {0x1f6cc, 0x1f6cc}, + {0x1f6d0, 0x1f6d2}, + {0x1f6d5, 0x1f6d7}, + {0x1f6eb, 0x1f6ec}, + {0x1f6f4, 0x1f6fc}, + {0x1f7e0, 0x1f7eb}, + {0x1f90c, 0x1f93a}, + {0x1f93c, 0x1f945}, + {0x1f947, 0x1f978}, + {0x1f97a, 0x1f9cb}, + {0x1f9cd, 0x1f9ff}, + {0x1fa70, 0x1fa74}, + {0x1fa78, 0x1fa7a}, + {0x1fa80, 0x1fa86}, + {0x1fa90, 0x1faa8}, + {0x1fab0, 0x1fab6}, + {0x1fac0, 0x1fac2}, + {0x1fad0, 0x1fad6}, + {0x20000, 0x2fffd}, + {0x30000, 0x3fffd}, +}; + +static const CodepointRange unprintable[] = { + {0x0080, 0x009f}, + {0x0378, 0x0379}, + {0x0380, 0x0383}, + {0x038b, 0x038b}, + {0x038d, 0x038d}, + {0x03a2, 0x03a2}, + {0x0530, 0x0530}, + {0x0557, 0x0558}, + {0x058b, 0x058c}, + {0x0590, 0x0590}, + {0x05c8, 0x05cf}, + {0x05eb, 0x05ee}, + {0x05f5, 0x05ff}, + {0x061d, 0x061d}, + {0x070e, 0x070e}, + {0x074b, 0x074c}, + {0x07b2, 0x07bf}, + {0x07fb, 0x07fc}, + {0x082e, 0x082f}, + {0x083f, 0x083f}, + {0x085c, 0x085d}, + {0x085f, 0x085f}, + {0x086b, 0x089f}, + {0x08b5, 0x08b5}, + {0x08c8, 0x08d2}, + {0x0984, 0x0984}, + {0x098d, 0x098e}, + {0x0991, 0x0992}, + {0x09a9, 0x09a9}, + {0x09b1, 0x09b1}, + {0x09b3, 0x09b5}, + {0x09ba, 0x09bb}, + {0x09c5, 0x09c6}, + {0x09c9, 0x09ca}, + {0x09cf, 0x09d6}, + {0x09d8, 0x09db}, + {0x09de, 0x09de}, + {0x09e4, 0x09e5}, + {0x09ff, 0x0a00}, + {0x0a04, 0x0a04}, + {0x0a0b, 0x0a0e}, + {0x0a11, 0x0a12}, + {0x0a29, 0x0a29}, + {0x0a31, 0x0a31}, + {0x0a34, 0x0a34}, + {0x0a37, 0x0a37}, + {0x0a3a, 0x0a3b}, + {0x0a3d, 0x0a3d}, + {0x0a43, 0x0a46}, + {0x0a49, 0x0a4a}, + {0x0a4e, 0x0a50}, + {0x0a52, 0x0a58}, + {0x0a5d, 0x0a5d}, + {0x0a5f, 0x0a65}, + {0x0a77, 0x0a80}, + {0x0a84, 0x0a84}, + {0x0a8e, 0x0a8e}, + {0x0a92, 0x0a92}, + {0x0aa9, 0x0aa9}, + {0x0ab1, 0x0ab1}, + {0x0ab4, 0x0ab4}, + {0x0aba, 0x0abb}, + {0x0ac6, 0x0ac6}, + {0x0aca, 0x0aca}, + {0x0ace, 0x0acf}, + {0x0ad1, 0x0adf}, + {0x0ae4, 0x0ae5}, + {0x0af2, 0x0af8}, + {0x0b00, 0x0b00}, + {0x0b04, 0x0b04}, + {0x0b0d, 0x0b0e}, + {0x0b11, 0x0b12}, + {0x0b29, 0x0b29}, + {0x0b31, 0x0b31}, + {0x0b34, 0x0b34}, + {0x0b3a, 0x0b3b}, + {0x0b45, 0x0b46}, + {0x0b49, 0x0b4a}, + {0x0b4e, 0x0b54}, + {0x0b58, 0x0b5b}, + {0x0b5e, 0x0b5e}, + {0x0b64, 0x0b65}, + {0x0b78, 0x0b81}, + {0x0b84, 0x0b84}, + {0x0b8b, 0x0b8d}, + {0x0b91, 0x0b91}, + {0x0b96, 0x0b98}, + {0x0b9b, 0x0b9b}, + {0x0b9d, 0x0b9d}, + {0x0ba0, 0x0ba2}, + {0x0ba5, 0x0ba7}, + {0x0bab, 0x0bad}, + {0x0bba, 0x0bbd}, + {0x0bc3, 0x0bc5}, + {0x0bc9, 0x0bc9}, + {0x0bce, 0x0bcf}, + {0x0bd1, 0x0bd6}, + {0x0bd8, 0x0be5}, + {0x0bfb, 0x0bff}, + {0x0c0d, 0x0c0d}, + {0x0c11, 0x0c11}, + {0x0c29, 0x0c29}, + {0x0c3a, 0x0c3c}, + {0x0c45, 0x0c45}, + {0x0c49, 0x0c49}, + {0x0c4e, 0x0c54}, + {0x0c57, 0x0c57}, + {0x0c5b, 0x0c5f}, + {0x0c64, 0x0c65}, + {0x0c70, 0x0c76}, + {0x0c8d, 0x0c8d}, + {0x0c91, 0x0c91}, + {0x0ca9, 0x0ca9}, + {0x0cb4, 0x0cb4}, + {0x0cba, 0x0cbb}, + {0x0cc5, 0x0cc5}, + {0x0cc9, 0x0cc9}, + {0x0cce, 0x0cd4}, + {0x0cd7, 0x0cdd}, + {0x0cdf, 0x0cdf}, + {0x0ce4, 0x0ce5}, + {0x0cf0, 0x0cf0}, + {0x0cf3, 0x0cff}, + {0x0d0d, 0x0d0d}, + {0x0d11, 0x0d11}, + {0x0d45, 0x0d45}, + {0x0d49, 0x0d49}, + {0x0d50, 0x0d53}, + {0x0d64, 0x0d65}, + {0x0d80, 0x0d80}, + {0x0d84, 0x0d84}, + {0x0d97, 0x0d99}, + {0x0db2, 0x0db2}, + {0x0dbc, 0x0dbc}, + {0x0dbe, 0x0dbf}, + {0x0dc7, 0x0dc9}, + {0x0dcb, 0x0dce}, + {0x0dd5, 0x0dd5}, + {0x0dd7, 0x0dd7}, + {0x0de0, 0x0de5}, + {0x0df0, 0x0df1}, + {0x0df5, 0x0e00}, + {0x0e3b, 0x0e3e}, + {0x0e5c, 0x0e80}, + {0x0e83, 0x0e83}, + {0x0e85, 0x0e85}, + {0x0e8b, 0x0e8b}, + {0x0ea4, 0x0ea4}, + {0x0ea6, 0x0ea6}, + {0x0ebe, 0x0ebf}, + {0x0ec5, 0x0ec5}, + {0x0ec7, 0x0ec7}, + {0x0ece, 0x0ecf}, + {0x0eda, 0x0edb}, + {0x0ee0, 0x0eff}, + {0x0f48, 0x0f48}, + {0x0f6d, 0x0f70}, + {0x0f98, 0x0f98}, + {0x0fbd, 0x0fbd}, + {0x0fcd, 0x0fcd}, + {0x0fdb, 0x0fff}, + {0x10c6, 0x10c6}, + {0x10c8, 0x10cc}, + {0x10ce, 0x10cf}, + {0x1249, 0x1249}, + {0x124e, 0x124f}, + {0x1257, 0x1257}, + {0x1259, 0x1259}, + {0x125e, 0x125f}, + {0x1289, 0x1289}, + {0x128e, 0x128f}, + {0x12b1, 0x12b1}, + {0x12b6, 0x12b7}, + {0x12bf, 0x12bf}, + {0x12c1, 0x12c1}, + {0x12c6, 0x12c7}, + {0x12d7, 0x12d7}, + {0x1311, 0x1311}, + {0x1316, 0x1317}, + {0x135b, 0x135c}, + {0x137d, 0x137f}, + {0x139a, 0x139f}, + {0x13f6, 0x13f7}, + {0x13fe, 0x13ff}, + {0x169d, 0x169f}, + {0x16f9, 0x16ff}, + {0x170d, 0x170d}, + {0x1715, 0x171f}, + {0x1737, 0x173f}, + {0x1754, 0x175f}, + {0x176d, 0x176d}, + {0x1771, 0x1771}, + {0x1774, 0x177f}, + {0x17de, 0x17df}, + {0x17ea, 0x17ef}, + {0x17fa, 0x17ff}, + {0x180f, 0x180f}, + {0x181a, 0x181f}, + {0x1879, 0x187f}, + {0x18ab, 0x18af}, + {0x18f6, 0x18ff}, + {0x191f, 0x191f}, + {0x192c, 0x192f}, + {0x193c, 0x193f}, + {0x1941, 0x1943}, + {0x196e, 0x196f}, + {0x1975, 0x197f}, + {0x19ac, 0x19af}, + {0x19ca, 0x19cf}, + {0x19db, 0x19dd}, + {0x1a1c, 0x1a1d}, + {0x1a5f, 0x1a5f}, + {0x1a7d, 0x1a7e}, + {0x1a8a, 0x1a8f}, + {0x1a9a, 0x1a9f}, + {0x1aae, 0x1aaf}, + {0x1ac1, 0x1aff}, + {0x1b4c, 0x1b4f}, + {0x1b7d, 0x1b7f}, + {0x1bf4, 0x1bfb}, + {0x1c38, 0x1c3a}, + {0x1c4a, 0x1c4c}, + {0x1c89, 0x1c8f}, + {0x1cbb, 0x1cbc}, + {0x1cc8, 0x1ccf}, + {0x1cfb, 0x1cff}, + {0x1dfa, 0x1dfa}, + {0x1f16, 0x1f17}, + {0x1f1e, 0x1f1f}, + {0x1f46, 0x1f47}, + {0x1f4e, 0x1f4f}, + {0x1f58, 0x1f58}, + {0x1f5a, 0x1f5a}, + {0x1f5c, 0x1f5c}, + {0x1f5e, 0x1f5e}, + {0x1f7e, 0x1f7f}, + {0x1fb5, 0x1fb5}, + {0x1fc5, 0x1fc5}, + {0x1fd4, 0x1fd5}, + {0x1fdc, 0x1fdc}, + {0x1ff0, 0x1ff1}, + {0x1ff5, 0x1ff5}, + {0x1fff, 0x1fff}, + {0x2065, 0x2065}, + {0x2072, 0x2073}, + {0x208f, 0x208f}, + {0x209d, 0x209f}, + {0x20c0, 0x20cf}, + {0x20f1, 0x20ff}, + {0x218c, 0x218f}, + {0x2427, 0x243f}, + {0x244b, 0x245f}, + {0x2b74, 0x2b75}, + {0x2b96, 0x2b96}, + {0x2c2f, 0x2c2f}, + {0x2c5f, 0x2c5f}, + {0x2cf4, 0x2cf8}, + {0x2d26, 0x2d26}, + {0x2d28, 0x2d2c}, + {0x2d2e, 0x2d2f}, + {0x2d68, 0x2d6e}, + {0x2d71, 0x2d7e}, + {0x2d97, 0x2d9f}, + {0x2da7, 0x2da7}, + {0x2daf, 0x2daf}, + {0x2db7, 0x2db7}, + {0x2dbf, 0x2dbf}, + {0x2dc7, 0x2dc7}, + {0x2dcf, 0x2dcf}, + {0x2dd7, 0x2dd7}, + {0x2ddf, 0x2ddf}, + {0x2e53, 0x2e7f}, + {0x2e9a, 0x2e9a}, + {0x2ef4, 0x2eff}, + {0x2fd6, 0x2fef}, + {0x2ffc, 0x2fff}, + {0x3040, 0x3040}, + {0x3097, 0x3098}, + {0x3100, 0x3104}, + {0x3130, 0x3130}, + {0x318f, 0x318f}, + {0x31e4, 0x31ef}, + {0x321f, 0x321f}, + {0x9ffd, 0x9fff}, + {0xa48d, 0xa48f}, + {0xa4c7, 0xa4cf}, + {0xa62c, 0xa63f}, + {0xa6f8, 0xa6ff}, + {0xa7c0, 0xa7c1}, + {0xa7cb, 0xa7f4}, + {0xa82d, 0xa82f}, + {0xa83a, 0xa83f}, + {0xa878, 0xa87f}, + {0xa8c6, 0xa8cd}, + {0xa8da, 0xa8df}, + {0xa954, 0xa95e}, + {0xa97d, 0xa97f}, + {0xa9ce, 0xa9ce}, + {0xa9da, 0xa9dd}, + {0xa9ff, 0xa9ff}, + {0xaa37, 0xaa3f}, + {0xaa4e, 0xaa4f}, + {0xaa5a, 0xaa5b}, + {0xaac3, 0xaada}, + {0xaaf7, 0xab00}, + {0xab07, 0xab08}, + {0xab0f, 0xab10}, + {0xab17, 0xab1f}, + {0xab27, 0xab27}, + {0xab2f, 0xab2f}, + {0xab6c, 0xab6f}, + {0xabee, 0xabef}, + {0xabfa, 0xabff}, + {0xd7a4, 0xd7af}, + {0xd7c7, 0xd7ca}, + {0xd7fc, 0xf8ff}, + {0xfa6e, 0xfa6f}, + {0xfada, 0xfaff}, + {0xfb07, 0xfb12}, + {0xfb18, 0xfb1c}, + {0xfb37, 0xfb37}, + {0xfb3d, 0xfb3d}, + {0xfb3f, 0xfb3f}, + {0xfb42, 0xfb42}, + {0xfb45, 0xfb45}, + {0xfbc2, 0xfbd2}, + {0xfd40, 0xfd4f}, + {0xfd90, 0xfd91}, + {0xfdc8, 0xfdef}, + {0xfdfe, 0xfdff}, + {0xfe1a, 0xfe1f}, + {0xfe53, 0xfe53}, + {0xfe67, 0xfe67}, + {0xfe6c, 0xfe6f}, + {0xfe75, 0xfe75}, + {0xfefd, 0xfefe}, + {0xff00, 0xff00}, + {0xffbf, 0xffc1}, + {0xffc8, 0xffc9}, + {0xffd0, 0xffd1}, + {0xffd8, 0xffd9}, + {0xffdd, 0xffdf}, + {0xffe7, 0xffe7}, + {0xffef, 0xfff8}, + {0xfffe, 0xffff}, + {0x1000c, 0x1000c}, + {0x10027, 0x10027}, + {0x1003b, 0x1003b}, + {0x1003e, 0x1003e}, + {0x1004e, 0x1004f}, + {0x1005e, 0x1007f}, + {0x100fb, 0x100ff}, + {0x10103, 0x10106}, + {0x10134, 0x10136}, + {0x1018f, 0x1018f}, + {0x1019d, 0x1019f}, + {0x101a1, 0x101cf}, + {0x101fe, 0x1027f}, + {0x1029d, 0x1029f}, + {0x102d1, 0x102df}, + {0x102fc, 0x102ff}, + {0x10324, 0x1032c}, + {0x1034b, 0x1034f}, + {0x1037b, 0x1037f}, + {0x1039e, 0x1039e}, + {0x103c4, 0x103c7}, + {0x103d6, 0x103ff}, + {0x1049e, 0x1049f}, + {0x104aa, 0x104af}, + {0x104d4, 0x104d7}, + {0x104fc, 0x104ff}, + {0x10528, 0x1052f}, + {0x10564, 0x1056e}, + {0x10570, 0x105ff}, + {0x10737, 0x1073f}, + {0x10756, 0x1075f}, + {0x10768, 0x107ff}, + {0x10806, 0x10807}, + {0x10809, 0x10809}, + {0x10836, 0x10836}, + {0x10839, 0x1083b}, + {0x1083d, 0x1083e}, + {0x10856, 0x10856}, + {0x1089f, 0x108a6}, + {0x108b0, 0x108df}, + {0x108f3, 0x108f3}, + {0x108f6, 0x108fa}, + {0x1091c, 0x1091e}, + {0x1093a, 0x1093e}, + {0x10940, 0x1097f}, + {0x109b8, 0x109bb}, + {0x109d0, 0x109d1}, + {0x10a04, 0x10a04}, + {0x10a07, 0x10a0b}, + {0x10a14, 0x10a14}, + {0x10a18, 0x10a18}, + {0x10a36, 0x10a37}, + {0x10a3b, 0x10a3e}, + {0x10a49, 0x10a4f}, + {0x10a59, 0x10a5f}, + {0x10aa0, 0x10abf}, + {0x10ae7, 0x10aea}, + {0x10af7, 0x10aff}, + {0x10b36, 0x10b38}, + {0x10b56, 0x10b57}, + {0x10b73, 0x10b77}, + {0x10b92, 0x10b98}, + {0x10b9d, 0x10ba8}, + {0x10bb0, 0x10bff}, + {0x10c49, 0x10c7f}, + {0x10cb3, 0x10cbf}, + {0x10cf3, 0x10cf9}, + {0x10d28, 0x10d2f}, + {0x10d3a, 0x10e5f}, + {0x10e7f, 0x10e7f}, + {0x10eaa, 0x10eaa}, + {0x10eae, 0x10eaf}, + {0x10eb2, 0x10eff}, + {0x10f28, 0x10f2f}, + {0x10f5a, 0x10faf}, + {0x10fcc, 0x10fdf}, + {0x10ff7, 0x10fff}, + {0x1104e, 0x11051}, + {0x11070, 0x1107e}, + {0x110c2, 0x110cc}, + {0x110ce, 0x110cf}, + {0x110e9, 0x110ef}, + {0x110fa, 0x110ff}, + {0x11135, 0x11135}, + {0x11148, 0x1114f}, + {0x11177, 0x1117f}, + {0x111e0, 0x111e0}, + {0x111f5, 0x111ff}, + {0x11212, 0x11212}, + {0x1123f, 0x1127f}, + {0x11287, 0x11287}, + {0x11289, 0x11289}, + {0x1128e, 0x1128e}, + {0x1129e, 0x1129e}, + {0x112aa, 0x112af}, + {0x112eb, 0x112ef}, + {0x112fa, 0x112ff}, + {0x11304, 0x11304}, + {0x1130d, 0x1130e}, + {0x11311, 0x11312}, + {0x11329, 0x11329}, + {0x11331, 0x11331}, + {0x11334, 0x11334}, + {0x1133a, 0x1133a}, + {0x11345, 0x11346}, + {0x11349, 0x1134a}, + {0x1134e, 0x1134f}, + {0x11351, 0x11356}, + {0x11358, 0x1135c}, + {0x11364, 0x11365}, + {0x1136d, 0x1136f}, + {0x11375, 0x113ff}, + {0x1145c, 0x1145c}, + {0x11462, 0x1147f}, + {0x114c8, 0x114cf}, + {0x114da, 0x1157f}, + {0x115b6, 0x115b7}, + {0x115de, 0x115ff}, + {0x11645, 0x1164f}, + {0x1165a, 0x1165f}, + {0x1166d, 0x1167f}, + {0x116b9, 0x116bf}, + {0x116ca, 0x116ff}, + {0x1171b, 0x1171c}, + {0x1172c, 0x1172f}, + {0x11740, 0x117ff}, + {0x1183c, 0x1189f}, + {0x118f3, 0x118fe}, + {0x11907, 0x11908}, + {0x1190a, 0x1190b}, + {0x11914, 0x11914}, + {0x11917, 0x11917}, + {0x11936, 0x11936}, + {0x11939, 0x1193a}, + {0x11947, 0x1194f}, + {0x1195a, 0x1199f}, + {0x119a8, 0x119a9}, + {0x119d8, 0x119d9}, + {0x119e5, 0x119ff}, + {0x11a48, 0x11a4f}, + {0x11aa3, 0x11abf}, + {0x11af9, 0x11bff}, + {0x11c09, 0x11c09}, + {0x11c37, 0x11c37}, + {0x11c46, 0x11c4f}, + {0x11c6d, 0x11c6f}, + {0x11c90, 0x11c91}, + {0x11ca8, 0x11ca8}, + {0x11cb7, 0x11cff}, + {0x11d07, 0x11d07}, + {0x11d0a, 0x11d0a}, + {0x11d37, 0x11d39}, + {0x11d3b, 0x11d3b}, + {0x11d3e, 0x11d3e}, + {0x11d48, 0x11d4f}, + {0x11d5a, 0x11d5f}, + {0x11d66, 0x11d66}, + {0x11d69, 0x11d69}, + {0x11d8f, 0x11d8f}, + {0x11d92, 0x11d92}, + {0x11d99, 0x11d9f}, + {0x11daa, 0x11edf}, + {0x11ef9, 0x11faf}, + {0x11fb1, 0x11fbf}, + {0x11ff2, 0x11ffe}, + {0x1239a, 0x123ff}, + {0x1246f, 0x1246f}, + {0x12475, 0x1247f}, + {0x12544, 0x12fff}, + {0x1342f, 0x1342f}, + {0x13439, 0x143ff}, + {0x14647, 0x167ff}, + {0x16a39, 0x16a3f}, + {0x16a5f, 0x16a5f}, + {0x16a6a, 0x16a6d}, + {0x16a70, 0x16acf}, + {0x16aee, 0x16aef}, + {0x16af6, 0x16aff}, + {0x16b46, 0x16b4f}, + {0x16b5a, 0x16b5a}, + {0x16b62, 0x16b62}, + {0x16b78, 0x16b7c}, + {0x16b90, 0x16e3f}, + {0x16e9b, 0x16eff}, + {0x16f4b, 0x16f4e}, + {0x16f88, 0x16f8e}, + {0x16fa0, 0x16fdf}, + {0x16fe5, 0x16fef}, + {0x16ff2, 0x16fff}, + {0x187f8, 0x187ff}, + {0x18cd6, 0x18cff}, + {0x18d09, 0x1afff}, + {0x1b11f, 0x1b14f}, + {0x1b153, 0x1b163}, + {0x1b168, 0x1b16f}, + {0x1b2fc, 0x1bbff}, + {0x1bc6b, 0x1bc6f}, + {0x1bc7d, 0x1bc7f}, + {0x1bc89, 0x1bc8f}, + {0x1bc9a, 0x1bc9b}, + {0x1bca4, 0x1cfff}, + {0x1d0f6, 0x1d0ff}, + {0x1d127, 0x1d128}, + {0x1d1e9, 0x1d1ff}, + {0x1d246, 0x1d2df}, + {0x1d2f4, 0x1d2ff}, + {0x1d357, 0x1d35f}, + {0x1d379, 0x1d3ff}, + {0x1d455, 0x1d455}, + {0x1d49d, 0x1d49d}, + {0x1d4a0, 0x1d4a1}, + {0x1d4a3, 0x1d4a4}, + {0x1d4a7, 0x1d4a8}, + {0x1d4ad, 0x1d4ad}, + {0x1d4ba, 0x1d4ba}, + {0x1d4bc, 0x1d4bc}, + {0x1d4c4, 0x1d4c4}, + {0x1d506, 0x1d506}, + {0x1d50b, 0x1d50c}, + {0x1d515, 0x1d515}, + {0x1d51d, 0x1d51d}, + {0x1d53a, 0x1d53a}, + {0x1d53f, 0x1d53f}, + {0x1d545, 0x1d545}, + {0x1d547, 0x1d549}, + {0x1d551, 0x1d551}, + {0x1d6a6, 0x1d6a7}, + {0x1d7cc, 0x1d7cd}, + {0x1da8c, 0x1da9a}, + {0x1daa0, 0x1daa0}, + {0x1dab0, 0x1dfff}, + {0x1e007, 0x1e007}, + {0x1e019, 0x1e01a}, + {0x1e022, 0x1e022}, + {0x1e025, 0x1e025}, + {0x1e02b, 0x1e0ff}, + {0x1e12d, 0x1e12f}, + {0x1e13e, 0x1e13f}, + {0x1e14a, 0x1e14d}, + {0x1e150, 0x1e2bf}, + {0x1e2fa, 0x1e2fe}, + {0x1e300, 0x1e7ff}, + {0x1e8c5, 0x1e8c6}, + {0x1e8d7, 0x1e8ff}, + {0x1e94c, 0x1e94f}, + {0x1e95a, 0x1e95d}, + {0x1e960, 0x1ec70}, + {0x1ecb5, 0x1ed00}, + {0x1ed3e, 0x1edff}, + {0x1ee04, 0x1ee04}, + {0x1ee20, 0x1ee20}, + {0x1ee23, 0x1ee23}, + {0x1ee25, 0x1ee26}, + {0x1ee28, 0x1ee28}, + {0x1ee33, 0x1ee33}, + {0x1ee38, 0x1ee38}, + {0x1ee3a, 0x1ee3a}, + {0x1ee3c, 0x1ee41}, + {0x1ee43, 0x1ee46}, + {0x1ee48, 0x1ee48}, + {0x1ee4a, 0x1ee4a}, + {0x1ee4c, 0x1ee4c}, + {0x1ee50, 0x1ee50}, + {0x1ee53, 0x1ee53}, + {0x1ee55, 0x1ee56}, + {0x1ee58, 0x1ee58}, + {0x1ee5a, 0x1ee5a}, + {0x1ee5c, 0x1ee5c}, + {0x1ee5e, 0x1ee5e}, + {0x1ee60, 0x1ee60}, + {0x1ee63, 0x1ee63}, + {0x1ee65, 0x1ee66}, + {0x1ee6b, 0x1ee6b}, + {0x1ee73, 0x1ee73}, + {0x1ee78, 0x1ee78}, + {0x1ee7d, 0x1ee7d}, + {0x1ee7f, 0x1ee7f}, + {0x1ee8a, 0x1ee8a}, + {0x1ee9c, 0x1eea0}, + {0x1eea4, 0x1eea4}, + {0x1eeaa, 0x1eeaa}, + {0x1eebc, 0x1eeef}, + {0x1eef2, 0x1efff}, + {0x1f02c, 0x1f02f}, + {0x1f094, 0x1f09f}, + {0x1f0af, 0x1f0b0}, + {0x1f0c0, 0x1f0c0}, + {0x1f0d0, 0x1f0d0}, + {0x1f0f6, 0x1f0ff}, + {0x1f1ae, 0x1f1e5}, + {0x1f203, 0x1f20f}, + {0x1f23c, 0x1f23f}, + {0x1f249, 0x1f24f}, + {0x1f252, 0x1f25f}, + {0x1f266, 0x1f2ff}, + {0x1f6d8, 0x1f6df}, + {0x1f6ed, 0x1f6ef}, + {0x1f6fd, 0x1f6ff}, + {0x1f774, 0x1f77f}, + {0x1f7d9, 0x1f7df}, + {0x1f7ec, 0x1f7ff}, + {0x1f80c, 0x1f80f}, + {0x1f848, 0x1f84f}, + {0x1f85a, 0x1f85f}, + {0x1f888, 0x1f88f}, + {0x1f8ae, 0x1f8af}, + {0x1f8b2, 0x1f8ff}, + {0x1f979, 0x1f979}, + {0x1f9cc, 0x1f9cc}, + {0x1fa54, 0x1fa5f}, + {0x1fa6e, 0x1fa6f}, + {0x1fa75, 0x1fa77}, + {0x1fa7b, 0x1fa7f}, + {0x1fa87, 0x1fa8f}, + {0x1faa9, 0x1faaf}, + {0x1fab7, 0x1fabf}, + {0x1fac3, 0x1facf}, + {0x1fad7, 0x1faff}, + {0x1fb93, 0x1fb93}, + {0x1fbcb, 0x1fbef}, + {0x1fbfa, 0x1ffff}, + {0x2a6de, 0x2a6ff}, + {0x2b735, 0x2b73f}, + {0x2b81e, 0x2b81f}, + {0x2cea2, 0x2ceaf}, + {0x2ebe1, 0x2f7ff}, + {0x2fa1e, 0x2ffff}, + {0x3134b, 0xe0000}, + {0xe0002, 0xe001f}, + {0xe0080, 0xe00ff}, + {0xe01f0, 0x10ffff}, +}; + diff -Nru dte-1.9.1/src/util/unidata.lua dte-1.10/src/util/unidata.lua --- dte-1.9.1/src/util/unidata.lua 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/unidata.lua 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,166 @@ +local progname = arg[0] +local RangeTable = {} +RangeTable.__index = RangeTable + +function RangeTable.new() + return setmetatable({n = 0}, RangeTable) +end + +function RangeTable:insert(min, max) + local n = self.n + 1 + self[n] = {min = assert(min), max = assert(max)} + self.n = n +end + +function RangeTable:remove(index) + table.remove(self, index) + self.n = self.n - 1 +end + +function RangeTable:contains(codepoint) + for i = 1, self.n do + local range = self[i] + if codepoint >= range.min and codepoint <= range.max then + return true + end + end + return false +end + +function RangeTable:merge_adjacent() + for i = self.n, 1, -1 do + local cur, prev = self[i], self[i - 1] + if prev and 1 + prev.max == cur.min then + prev.max = cur.max + self:remove(i) + end + end + return self +end + +function RangeTable:print_ranges(name, output) + local n = assert(self.n) + output:write("static const CodepointRange ", name, "[] = {\n") + for i = 1, n do + local min = assert(self[i].min) + local max = assert(self[i].max) + output:write((" {0x%04x, 0x%04x},\n"):format(min, max)) + end + output:write("};\n\n") +end + +local function read_ucd(path, pattern) + assert(pattern) + if not path then + io.stderr:write ( + "Usage: ", progname, " UnicodeData.txt EastAsianWidth.txt\n", + "(available from: https://unicode.org/Public/11.0.0/ucd/)\n" + ) + os.exit(1) + end + local file = assert(io.open(path, "r")) + local text = assert(file:read("*a")) + file:close() + assert(text:find(pattern) == 1, "Unrecognized input format") + return text +end + +local unidata = read_ucd(arg[1], "^0000;") +local eaw = read_ucd(arg[2], "^# EastAsianWidth") +local dcp = read_ucd(arg[3], "^# DerivedCoreProperties") + +local nonspacing_mark = RangeTable.new() +local special_whitespace = RangeTable.new() +local unprintable = RangeTable.new() +local default_ignorable = RangeTable.new() +local double_width = RangeTable.new() +local exclude = RangeTable.new() + +local mappings = { + Zs = special_whitespace, -- Space_Separator (various non-zero width spaces) + Zl = special_whitespace, -- Line_Separator (U+2028 only) + Zp = special_whitespace, -- Paragraph_Separator (U+2029 only) + Cc = unprintable, -- Control + Cs = unprintable, -- Surrogate + Co = unprintable, -- Private_Use + Cn = unprintable, -- Unassigned + Me = nonspacing_mark, -- Enclosing_Mark + Mn = nonspacing_mark, -- Nonspacing_Mark + + -- This is a "default ignorable" character in the "Cf" (Format) + -- category, but it's included in special_whitespace because it + -- looks exactly like a normal space when using a monospace font: + [0x00AD] = special_whitespace, -- Soft hyphen + + -- These are "Zs" characters, but don't need to be in special_whitespace + -- because they can easily be distinguished from a normal space: + [0x1680] = exclude, -- Ogham space mark (looks like an em dash) + [0x3000] = exclude, -- Ideographic space (double width space) +} + +-- ASCII +for u = 0x00, 0x7F do + mappings[u] = exclude +end + +for min, max, property in dcp:gmatch "\n(%x+)%.*(%x*) *; *([%w_]+)" do + if property == "Default_Ignorable_Code_Point" then + min = assert(tonumber(min, 16)) + max = tonumber(max, 16) + if not mappings[min] then + default_ignorable:insert(min, max or min) + end + end +end + +local prev_codepoint = -1 +local range = false +for codepoint, name, category in unidata:gmatch "(%x+);([^;]*);(%u%a);[^\n]*\n" do + codepoint = assert(tonumber(codepoint, 16)) + assert(codepoint > prev_codepoint) + assert(name) + assert(category) + + -- Omitted codepoints default to the "Cn" (unassigned) category + -- https://www.unicode.org/reports/tr44/tr44-21.html#Default_Values_Table + if not range and prev_codepoint < codepoint - 1 then + unprintable:insert(prev_codepoint + 1, codepoint - 1) + end + + local t = mappings[codepoint] or mappings[category] + + if t == unprintable and default_ignorable:contains(codepoint) then + -- Don't add codepoints to the unprintable table if they've + -- already been added to the default_ignorable table + elseif range then + assert(name:match("^<.*, Last>$")) + range = false + if t then + t:insert(prev_codepoint, codepoint) + end + elseif name:match("^<.*, First>$") then + range = true + elseif t then + t:insert(codepoint, codepoint) + end + + prev_codepoint = codepoint +end + +assert(prev_codepoint == 0x10FFFD) +assert(exclude.n == 127 + 3) +unprintable:insert(prev_codepoint + 1, 0x10FFFF) + +for min, max in eaw:gmatch "\n(%x+)%.*(%x*);[WF]" do + min = assert(tonumber(min, 16)) + max = tonumber(max, 16) + double_width:insert(min, max or min) +end + +local stdout = io.stdout +stdout:write("typedef struct {CodePoint first, last;} CodepointRange;\n\n") +special_whitespace:merge_adjacent():print_ranges("special_whitespace", stdout) +default_ignorable:merge_adjacent():print_ranges("default_ignorable", stdout) +nonspacing_mark:merge_adjacent():print_ranges("nonspacing_mark", stdout) +double_width:merge_adjacent():print_ranges("double_width", stdout) +unprintable:merge_adjacent():print_ranges("unprintable", stdout) diff -Nru dte-1.9.1/src/util/utf8.c dte-1.10/src/util/utf8.c --- dte-1.9.1/src/util/utf8.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/utf8.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,30 +1,39 @@ #include +#include #include "utf8.h" #include "ascii.h" +#include "debug.h" -static int u_seq_len(unsigned int first_byte) -{ - if (first_byte < 0x80) { - return 1; - } - if (first_byte < 0xc0) { - return 0; - } - if (first_byte < 0xe0) { - return 2; - } - if (first_byte < 0xf0) { - return 3; - } +enum { + I = -1, // Invalid byte + C = 0, // Continuation byte +}; + +static const int8_t seq_len_table[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 00..0F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10..1F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 20..2F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 30..3F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40..4F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 50..5F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60..6F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 70..7F + C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // 80..8F + C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // 90..9F + C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // A0..AF + C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, C, // B0..BF + I, I, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..CF + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D0..DF + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E0..EF + 4, 4, 4, 4, 4, I, I, I, I, I, I, I, I, I, I, I // F0..FF +}; - // Could be 0xf8 but RFC 3629 doesn't allow codepoints above 0x10ffff - if (first_byte < 0xf5) { - return 4; - } - return -1; +static int u_seq_len(unsigned char first_byte) +{ + return seq_len_table[first_byte]; } -static bool u_is_continuation(CodePoint u) +static bool u_is_continuation_byte(unsigned char u) { return (u & 0xc0) == 0x80; } @@ -46,7 +55,7 @@ */ static unsigned int u_get_first_byte_mask(unsigned int len) { - return (1U << 7U >> len) - 1; + return (0x80 >> len) - 1; } size_t u_str_width(const unsigned char *str) @@ -61,26 +70,20 @@ CodePoint u_prev_char(const unsigned char *buf, size_t *idx) { size_t i = *idx; - unsigned int count, shift; - CodePoint u; - - u = buf[--i]; - if (u < 0x80) { + unsigned char ch = buf[--i]; + if (likely(ch < 0x80)) { *idx = i; - return u; + return (CodePoint)ch; } - if (!u_is_continuation(u)) { + if (!u_is_continuation_byte(ch)) { goto invalid; } - u &= 0x3f; - count = 1; - shift = 6; - while (i) { - unsigned int ch = buf[--i]; + CodePoint u = ch & 0x3f; + for (unsigned int count = 1, shift = 6; i > 0; ) { + ch = buf[--i]; unsigned int len = u_seq_len(ch); - count++; if (len == 0) { if (count == 4) { @@ -97,11 +100,11 @@ if (!u_seq_len_ok(u, len)) { break; } - *idx = i; return u; } } + invalid: *idx = *idx - 1; u = buf[*idx]; @@ -112,7 +115,7 @@ { size_t i = *idx; CodePoint u = str[i]; - if (u < 0x80) { + if (likely(u < 0x80)) { *idx = i + 1; return u; } @@ -123,7 +126,7 @@ { size_t i = *idx; CodePoint u = buf[i]; - if (u < 0x80) { + if (likely(u < 0x80)) { *idx = i + 1; return u; } @@ -133,20 +136,17 @@ CodePoint u_get_nonascii(const unsigned char *buf, size_t size, size_t *idx) { size_t i = *idx; - int len, c; - unsigned int first, u; - - first = buf[i++]; - len = u_seq_len(first); + unsigned int first = buf[i++]; + int len = u_seq_len(first); if (unlikely(len < 2 || len > size - i + 1)) { goto invalid; } - u = first & u_get_first_byte_mask(len); - c = len - 1; + CodePoint u = first & u_get_first_byte_mask(len); + int c = len - 1; do { - CodePoint ch = buf[i++]; - if (!u_is_continuation(ch)) { + unsigned char ch = buf[i++]; + if (!u_is_continuation_byte(ch)) { goto invalid; } u = (u << 6) | (ch & 0x3f); @@ -165,61 +165,49 @@ void u_set_char_raw(char *str, size_t *idx, CodePoint u) { - size_t i = *idx; - if (u <= 0x7f) { - str[i++] = u; - } else if (u <= 0x7ff) { - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xc0; - i += 2; - } else if (u <= 0xffff) { - str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xe0; - i += 3; - } else if (u <= 0x10ffff) { - str[i + 3] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xf0; - i += 4; - } else { - // Invalid byte value - str[i++] = u & 0xff; + static const uint8_t prefixes[] = {0, 0, 0xC0, 0xE0, 0xF0}; + const size_t len = u_char_size(u); + const uint8_t first_byte_prefix = prefixes[len]; + const size_t i = *idx; + + switch (len) { + case 4: + str[i + 3] = (u & 0x3F) | 0x80; + u >>= 6; + // Fallthrough + case 3: + str[i + 2] = (u & 0x3F) | 0x80; + u >>= 6; + // Fallthrough + case 2: + str[i + 1] = (u & 0x3F) | 0x80; + u >>= 6; + // Fallthrough + case 1: + str[i] = (u & 0xFF) | first_byte_prefix; + *idx = i + len; + break; + default: + BUG("unexpected UTF-8 sequence length: %zu", len); } - *idx = i; } void u_set_char(char *str, size_t *idx, CodePoint u) { size_t i = *idx; - if (u < 0x80) { - if (ascii_iscntrl(u)) { - u_set_ctrl(str, idx, u); - } else { - str[i++] = u; - *idx = i; + if (likely(u <= 0x7F)) { + if (unlikely(ascii_iscntrl(u))) { + // Use caret notation for control chars: + str[i++] = '^'; + u = (u + 64) & 0x7F; } + str[i++] = u; + *idx = i; } else if (u_is_unprintable(u)) { u_set_hex(str, idx, u); - } else if (u <= 0x7ff) { - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xc0; - i += 2; - *idx = i; - } else if (u <= 0xffff) { - str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xe0; - i += 3; - *idx = i; - } else if (u <= 0x10ffff) { - str[i + 3] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 2] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 1] = (u & 0x3f) | 0x80; u >>= 6; - str[i + 0] = u | 0xf0; - i += 4; - *idx = i; + } else { + BUG_ON(u > 0x10FFFF); // (implied by !u_is_unprintable(u)) + u_set_char_raw(str, idx, u); } } @@ -245,7 +233,6 @@ { int w = *width; size_t idx = 0; - while (str[idx] && w > 0) { w -= u_char_width(u_str_get_char(str, &idx)); } @@ -255,40 +242,3 @@ *width -= w; return idx; } - -static bool has_prefix(const char *str, const char *prefix_lcase) -{ - size_t ni = 0; - size_t hi = 0; - CodePoint pc; - while ((pc = u_str_get_char(prefix_lcase, &ni))) { - CodePoint sc = u_str_get_char(str, &hi); - if (sc != pc && u_to_lower(sc) != pc) { - return false; - } - } - return true; -} - -ssize_t u_str_index(const char *haystack, const char *needle_lcase) -{ - size_t hi = 0; - size_t ni = 0; - CodePoint nc = u_str_get_char(needle_lcase, &ni); - - if (!nc) { - return 0; - } - - while (haystack[hi]) { - size_t prev = hi; - CodePoint hc = u_str_get_char(haystack, &hi); - if ( - (hc == nc || u_to_lower(hc) == nc) - && has_prefix(haystack + hi, needle_lcase + ni) - ) { - return prev; - } - } - return -1; -} diff -Nru dte-1.9.1/src/util/utf8.h dte-1.10/src/util/utf8.h --- dte-1.9.1/src/util/utf8.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/utf8.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,34 +1,26 @@ -#ifndef UTIL_UCHAR_H -#define UTIL_UCHAR_H +#ifndef UTIL_UTF8_H +#define UTIL_UTF8_H -#include +#include +#include "macros.h" #include "unicode.h" static inline size_t u_char_size(CodePoint u) { - if (u <= UINT32_C(0x7f)) { + if (likely(u <= 0x7f)) { return 1; - } else if (u <= UINT32_C(0x7ff)) { + } else if (u <= 0x7ff) { return 2; - } else if (u <= UINT32_C(0xffff)) { + } else if (u <= 0xffff) { return 3; - } else if (u <= UINT32_C(0x10ffff)) { + } else if (u <= UNICODE_MAX_VALID_CODEPOINT) { return 4; } - // Invalid byte in UTF-8 byte sequence. + // Invalid byte in UTF-8 byte sequence return 1; } -NONNULL_ARGS -static inline void u_set_ctrl(char *buf, size_t *idx, CodePoint u) -{ - size_t i = *idx; - buf[i++] = '^'; - buf[i++] = (u == 0x7F) ? '?' : u | 0x40; - *idx = i; -} - size_t u_str_width(const unsigned char *str); CodePoint u_prev_char(const unsigned char *buf, size_t *idx); @@ -51,6 +43,4 @@ */ size_t u_skip_chars(const char *str, int *width); -ssize_t u_str_index(const char *haystack, const char *needle_lcase); - #endif diff -Nru dte-1.9.1/src/util/wbuf.c dte-1.10/src/util/wbuf.c --- dte-1.9.1/src/util/wbuf.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/wbuf.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -#include -#include "macros.h" -#include "wbuf.h" -#include "xreadwrite.h" - -#define XFLUSH(buf) do { \ - ssize_t rc_ = wbuf_flush(buf); \ - if (unlikely(rc_ < 0)) { \ - return rc_; \ - } \ -} while (0) - -NONNULL_ARGS -static size_t wbuf_avail(WriteBuffer *wbuf) -{ - return sizeof(wbuf->buf) - wbuf->fill; -} - -ssize_t wbuf_flush(WriteBuffer *wbuf) -{ - if (wbuf->fill) { - ssize_t rc = xwrite(wbuf->fd, wbuf->buf, wbuf->fill); - if (unlikely(rc < 0)) { - return rc; - } - wbuf->fill = 0; - } - return 0; -} - -void wbuf_need_space(WriteBuffer *wbuf, size_t count) -{ - if (wbuf_avail(wbuf) < count) { - wbuf_flush(wbuf); - } -} - -ssize_t wbuf_write(WriteBuffer *wbuf, const char *buf, size_t count) -{ - if (count > wbuf_avail(wbuf)) { - XFLUSH(wbuf); - if (unlikely(count >= sizeof(wbuf->buf))) { - ssize_t rc = xwrite(wbuf->fd, buf, count); - if (unlikely(rc < 0)) { - return rc; - } - return 0; - } - } - memcpy(wbuf->buf + wbuf->fill, buf, count); - wbuf->fill += count; - return 0; -} - -ssize_t wbuf_write_str(WriteBuffer *wbuf, const char *str) -{ - return wbuf_write(wbuf, str, strlen(str)); -} - -ssize_t wbuf_write_ch(WriteBuffer *wbuf, char ch) -{ - if (wbuf_avail(wbuf) == 0) { - XFLUSH(wbuf); - } - wbuf->buf[wbuf->fill++] = ch; - return 0; -} diff -Nru dte-1.9.1/src/util/wbuf.h dte-1.10/src/util/wbuf.h --- dte-1.9.1/src/util/wbuf.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/wbuf.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,24 +0,0 @@ -#ifndef UTIL_WBUF_H -#define UTIL_WBUF_H - -#include -#include "macros.h" - -typedef struct { - size_t fill; - int fd; - char buf[8192]; -} WriteBuffer; - -#define WBUF_INIT { \ - .fill = 0, \ - .fd = -1 \ -} - -ssize_t wbuf_flush(WriteBuffer *wbuf) NONNULL_ARGS; -ssize_t wbuf_write(WriteBuffer *wbuf, const char *buf, size_t count) NONNULL_ARGS; -ssize_t wbuf_write_str(WriteBuffer *wbuf, const char *str) NONNULL_ARGS; -ssize_t wbuf_write_ch(WriteBuffer *wbuf, char ch) NONNULL_ARGS; -void wbuf_need_space(WriteBuffer *wbuf, size_t count) NONNULL_ARGS; - -#endif diff -Nru dte-1.9.1/src/util/wcwidth.c dte-1.10/src/util/wcwidth.c --- dte-1.9.1/src/util/wcwidth.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/wcwidth.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,1131 +0,0 @@ -static const CodepointRange special_whitespace[] = { - {0x00a0, 0x00a0}, - {0x00ad, 0x00ad}, - {0x2000, 0x200a}, - {0x2028, 0x2029}, - {0x202f, 0x202f}, - {0x205f, 0x205f}, -}; - -static const CodepointRange default_ignorable[] = { - {0x034f, 0x034f}, - {0x061c, 0x061c}, - {0x115f, 0x1160}, - {0x17b4, 0x17b5}, - {0x180b, 0x180e}, - {0x200b, 0x200f}, - {0x202a, 0x202e}, - {0x2060, 0x206f}, - {0x3164, 0x3164}, - {0xfe00, 0xfe0f}, - {0xfeff, 0xfeff}, - {0xffa0, 0xffa0}, - {0xfff0, 0xfff8}, - {0x1bca0, 0x1bca3}, - {0x1d173, 0x1d17a}, - {0xe0000, 0xe0fff}, -}; - -static const CodepointRange nonspacing_mark[] = { - {0x0300, 0x036f}, - {0x0483, 0x0489}, - {0x0591, 0x05bd}, - {0x05bf, 0x05bf}, - {0x05c1, 0x05c2}, - {0x05c4, 0x05c5}, - {0x05c7, 0x05c7}, - {0x0610, 0x061a}, - {0x064b, 0x065f}, - {0x0670, 0x0670}, - {0x06d6, 0x06dc}, - {0x06df, 0x06e4}, - {0x06e7, 0x06e8}, - {0x06ea, 0x06ed}, - {0x0711, 0x0711}, - {0x0730, 0x074a}, - {0x07a6, 0x07b0}, - {0x07eb, 0x07f3}, - {0x07fd, 0x07fd}, - {0x0816, 0x0819}, - {0x081b, 0x0823}, - {0x0825, 0x0827}, - {0x0829, 0x082d}, - {0x0859, 0x085b}, - {0x08d3, 0x08e1}, - {0x08e3, 0x0902}, - {0x093a, 0x093a}, - {0x093c, 0x093c}, - {0x0941, 0x0948}, - {0x094d, 0x094d}, - {0x0951, 0x0957}, - {0x0962, 0x0963}, - {0x0981, 0x0981}, - {0x09bc, 0x09bc}, - {0x09c1, 0x09c4}, - {0x09cd, 0x09cd}, - {0x09e2, 0x09e3}, - {0x09fe, 0x09fe}, - {0x0a01, 0x0a02}, - {0x0a3c, 0x0a3c}, - {0x0a41, 0x0a42}, - {0x0a47, 0x0a48}, - {0x0a4b, 0x0a4d}, - {0x0a51, 0x0a51}, - {0x0a70, 0x0a71}, - {0x0a75, 0x0a75}, - {0x0a81, 0x0a82}, - {0x0abc, 0x0abc}, - {0x0ac1, 0x0ac5}, - {0x0ac7, 0x0ac8}, - {0x0acd, 0x0acd}, - {0x0ae2, 0x0ae3}, - {0x0afa, 0x0aff}, - {0x0b01, 0x0b01}, - {0x0b3c, 0x0b3c}, - {0x0b3f, 0x0b3f}, - {0x0b41, 0x0b44}, - {0x0b4d, 0x0b4d}, - {0x0b56, 0x0b56}, - {0x0b62, 0x0b63}, - {0x0b82, 0x0b82}, - {0x0bc0, 0x0bc0}, - {0x0bcd, 0x0bcd}, - {0x0c00, 0x0c00}, - {0x0c04, 0x0c04}, - {0x0c3e, 0x0c40}, - {0x0c46, 0x0c48}, - {0x0c4a, 0x0c4d}, - {0x0c55, 0x0c56}, - {0x0c62, 0x0c63}, - {0x0c81, 0x0c81}, - {0x0cbc, 0x0cbc}, - {0x0cbf, 0x0cbf}, - {0x0cc6, 0x0cc6}, - {0x0ccc, 0x0ccd}, - {0x0ce2, 0x0ce3}, - {0x0d00, 0x0d01}, - {0x0d3b, 0x0d3c}, - {0x0d41, 0x0d44}, - {0x0d4d, 0x0d4d}, - {0x0d62, 0x0d63}, - {0x0dca, 0x0dca}, - {0x0dd2, 0x0dd4}, - {0x0dd6, 0x0dd6}, - {0x0e31, 0x0e31}, - {0x0e34, 0x0e3a}, - {0x0e47, 0x0e4e}, - {0x0eb1, 0x0eb1}, - {0x0eb4, 0x0ebc}, - {0x0ec8, 0x0ecd}, - {0x0f18, 0x0f19}, - {0x0f35, 0x0f35}, - {0x0f37, 0x0f37}, - {0x0f39, 0x0f39}, - {0x0f71, 0x0f7e}, - {0x0f80, 0x0f84}, - {0x0f86, 0x0f87}, - {0x0f8d, 0x0f97}, - {0x0f99, 0x0fbc}, - {0x0fc6, 0x0fc6}, - {0x102d, 0x1030}, - {0x1032, 0x1037}, - {0x1039, 0x103a}, - {0x103d, 0x103e}, - {0x1058, 0x1059}, - {0x105e, 0x1060}, - {0x1071, 0x1074}, - {0x1082, 0x1082}, - {0x1085, 0x1086}, - {0x108d, 0x108d}, - {0x109d, 0x109d}, - {0x135d, 0x135f}, - {0x1712, 0x1714}, - {0x1732, 0x1734}, - {0x1752, 0x1753}, - {0x1772, 0x1773}, - {0x17b4, 0x17b5}, - {0x17b7, 0x17bd}, - {0x17c6, 0x17c6}, - {0x17c9, 0x17d3}, - {0x17dd, 0x17dd}, - {0x180b, 0x180d}, - {0x1885, 0x1886}, - {0x18a9, 0x18a9}, - {0x1920, 0x1922}, - {0x1927, 0x1928}, - {0x1932, 0x1932}, - {0x1939, 0x193b}, - {0x1a17, 0x1a18}, - {0x1a1b, 0x1a1b}, - {0x1a56, 0x1a56}, - {0x1a58, 0x1a5e}, - {0x1a60, 0x1a60}, - {0x1a62, 0x1a62}, - {0x1a65, 0x1a6c}, - {0x1a73, 0x1a7c}, - {0x1a7f, 0x1a7f}, - {0x1ab0, 0x1abe}, - {0x1b00, 0x1b03}, - {0x1b34, 0x1b34}, - {0x1b36, 0x1b3a}, - {0x1b3c, 0x1b3c}, - {0x1b42, 0x1b42}, - {0x1b6b, 0x1b73}, - {0x1b80, 0x1b81}, - {0x1ba2, 0x1ba5}, - {0x1ba8, 0x1ba9}, - {0x1bab, 0x1bad}, - {0x1be6, 0x1be6}, - {0x1be8, 0x1be9}, - {0x1bed, 0x1bed}, - {0x1bef, 0x1bf1}, - {0x1c2c, 0x1c33}, - {0x1c36, 0x1c37}, - {0x1cd0, 0x1cd2}, - {0x1cd4, 0x1ce0}, - {0x1ce2, 0x1ce8}, - {0x1ced, 0x1ced}, - {0x1cf4, 0x1cf4}, - {0x1cf8, 0x1cf9}, - {0x1dc0, 0x1df9}, - {0x1dfb, 0x1dff}, - {0x20d0, 0x20f0}, - {0x2cef, 0x2cf1}, - {0x2d7f, 0x2d7f}, - {0x2de0, 0x2dff}, - {0x302a, 0x302d}, - {0x3099, 0x309a}, - {0xa66f, 0xa672}, - {0xa674, 0xa67d}, - {0xa69e, 0xa69f}, - {0xa6f0, 0xa6f1}, - {0xa802, 0xa802}, - {0xa806, 0xa806}, - {0xa80b, 0xa80b}, - {0xa825, 0xa826}, - {0xa8c4, 0xa8c5}, - {0xa8e0, 0xa8f1}, - {0xa8ff, 0xa8ff}, - {0xa926, 0xa92d}, - {0xa947, 0xa951}, - {0xa980, 0xa982}, - {0xa9b3, 0xa9b3}, - {0xa9b6, 0xa9b9}, - {0xa9bc, 0xa9bd}, - {0xa9e5, 0xa9e5}, - {0xaa29, 0xaa2e}, - {0xaa31, 0xaa32}, - {0xaa35, 0xaa36}, - {0xaa43, 0xaa43}, - {0xaa4c, 0xaa4c}, - {0xaa7c, 0xaa7c}, - {0xaab0, 0xaab0}, - {0xaab2, 0xaab4}, - {0xaab7, 0xaab8}, - {0xaabe, 0xaabf}, - {0xaac1, 0xaac1}, - {0xaaec, 0xaaed}, - {0xaaf6, 0xaaf6}, - {0xabe5, 0xabe5}, - {0xabe8, 0xabe8}, - {0xabed, 0xabed}, - {0xfb1e, 0xfb1e}, - {0xfe00, 0xfe0f}, - {0xfe20, 0xfe2f}, - {0x101fd, 0x101fd}, - {0x102e0, 0x102e0}, - {0x10376, 0x1037a}, - {0x10a01, 0x10a03}, - {0x10a05, 0x10a06}, - {0x10a0c, 0x10a0f}, - {0x10a38, 0x10a3a}, - {0x10a3f, 0x10a3f}, - {0x10ae5, 0x10ae6}, - {0x10d24, 0x10d27}, - {0x10f46, 0x10f50}, - {0x11001, 0x11001}, - {0x11038, 0x11046}, - {0x1107f, 0x11081}, - {0x110b3, 0x110b6}, - {0x110b9, 0x110ba}, - {0x11100, 0x11102}, - {0x11127, 0x1112b}, - {0x1112d, 0x11134}, - {0x11173, 0x11173}, - {0x11180, 0x11181}, - {0x111b6, 0x111be}, - {0x111c9, 0x111cc}, - {0x1122f, 0x11231}, - {0x11234, 0x11234}, - {0x11236, 0x11237}, - {0x1123e, 0x1123e}, - {0x112df, 0x112df}, - {0x112e3, 0x112ea}, - {0x11300, 0x11301}, - {0x1133b, 0x1133c}, - {0x11340, 0x11340}, - {0x11366, 0x1136c}, - {0x11370, 0x11374}, - {0x11438, 0x1143f}, - {0x11442, 0x11444}, - {0x11446, 0x11446}, - {0x1145e, 0x1145e}, - {0x114b3, 0x114b8}, - {0x114ba, 0x114ba}, - {0x114bf, 0x114c0}, - {0x114c2, 0x114c3}, - {0x115b2, 0x115b5}, - {0x115bc, 0x115bd}, - {0x115bf, 0x115c0}, - {0x115dc, 0x115dd}, - {0x11633, 0x1163a}, - {0x1163d, 0x1163d}, - {0x1163f, 0x11640}, - {0x116ab, 0x116ab}, - {0x116ad, 0x116ad}, - {0x116b0, 0x116b5}, - {0x116b7, 0x116b7}, - {0x1171d, 0x1171f}, - {0x11722, 0x11725}, - {0x11727, 0x1172b}, - {0x1182f, 0x11837}, - {0x11839, 0x1183a}, - {0x119d4, 0x119d7}, - {0x119da, 0x119db}, - {0x119e0, 0x119e0}, - {0x11a01, 0x11a0a}, - {0x11a33, 0x11a38}, - {0x11a3b, 0x11a3e}, - {0x11a47, 0x11a47}, - {0x11a51, 0x11a56}, - {0x11a59, 0x11a5b}, - {0x11a8a, 0x11a96}, - {0x11a98, 0x11a99}, - {0x11c30, 0x11c36}, - {0x11c38, 0x11c3d}, - {0x11c3f, 0x11c3f}, - {0x11c92, 0x11ca7}, - {0x11caa, 0x11cb0}, - {0x11cb2, 0x11cb3}, - {0x11cb5, 0x11cb6}, - {0x11d31, 0x11d36}, - {0x11d3a, 0x11d3a}, - {0x11d3c, 0x11d3d}, - {0x11d3f, 0x11d45}, - {0x11d47, 0x11d47}, - {0x11d90, 0x11d91}, - {0x11d95, 0x11d95}, - {0x11d97, 0x11d97}, - {0x11ef3, 0x11ef4}, - {0x16af0, 0x16af4}, - {0x16b30, 0x16b36}, - {0x16f4f, 0x16f4f}, - {0x16f8f, 0x16f92}, - {0x1bc9d, 0x1bc9e}, - {0x1d167, 0x1d169}, - {0x1d17b, 0x1d182}, - {0x1d185, 0x1d18b}, - {0x1d1aa, 0x1d1ad}, - {0x1d242, 0x1d244}, - {0x1da00, 0x1da36}, - {0x1da3b, 0x1da6c}, - {0x1da75, 0x1da75}, - {0x1da84, 0x1da84}, - {0x1da9b, 0x1da9f}, - {0x1daa1, 0x1daaf}, - {0x1e000, 0x1e006}, - {0x1e008, 0x1e018}, - {0x1e01b, 0x1e021}, - {0x1e023, 0x1e024}, - {0x1e026, 0x1e02a}, - {0x1e130, 0x1e136}, - {0x1e2ec, 0x1e2ef}, - {0x1e8d0, 0x1e8d6}, - {0x1e944, 0x1e94a}, - {0xe0100, 0xe01ef}, -}; - -static const CodepointRange double_width[] = { - {0x1100, 0x115f}, - {0x231a, 0x231b}, - {0x2329, 0x232a}, - {0x23e9, 0x23ec}, - {0x23f0, 0x23f0}, - {0x23f3, 0x23f3}, - {0x25fd, 0x25fe}, - {0x2614, 0x2615}, - {0x2648, 0x2653}, - {0x267f, 0x267f}, - {0x2693, 0x2693}, - {0x26a1, 0x26a1}, - {0x26aa, 0x26ab}, - {0x26bd, 0x26be}, - {0x26c4, 0x26c5}, - {0x26ce, 0x26ce}, - {0x26d4, 0x26d4}, - {0x26ea, 0x26ea}, - {0x26f2, 0x26f3}, - {0x26f5, 0x26f5}, - {0x26fa, 0x26fa}, - {0x26fd, 0x26fd}, - {0x2705, 0x2705}, - {0x270a, 0x270b}, - {0x2728, 0x2728}, - {0x274c, 0x274c}, - {0x274e, 0x274e}, - {0x2753, 0x2755}, - {0x2757, 0x2757}, - {0x2795, 0x2797}, - {0x27b0, 0x27b0}, - {0x27bf, 0x27bf}, - {0x2b1b, 0x2b1c}, - {0x2b50, 0x2b50}, - {0x2b55, 0x2b55}, - {0x2e80, 0x2e99}, - {0x2e9b, 0x2ef3}, - {0x2f00, 0x2fd5}, - {0x2ff0, 0x2ffb}, - {0x3000, 0x303e}, - {0x3041, 0x3096}, - {0x3099, 0x30ff}, - {0x3105, 0x312f}, - {0x3131, 0x318e}, - {0x3190, 0x31ba}, - {0x31c0, 0x31e3}, - {0x31f0, 0x321e}, - {0x3220, 0x3247}, - {0x3250, 0x4dbf}, - {0x4e00, 0xa48c}, - {0xa490, 0xa4c6}, - {0xa960, 0xa97c}, - {0xac00, 0xd7a3}, - {0xf900, 0xfaff}, - {0xfe10, 0xfe19}, - {0xfe30, 0xfe52}, - {0xfe54, 0xfe66}, - {0xfe68, 0xfe6b}, - {0xff01, 0xff60}, - {0xffe0, 0xffe6}, - {0x16fe0, 0x16fe3}, - {0x17000, 0x187f7}, - {0x18800, 0x18af2}, - {0x1b000, 0x1b11e}, - {0x1b150, 0x1b152}, - {0x1b164, 0x1b167}, - {0x1b170, 0x1b2fb}, - {0x1f004, 0x1f004}, - {0x1f0cf, 0x1f0cf}, - {0x1f18e, 0x1f18e}, - {0x1f191, 0x1f19a}, - {0x1f200, 0x1f202}, - {0x1f210, 0x1f23b}, - {0x1f240, 0x1f248}, - {0x1f250, 0x1f251}, - {0x1f260, 0x1f265}, - {0x1f300, 0x1f320}, - {0x1f32d, 0x1f335}, - {0x1f337, 0x1f37c}, - {0x1f37e, 0x1f393}, - {0x1f3a0, 0x1f3ca}, - {0x1f3cf, 0x1f3d3}, - {0x1f3e0, 0x1f3f0}, - {0x1f3f4, 0x1f3f4}, - {0x1f3f8, 0x1f43e}, - {0x1f440, 0x1f440}, - {0x1f442, 0x1f4fc}, - {0x1f4ff, 0x1f53d}, - {0x1f54b, 0x1f54e}, - {0x1f550, 0x1f567}, - {0x1f57a, 0x1f57a}, - {0x1f595, 0x1f596}, - {0x1f5a4, 0x1f5a4}, - {0x1f5fb, 0x1f64f}, - {0x1f680, 0x1f6c5}, - {0x1f6cc, 0x1f6cc}, - {0x1f6d0, 0x1f6d2}, - {0x1f6d5, 0x1f6d5}, - {0x1f6eb, 0x1f6ec}, - {0x1f6f4, 0x1f6fa}, - {0x1f7e0, 0x1f7eb}, - {0x1f90d, 0x1f971}, - {0x1f973, 0x1f976}, - {0x1f97a, 0x1f9a2}, - {0x1f9a5, 0x1f9aa}, - {0x1f9ae, 0x1f9ca}, - {0x1f9cd, 0x1f9ff}, - {0x1fa70, 0x1fa73}, - {0x1fa78, 0x1fa7a}, - {0x1fa80, 0x1fa82}, - {0x1fa90, 0x1fa95}, - {0x20000, 0x2fffd}, - {0x30000, 0x3fffd}, -}; - -static const CodepointRange unprintable[] = { - {0x0080, 0x009f}, - {0x0378, 0x0379}, - {0x0380, 0x0383}, - {0x038b, 0x038b}, - {0x038d, 0x038d}, - {0x03a2, 0x03a2}, - {0x0530, 0x0530}, - {0x0557, 0x0558}, - {0x058b, 0x058c}, - {0x0590, 0x0590}, - {0x05c8, 0x05cf}, - {0x05eb, 0x05ee}, - {0x05f5, 0x05ff}, - {0x061d, 0x061d}, - {0x070e, 0x070e}, - {0x074b, 0x074c}, - {0x07b2, 0x07bf}, - {0x07fb, 0x07fc}, - {0x082e, 0x082f}, - {0x083f, 0x083f}, - {0x085c, 0x085d}, - {0x085f, 0x085f}, - {0x086b, 0x089f}, - {0x08b5, 0x08b5}, - {0x08be, 0x08d2}, - {0x0984, 0x0984}, - {0x098d, 0x098e}, - {0x0991, 0x0992}, - {0x09a9, 0x09a9}, - {0x09b1, 0x09b1}, - {0x09b3, 0x09b5}, - {0x09ba, 0x09bb}, - {0x09c5, 0x09c6}, - {0x09c9, 0x09ca}, - {0x09cf, 0x09d6}, - {0x09d8, 0x09db}, - {0x09de, 0x09de}, - {0x09e4, 0x09e5}, - {0x09ff, 0x0a00}, - {0x0a04, 0x0a04}, - {0x0a0b, 0x0a0e}, - {0x0a11, 0x0a12}, - {0x0a29, 0x0a29}, - {0x0a31, 0x0a31}, - {0x0a34, 0x0a34}, - {0x0a37, 0x0a37}, - {0x0a3a, 0x0a3b}, - {0x0a3d, 0x0a3d}, - {0x0a43, 0x0a46}, - {0x0a49, 0x0a4a}, - {0x0a4e, 0x0a50}, - {0x0a52, 0x0a58}, - {0x0a5d, 0x0a5d}, - {0x0a5f, 0x0a65}, - {0x0a77, 0x0a80}, - {0x0a84, 0x0a84}, - {0x0a8e, 0x0a8e}, - {0x0a92, 0x0a92}, - {0x0aa9, 0x0aa9}, - {0x0ab1, 0x0ab1}, - {0x0ab4, 0x0ab4}, - {0x0aba, 0x0abb}, - {0x0ac6, 0x0ac6}, - {0x0aca, 0x0aca}, - {0x0ace, 0x0acf}, - {0x0ad1, 0x0adf}, - {0x0ae4, 0x0ae5}, - {0x0af2, 0x0af8}, - {0x0b00, 0x0b00}, - {0x0b04, 0x0b04}, - {0x0b0d, 0x0b0e}, - {0x0b11, 0x0b12}, - {0x0b29, 0x0b29}, - {0x0b31, 0x0b31}, - {0x0b34, 0x0b34}, - {0x0b3a, 0x0b3b}, - {0x0b45, 0x0b46}, - {0x0b49, 0x0b4a}, - {0x0b4e, 0x0b55}, - {0x0b58, 0x0b5b}, - {0x0b5e, 0x0b5e}, - {0x0b64, 0x0b65}, - {0x0b78, 0x0b81}, - {0x0b84, 0x0b84}, - {0x0b8b, 0x0b8d}, - {0x0b91, 0x0b91}, - {0x0b96, 0x0b98}, - {0x0b9b, 0x0b9b}, - {0x0b9d, 0x0b9d}, - {0x0ba0, 0x0ba2}, - {0x0ba5, 0x0ba7}, - {0x0bab, 0x0bad}, - {0x0bba, 0x0bbd}, - {0x0bc3, 0x0bc5}, - {0x0bc9, 0x0bc9}, - {0x0bce, 0x0bcf}, - {0x0bd1, 0x0bd6}, - {0x0bd8, 0x0be5}, - {0x0bfb, 0x0bff}, - {0x0c0d, 0x0c0d}, - {0x0c11, 0x0c11}, - {0x0c29, 0x0c29}, - {0x0c3a, 0x0c3c}, - {0x0c45, 0x0c45}, - {0x0c49, 0x0c49}, - {0x0c4e, 0x0c54}, - {0x0c57, 0x0c57}, - {0x0c5b, 0x0c5f}, - {0x0c64, 0x0c65}, - {0x0c70, 0x0c76}, - {0x0c8d, 0x0c8d}, - {0x0c91, 0x0c91}, - {0x0ca9, 0x0ca9}, - {0x0cb4, 0x0cb4}, - {0x0cba, 0x0cbb}, - {0x0cc5, 0x0cc5}, - {0x0cc9, 0x0cc9}, - {0x0cce, 0x0cd4}, - {0x0cd7, 0x0cdd}, - {0x0cdf, 0x0cdf}, - {0x0ce4, 0x0ce5}, - {0x0cf0, 0x0cf0}, - {0x0cf3, 0x0cff}, - {0x0d04, 0x0d04}, - {0x0d0d, 0x0d0d}, - {0x0d11, 0x0d11}, - {0x0d45, 0x0d45}, - {0x0d49, 0x0d49}, - {0x0d50, 0x0d53}, - {0x0d64, 0x0d65}, - {0x0d80, 0x0d81}, - {0x0d84, 0x0d84}, - {0x0d97, 0x0d99}, - {0x0db2, 0x0db2}, - {0x0dbc, 0x0dbc}, - {0x0dbe, 0x0dbf}, - {0x0dc7, 0x0dc9}, - {0x0dcb, 0x0dce}, - {0x0dd5, 0x0dd5}, - {0x0dd7, 0x0dd7}, - {0x0de0, 0x0de5}, - {0x0df0, 0x0df1}, - {0x0df5, 0x0e00}, - {0x0e3b, 0x0e3e}, - {0x0e5c, 0x0e80}, - {0x0e83, 0x0e83}, - {0x0e85, 0x0e85}, - {0x0e8b, 0x0e8b}, - {0x0ea4, 0x0ea4}, - {0x0ea6, 0x0ea6}, - {0x0ebe, 0x0ebf}, - {0x0ec5, 0x0ec5}, - {0x0ec7, 0x0ec7}, - {0x0ece, 0x0ecf}, - {0x0eda, 0x0edb}, - {0x0ee0, 0x0eff}, - {0x0f48, 0x0f48}, - {0x0f6d, 0x0f70}, - {0x0f98, 0x0f98}, - {0x0fbd, 0x0fbd}, - {0x0fcd, 0x0fcd}, - {0x0fdb, 0x0fff}, - {0x10c6, 0x10c6}, - {0x10c8, 0x10cc}, - {0x10ce, 0x10cf}, - {0x1249, 0x1249}, - {0x124e, 0x124f}, - {0x1257, 0x1257}, - {0x1259, 0x1259}, - {0x125e, 0x125f}, - {0x1289, 0x1289}, - {0x128e, 0x128f}, - {0x12b1, 0x12b1}, - {0x12b6, 0x12b7}, - {0x12bf, 0x12bf}, - {0x12c1, 0x12c1}, - {0x12c6, 0x12c7}, - {0x12d7, 0x12d7}, - {0x1311, 0x1311}, - {0x1316, 0x1317}, - {0x135b, 0x135c}, - {0x137d, 0x137f}, - {0x139a, 0x139f}, - {0x13f6, 0x13f7}, - {0x13fe, 0x13ff}, - {0x169d, 0x169f}, - {0x16f9, 0x16ff}, - {0x170d, 0x170d}, - {0x1715, 0x171f}, - {0x1737, 0x173f}, - {0x1754, 0x175f}, - {0x176d, 0x176d}, - {0x1771, 0x1771}, - {0x1774, 0x177f}, - {0x17de, 0x17df}, - {0x17ea, 0x17ef}, - {0x17fa, 0x17ff}, - {0x180f, 0x180f}, - {0x181a, 0x181f}, - {0x1879, 0x187f}, - {0x18ab, 0x18af}, - {0x18f6, 0x18ff}, - {0x191f, 0x191f}, - {0x192c, 0x192f}, - {0x193c, 0x193f}, - {0x1941, 0x1943}, - {0x196e, 0x196f}, - {0x1975, 0x197f}, - {0x19ac, 0x19af}, - {0x19ca, 0x19cf}, - {0x19db, 0x19dd}, - {0x1a1c, 0x1a1d}, - {0x1a5f, 0x1a5f}, - {0x1a7d, 0x1a7e}, - {0x1a8a, 0x1a8f}, - {0x1a9a, 0x1a9f}, - {0x1aae, 0x1aaf}, - {0x1abf, 0x1aff}, - {0x1b4c, 0x1b4f}, - {0x1b7d, 0x1b7f}, - {0x1bf4, 0x1bfb}, - {0x1c38, 0x1c3a}, - {0x1c4a, 0x1c4c}, - {0x1c89, 0x1c8f}, - {0x1cbb, 0x1cbc}, - {0x1cc8, 0x1ccf}, - {0x1cfb, 0x1cff}, - {0x1dfa, 0x1dfa}, - {0x1f16, 0x1f17}, - {0x1f1e, 0x1f1f}, - {0x1f46, 0x1f47}, - {0x1f4e, 0x1f4f}, - {0x1f58, 0x1f58}, - {0x1f5a, 0x1f5a}, - {0x1f5c, 0x1f5c}, - {0x1f5e, 0x1f5e}, - {0x1f7e, 0x1f7f}, - {0x1fb5, 0x1fb5}, - {0x1fc5, 0x1fc5}, - {0x1fd4, 0x1fd5}, - {0x1fdc, 0x1fdc}, - {0x1ff0, 0x1ff1}, - {0x1ff5, 0x1ff5}, - {0x1fff, 0x1fff}, - {0x2065, 0x2065}, - {0x2072, 0x2073}, - {0x208f, 0x208f}, - {0x209d, 0x209f}, - {0x20c0, 0x20cf}, - {0x20f1, 0x20ff}, - {0x218c, 0x218f}, - {0x2427, 0x243f}, - {0x244b, 0x245f}, - {0x2b74, 0x2b75}, - {0x2b96, 0x2b97}, - {0x2c2f, 0x2c2f}, - {0x2c5f, 0x2c5f}, - {0x2cf4, 0x2cf8}, - {0x2d26, 0x2d26}, - {0x2d28, 0x2d2c}, - {0x2d2e, 0x2d2f}, - {0x2d68, 0x2d6e}, - {0x2d71, 0x2d7e}, - {0x2d97, 0x2d9f}, - {0x2da7, 0x2da7}, - {0x2daf, 0x2daf}, - {0x2db7, 0x2db7}, - {0x2dbf, 0x2dbf}, - {0x2dc7, 0x2dc7}, - {0x2dcf, 0x2dcf}, - {0x2dd7, 0x2dd7}, - {0x2ddf, 0x2ddf}, - {0x2e50, 0x2e7f}, - {0x2e9a, 0x2e9a}, - {0x2ef4, 0x2eff}, - {0x2fd6, 0x2fef}, - {0x2ffc, 0x2fff}, - {0x3040, 0x3040}, - {0x3097, 0x3098}, - {0x3100, 0x3104}, - {0x3130, 0x3130}, - {0x318f, 0x318f}, - {0x31bb, 0x31bf}, - {0x31e4, 0x31ef}, - {0x321f, 0x321f}, - {0x4db6, 0x4dbf}, - {0x9ff0, 0x9fff}, - {0xa48d, 0xa48f}, - {0xa4c7, 0xa4cf}, - {0xa62c, 0xa63f}, - {0xa6f8, 0xa6ff}, - {0xa7c0, 0xa7c1}, - {0xa7c7, 0xa7f6}, - {0xa82c, 0xa82f}, - {0xa83a, 0xa83f}, - {0xa878, 0xa87f}, - {0xa8c6, 0xa8cd}, - {0xa8da, 0xa8df}, - {0xa954, 0xa95e}, - {0xa97d, 0xa97f}, - {0xa9ce, 0xa9ce}, - {0xa9da, 0xa9dd}, - {0xa9ff, 0xa9ff}, - {0xaa37, 0xaa3f}, - {0xaa4e, 0xaa4f}, - {0xaa5a, 0xaa5b}, - {0xaac3, 0xaada}, - {0xaaf7, 0xab00}, - {0xab07, 0xab08}, - {0xab0f, 0xab10}, - {0xab17, 0xab1f}, - {0xab27, 0xab27}, - {0xab2f, 0xab2f}, - {0xab68, 0xab6f}, - {0xabee, 0xabef}, - {0xabfa, 0xabff}, - {0xd7a4, 0xd7af}, - {0xd7c7, 0xd7ca}, - {0xd7fc, 0xf8ff}, - {0xfa6e, 0xfa6f}, - {0xfada, 0xfaff}, - {0xfb07, 0xfb12}, - {0xfb18, 0xfb1c}, - {0xfb37, 0xfb37}, - {0xfb3d, 0xfb3d}, - {0xfb3f, 0xfb3f}, - {0xfb42, 0xfb42}, - {0xfb45, 0xfb45}, - {0xfbc2, 0xfbd2}, - {0xfd40, 0xfd4f}, - {0xfd90, 0xfd91}, - {0xfdc8, 0xfdef}, - {0xfdfe, 0xfdff}, - {0xfe1a, 0xfe1f}, - {0xfe53, 0xfe53}, - {0xfe67, 0xfe67}, - {0xfe6c, 0xfe6f}, - {0xfe75, 0xfe75}, - {0xfefd, 0xfefe}, - {0xff00, 0xff00}, - {0xffbf, 0xffc1}, - {0xffc8, 0xffc9}, - {0xffd0, 0xffd1}, - {0xffd8, 0xffd9}, - {0xffdd, 0xffdf}, - {0xffe7, 0xffe7}, - {0xffef, 0xfff8}, - {0xfffe, 0xffff}, - {0x1000c, 0x1000c}, - {0x10027, 0x10027}, - {0x1003b, 0x1003b}, - {0x1003e, 0x1003e}, - {0x1004e, 0x1004f}, - {0x1005e, 0x1007f}, - {0x100fb, 0x100ff}, - {0x10103, 0x10106}, - {0x10134, 0x10136}, - {0x1018f, 0x1018f}, - {0x1019c, 0x1019f}, - {0x101a1, 0x101cf}, - {0x101fe, 0x1027f}, - {0x1029d, 0x1029f}, - {0x102d1, 0x102df}, - {0x102fc, 0x102ff}, - {0x10324, 0x1032c}, - {0x1034b, 0x1034f}, - {0x1037b, 0x1037f}, - {0x1039e, 0x1039e}, - {0x103c4, 0x103c7}, - {0x103d6, 0x103ff}, - {0x1049e, 0x1049f}, - {0x104aa, 0x104af}, - {0x104d4, 0x104d7}, - {0x104fc, 0x104ff}, - {0x10528, 0x1052f}, - {0x10564, 0x1056e}, - {0x10570, 0x105ff}, - {0x10737, 0x1073f}, - {0x10756, 0x1075f}, - {0x10768, 0x107ff}, - {0x10806, 0x10807}, - {0x10809, 0x10809}, - {0x10836, 0x10836}, - {0x10839, 0x1083b}, - {0x1083d, 0x1083e}, - {0x10856, 0x10856}, - {0x1089f, 0x108a6}, - {0x108b0, 0x108df}, - {0x108f3, 0x108f3}, - {0x108f6, 0x108fa}, - {0x1091c, 0x1091e}, - {0x1093a, 0x1093e}, - {0x10940, 0x1097f}, - {0x109b8, 0x109bb}, - {0x109d0, 0x109d1}, - {0x10a04, 0x10a04}, - {0x10a07, 0x10a0b}, - {0x10a14, 0x10a14}, - {0x10a18, 0x10a18}, - {0x10a36, 0x10a37}, - {0x10a3b, 0x10a3e}, - {0x10a49, 0x10a4f}, - {0x10a59, 0x10a5f}, - {0x10aa0, 0x10abf}, - {0x10ae7, 0x10aea}, - {0x10af7, 0x10aff}, - {0x10b36, 0x10b38}, - {0x10b56, 0x10b57}, - {0x10b73, 0x10b77}, - {0x10b92, 0x10b98}, - {0x10b9d, 0x10ba8}, - {0x10bb0, 0x10bff}, - {0x10c49, 0x10c7f}, - {0x10cb3, 0x10cbf}, - {0x10cf3, 0x10cf9}, - {0x10d28, 0x10d2f}, - {0x10d3a, 0x10e5f}, - {0x10e7f, 0x10eff}, - {0x10f28, 0x10f2f}, - {0x10f5a, 0x10fdf}, - {0x10ff7, 0x10fff}, - {0x1104e, 0x11051}, - {0x11070, 0x1107e}, - {0x110c2, 0x110cc}, - {0x110ce, 0x110cf}, - {0x110e9, 0x110ef}, - {0x110fa, 0x110ff}, - {0x11135, 0x11135}, - {0x11147, 0x1114f}, - {0x11177, 0x1117f}, - {0x111ce, 0x111cf}, - {0x111e0, 0x111e0}, - {0x111f5, 0x111ff}, - {0x11212, 0x11212}, - {0x1123f, 0x1127f}, - {0x11287, 0x11287}, - {0x11289, 0x11289}, - {0x1128e, 0x1128e}, - {0x1129e, 0x1129e}, - {0x112aa, 0x112af}, - {0x112eb, 0x112ef}, - {0x112fa, 0x112ff}, - {0x11304, 0x11304}, - {0x1130d, 0x1130e}, - {0x11311, 0x11312}, - {0x11329, 0x11329}, - {0x11331, 0x11331}, - {0x11334, 0x11334}, - {0x1133a, 0x1133a}, - {0x11345, 0x11346}, - {0x11349, 0x1134a}, - {0x1134e, 0x1134f}, - {0x11351, 0x11356}, - {0x11358, 0x1135c}, - {0x11364, 0x11365}, - {0x1136d, 0x1136f}, - {0x11375, 0x113ff}, - {0x1145a, 0x1145a}, - {0x1145c, 0x1145c}, - {0x11460, 0x1147f}, - {0x114c8, 0x114cf}, - {0x114da, 0x1157f}, - {0x115b6, 0x115b7}, - {0x115de, 0x115ff}, - {0x11645, 0x1164f}, - {0x1165a, 0x1165f}, - {0x1166d, 0x1167f}, - {0x116b9, 0x116bf}, - {0x116ca, 0x116ff}, - {0x1171b, 0x1171c}, - {0x1172c, 0x1172f}, - {0x11740, 0x117ff}, - {0x1183c, 0x1189f}, - {0x118f3, 0x118fe}, - {0x11900, 0x1199f}, - {0x119a8, 0x119a9}, - {0x119d8, 0x119d9}, - {0x119e5, 0x119ff}, - {0x11a48, 0x11a4f}, - {0x11aa3, 0x11abf}, - {0x11af9, 0x11bff}, - {0x11c09, 0x11c09}, - {0x11c37, 0x11c37}, - {0x11c46, 0x11c4f}, - {0x11c6d, 0x11c6f}, - {0x11c90, 0x11c91}, - {0x11ca8, 0x11ca8}, - {0x11cb7, 0x11cff}, - {0x11d07, 0x11d07}, - {0x11d0a, 0x11d0a}, - {0x11d37, 0x11d39}, - {0x11d3b, 0x11d3b}, - {0x11d3e, 0x11d3e}, - {0x11d48, 0x11d4f}, - {0x11d5a, 0x11d5f}, - {0x11d66, 0x11d66}, - {0x11d69, 0x11d69}, - {0x11d8f, 0x11d8f}, - {0x11d92, 0x11d92}, - {0x11d99, 0x11d9f}, - {0x11daa, 0x11edf}, - {0x11ef9, 0x11fbf}, - {0x11ff2, 0x11ffe}, - {0x1239a, 0x123ff}, - {0x1246f, 0x1246f}, - {0x12475, 0x1247f}, - {0x12544, 0x12fff}, - {0x1342f, 0x1342f}, - {0x13439, 0x143ff}, - {0x14647, 0x167ff}, - {0x16a39, 0x16a3f}, - {0x16a5f, 0x16a5f}, - {0x16a6a, 0x16a6d}, - {0x16a70, 0x16acf}, - {0x16aee, 0x16aef}, - {0x16af6, 0x16aff}, - {0x16b46, 0x16b4f}, - {0x16b5a, 0x16b5a}, - {0x16b62, 0x16b62}, - {0x16b78, 0x16b7c}, - {0x16b90, 0x16e3f}, - {0x16e9b, 0x16eff}, - {0x16f4b, 0x16f4e}, - {0x16f88, 0x16f8e}, - {0x16fa0, 0x16fdf}, - {0x16fe4, 0x16fff}, - {0x187f8, 0x187ff}, - {0x18af3, 0x1afff}, - {0x1b11f, 0x1b14f}, - {0x1b153, 0x1b163}, - {0x1b168, 0x1b16f}, - {0x1b2fc, 0x1bbff}, - {0x1bc6b, 0x1bc6f}, - {0x1bc7d, 0x1bc7f}, - {0x1bc89, 0x1bc8f}, - {0x1bc9a, 0x1bc9b}, - {0x1bca4, 0x1cfff}, - {0x1d0f6, 0x1d0ff}, - {0x1d127, 0x1d128}, - {0x1d1e9, 0x1d1ff}, - {0x1d246, 0x1d2df}, - {0x1d2f4, 0x1d2ff}, - {0x1d357, 0x1d35f}, - {0x1d379, 0x1d3ff}, - {0x1d455, 0x1d455}, - {0x1d49d, 0x1d49d}, - {0x1d4a0, 0x1d4a1}, - {0x1d4a3, 0x1d4a4}, - {0x1d4a7, 0x1d4a8}, - {0x1d4ad, 0x1d4ad}, - {0x1d4ba, 0x1d4ba}, - {0x1d4bc, 0x1d4bc}, - {0x1d4c4, 0x1d4c4}, - {0x1d506, 0x1d506}, - {0x1d50b, 0x1d50c}, - {0x1d515, 0x1d515}, - {0x1d51d, 0x1d51d}, - {0x1d53a, 0x1d53a}, - {0x1d53f, 0x1d53f}, - {0x1d545, 0x1d545}, - {0x1d547, 0x1d549}, - {0x1d551, 0x1d551}, - {0x1d6a6, 0x1d6a7}, - {0x1d7cc, 0x1d7cd}, - {0x1da8c, 0x1da9a}, - {0x1daa0, 0x1daa0}, - {0x1dab0, 0x1dfff}, - {0x1e007, 0x1e007}, - {0x1e019, 0x1e01a}, - {0x1e022, 0x1e022}, - {0x1e025, 0x1e025}, - {0x1e02b, 0x1e0ff}, - {0x1e12d, 0x1e12f}, - {0x1e13e, 0x1e13f}, - {0x1e14a, 0x1e14d}, - {0x1e150, 0x1e2bf}, - {0x1e2fa, 0x1e2fe}, - {0x1e300, 0x1e7ff}, - {0x1e8c5, 0x1e8c6}, - {0x1e8d7, 0x1e8ff}, - {0x1e94c, 0x1e94f}, - {0x1e95a, 0x1e95d}, - {0x1e960, 0x1ec70}, - {0x1ecb5, 0x1ed00}, - {0x1ed3e, 0x1edff}, - {0x1ee04, 0x1ee04}, - {0x1ee20, 0x1ee20}, - {0x1ee23, 0x1ee23}, - {0x1ee25, 0x1ee26}, - {0x1ee28, 0x1ee28}, - {0x1ee33, 0x1ee33}, - {0x1ee38, 0x1ee38}, - {0x1ee3a, 0x1ee3a}, - {0x1ee3c, 0x1ee41}, - {0x1ee43, 0x1ee46}, - {0x1ee48, 0x1ee48}, - {0x1ee4a, 0x1ee4a}, - {0x1ee4c, 0x1ee4c}, - {0x1ee50, 0x1ee50}, - {0x1ee53, 0x1ee53}, - {0x1ee55, 0x1ee56}, - {0x1ee58, 0x1ee58}, - {0x1ee5a, 0x1ee5a}, - {0x1ee5c, 0x1ee5c}, - {0x1ee5e, 0x1ee5e}, - {0x1ee60, 0x1ee60}, - {0x1ee63, 0x1ee63}, - {0x1ee65, 0x1ee66}, - {0x1ee6b, 0x1ee6b}, - {0x1ee73, 0x1ee73}, - {0x1ee78, 0x1ee78}, - {0x1ee7d, 0x1ee7d}, - {0x1ee7f, 0x1ee7f}, - {0x1ee8a, 0x1ee8a}, - {0x1ee9c, 0x1eea0}, - {0x1eea4, 0x1eea4}, - {0x1eeaa, 0x1eeaa}, - {0x1eebc, 0x1eeef}, - {0x1eef2, 0x1efff}, - {0x1f02c, 0x1f02f}, - {0x1f094, 0x1f09f}, - {0x1f0af, 0x1f0b0}, - {0x1f0c0, 0x1f0c0}, - {0x1f0d0, 0x1f0d0}, - {0x1f0f6, 0x1f0ff}, - {0x1f10d, 0x1f10f}, - {0x1f16d, 0x1f16f}, - {0x1f1ad, 0x1f1e5}, - {0x1f203, 0x1f20f}, - {0x1f23c, 0x1f23f}, - {0x1f249, 0x1f24f}, - {0x1f252, 0x1f25f}, - {0x1f266, 0x1f2ff}, - {0x1f6d6, 0x1f6df}, - {0x1f6ed, 0x1f6ef}, - {0x1f6fb, 0x1f6ff}, - {0x1f774, 0x1f77f}, - {0x1f7d9, 0x1f7df}, - {0x1f7ec, 0x1f7ff}, - {0x1f80c, 0x1f80f}, - {0x1f848, 0x1f84f}, - {0x1f85a, 0x1f85f}, - {0x1f888, 0x1f88f}, - {0x1f8ae, 0x1f8ff}, - {0x1f90c, 0x1f90c}, - {0x1f972, 0x1f972}, - {0x1f977, 0x1f979}, - {0x1f9a3, 0x1f9a4}, - {0x1f9ab, 0x1f9ad}, - {0x1f9cb, 0x1f9cc}, - {0x1fa54, 0x1fa5f}, - {0x1fa6e, 0x1fa6f}, - {0x1fa74, 0x1fa77}, - {0x1fa7b, 0x1fa7f}, - {0x1fa83, 0x1fa8f}, - {0x1fa96, 0x1ffff}, - {0x2a6d7, 0x2a6ff}, - {0x2b735, 0x2b73f}, - {0x2b81e, 0x2b81f}, - {0x2cea2, 0x2ceaf}, - {0x2ebe1, 0x2f7ff}, - {0x2fa1e, 0xe0000}, - {0xe0002, 0xe001f}, - {0xe0080, 0xe00ff}, - {0xe01f0, 0x10ffff}, -}; - diff -Nru dte-1.9.1/src/util/wcwidth.lua dte-1.10/src/util/wcwidth.lua --- dte-1.9.1/src/util/wcwidth.lua 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/wcwidth.lua 1970-01-01 00:00:00.000000000 +0000 @@ -1,165 +0,0 @@ -local progname = arg[0] -local RangeTable = {} -RangeTable.__index = RangeTable - -function RangeTable.new() - return setmetatable({n = 0}, RangeTable) -end - -function RangeTable:insert(min, max) - local n = self.n + 1 - self[n] = {min = assert(min), max = assert(max)} - self.n = n -end - -function RangeTable:remove(index) - table.remove(self, index) - self.n = self.n - 1 -end - -function RangeTable:contains(codepoint) - for i = 1, self.n do - local range = self[i] - if codepoint >= range.min and codepoint <= range.max then - return true - end - end - return false -end - -function RangeTable:merge_adjacent() - for i = self.n, 1, -1 do - local cur, prev = self[i], self[i - 1] - if prev and 1 + prev.max == cur.min then - prev.max = cur.max - self:remove(i) - end - end - return self -end - -function RangeTable:print_ranges(name, output) - local n = assert(self.n) - output:write("static const CodepointRange ", name, "[] = {\n") - for i = 1, n do - local min = assert(self[i].min) - local max = assert(self[i].max) - output:write((" {0x%04x, 0x%04x},\n"):format(min, max)) - end - output:write("};\n\n") -end - -local function read_ucd(path, pattern) - assert(pattern) - if not path then - io.stderr:write ( - "Usage: ", progname, " UnicodeData.txt EastAsianWidth.txt\n", - "(available from: https://unicode.org/Public/11.0.0/ucd/)\n" - ) - os.exit(1) - end - local file = assert(io.open(path, "r")) - local text = assert(file:read("*a")) - file:close() - assert(text:find(pattern) == 1, "Unrecognized input format") - return text -end - -local unidata = read_ucd(arg[1], "^0000;") -local eaw = read_ucd(arg[2], "^# EastAsianWidth") -local dcp = read_ucd(arg[3], "^# DerivedCoreProperties") - -local nonspacing_mark = RangeTable.new() -local special_whitespace = RangeTable.new() -local unprintable = RangeTable.new() -local default_ignorable = RangeTable.new() -local double_width = RangeTable.new() -local exclude = RangeTable.new() - -local mappings = { - Zs = special_whitespace, -- Space_Separator (various non-zero width spaces) - Zl = special_whitespace, -- Line_Separator (U+2028 only) - Zp = special_whitespace, -- Paragraph_Separator (U+2029 only) - Cc = unprintable, -- Control - Cs = unprintable, -- Surrogate - Co = unprintable, -- Private_Use - Cn = unprintable, -- Unassigned - Me = nonspacing_mark, -- Enclosing_Mark - Mn = nonspacing_mark, -- Nonspacing_Mark - - -- This is a "default ignorable" character in the "Cf" (Format) - -- category, but it's included in special_whitespace because it - -- looks exactly like a normal space when using a monospace font: - [0x00AD] = special_whitespace, -- Soft hyphen - - -- These are "Zs" characters, but don't need to be in special_whitespace - -- because they can easily be distinguished from a normal space: - [0x1680] = exclude, -- Ogham space mark (looks like an em dash) - [0x3000] = exclude, -- Ideographic space (double width space) -} - --- ASCII -for u = 0x00, 0x7F do - mappings[u] = exclude -end - -for min, max, property in dcp:gmatch "\n(%x+)%.*(%x*) *; *([%w_]+)" do - if property == "Default_Ignorable_Code_Point" then - min = assert(tonumber(min, 16)) - max = tonumber(max, 16) - if not mappings[min] then - default_ignorable:insert(min, max or min) - end - end -end - -local prev_codepoint = -1 -local range = false -for codepoint, name, category in unidata:gmatch "(%x+);([^;]*);(%u%a);[^\n]*\n" do - codepoint = assert(tonumber(codepoint, 16)) - assert(codepoint > prev_codepoint) - assert(name) - assert(category) - - -- Omitted codepoints default to the "Cn" (unassigned) category - -- https://www.unicode.org/reports/tr44/tr44-21.html#Default_Values_Table - if not range and prev_codepoint < codepoint - 1 then - unprintable:insert(prev_codepoint + 1, codepoint - 1) - end - - local t = mappings[codepoint] or mappings[category] - - if t == unprintable and default_ignorable:contains(codepoint) then - -- Don't add codepoints to the unprintable table if they've - -- already been added to the default_ignorable table - elseif range then - assert(name:match("^<.*, Last>$")) - range = false - if t then - t:insert(prev_codepoint, codepoint) - end - elseif name:match("^<.*, First>$") then - range = true - elseif t then - t:insert(codepoint, codepoint) - end - - prev_codepoint = codepoint -end - -assert(prev_codepoint == 0x10FFFD) -assert(exclude.n == 127 + 3) -unprintable:insert(prev_codepoint + 1, 0x10FFFF) - -for min, max in eaw:gmatch "\n(%x+)%.*(%x*);[WF]" do - min = assert(tonumber(min, 16)) - max = tonumber(max, 16) - double_width:insert(min, max or min) -end - -local stdout = io.stdout -special_whitespace:merge_adjacent():print_ranges("special_whitespace", stdout) -default_ignorable:merge_adjacent():print_ranges("default_ignorable", stdout) -nonspacing_mark:merge_adjacent():print_ranges("nonspacing_mark", stdout) -double_width:merge_adjacent():print_ranges("double_width", stdout) -unprintable:merge_adjacent():print_ranges("unprintable", stdout) diff -Nru dte-1.9.1/src/util/xmalloc.c dte-1.10/src/util/xmalloc.c --- dte-1.9.1/src/util/xmalloc.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/xmalloc.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,12 @@ #include +#include +#include +#include #include #include -#include "checked-arith.h" #include "xmalloc.h" -#include "ascii.h" -#include "../debug.h" +#include "checked-arith.h" +#include "debug.h" static void *check_alloc(void *alloc) { @@ -55,51 +57,34 @@ return check_alloc(strdup(str)); } -char *xstrndup(const char *str, size_t n) -{ - return check_alloc(strndup(str, n)); -} - -char *xstrdup_toupper(const char *str) -{ - const size_t len = strlen(str); - char *upper_str = xmalloc(len + 1); - for (size_t i = 0; i < len; i++) { - upper_str[i] = ascii_toupper(str[i]); - } - upper_str[len] = '\0'; - return upper_str; -} - -char *xstrcut(const char *str, size_t size) -{ - char *s = xmalloc(size + 1); - memcpy(s, str, size); - s[size] = '\0'; - return s; -} - -VPRINTF(2) -static int xvasprintf_(char **strp, const char *format, va_list ap) +VPRINTF(1) +static char *xvasprintf(const char *format, va_list ap) { va_list ap2; va_copy(ap2, ap); int n = vsnprintf(NULL, 0, format, ap2); + va_end(ap2); if (unlikely(n < 0)) { - fatal_error("vsnprintf", EILSEQ); + goto error; + } else if (unlikely(n >= INT_MAX)) { + errno = EOVERFLOW; + goto error; } - va_end(ap2); - *strp = xmalloc(n + 1); - int m = vsnprintf(*strp, n + 1, format, ap); - BUG_ON(m != n); - return n; -} -char *xvasprintf(const char *format, va_list ap) -{ - char *str; - xvasprintf_(&str, format, ap); + char *str = malloc(n + 1); + if (unlikely(!str)) { + goto error; + } + + int m = vsnprintf(str, n + 1, format, ap); + if (unlikely(m < 0)) { + goto error; + } + BUG_ON(m != n); return str; + +error: + fatal_error(__func__, errno); } char *xasprintf(const char *format, ...) diff -Nru dte-1.9.1/src/util/xmalloc.h dte-1.10/src/util/xmalloc.h --- dte-1.9.1/src/util/xmalloc.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/xmalloc.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,6 @@ #ifndef UTIL_XMALLOC_H #define UTIL_XMALLOC_H -#include -#include #include #include #include "macros.h" @@ -20,49 +18,41 @@ void *xcalloc(size_t size) XMALLOC ALLOC_SIZE(1); void *xrealloc(void *ptr, size_t size) RETURNS_NONNULL ALLOC_SIZE(2); char *xstrdup(const char *str) XSTRDUP; -char *xstrndup(const char *str, size_t n) XSTRDUP; -char *xstrdup_toupper(const char *str) XSTRDUP; -char *xstrcut(const char *str, size_t size) XSTRDUP; -char *xvasprintf(const char *format, va_list ap) VPRINTF(1) XMALLOC; char *xasprintf(const char *format, ...) PRINTF(1) XMALLOC; size_t size_multiply_(size_t a, size_t b); size_t size_add(size_t a, size_t b); static inline size_t size_multiply(size_t a, size_t b) { - // If either argument is 1, the multiplication can't overflow - // and is thus safe to be inlined without checks. - if (a == 1 || b == 1) { + // If either argument is known at compile-time to be 1, the multiplication + // can't overflow and is thus safe to be inlined without checks + if ((IS_CT_CONSTANT(a) && a == 1) || (IS_CT_CONSTANT(b) && b == 1)) { return a * b; } - // Otherwise, emit a call to the checked implementation (which is - // extern, because it may call fatal_error()). + // Otherwise, emit a call to the checked implementation return size_multiply_(a, b); } NONNULL_ARGS_AND_RETURN ALLOC_SIZE(2) static inline void *xmemdup(const void *ptr, size_t size) { - void *buf = xmalloc(size); - memcpy(buf, ptr, size); - return buf; + return memcpy(xmalloc(size), ptr, size); } // Round x up to a multiple of r (which *must* be a power of 2) -static inline size_t ROUND_UP(size_t x, size_t r) +static inline size_t round_size_to_next_multiple(size_t x, size_t r) DIAGNOSE_IF(!IS_POWER_OF_2(r)) { r--; return (x + r) & ~r; } -static inline size_t round_size_to_next_power_of_2(size_t x) +XSTRDUP +static inline char *xstrcut(const char *str, size_t size) { - x--; - for (size_t i = 1, n = sizeof(size_t) * CHAR_BIT; i < n; i <<= 1) { - x |= x >> i; - } - return x + 1; + char *s = xmalloc(size + 1); + s[size] = '\0'; + return memcpy(s, str, size); } XSTRDUP diff -Nru dte-1.9.1/src/util/xreadwrite.c dte-1.10/src/util/xreadwrite.c --- dte-1.9.1/src/util/xreadwrite.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/xreadwrite.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,4 +1,5 @@ #include +#include #include #include "xreadwrite.h" @@ -8,7 +9,7 @@ size_t pos = 0; do { ssize_t rc = read(fd, b + pos, count - pos); - if (rc == -1) { + if (unlikely(rc == -1)) { if (errno == EINTR) { continue; } @@ -29,7 +30,7 @@ const size_t count_save = count; do { ssize_t rc = write(fd, b, count); - if (rc == -1) { + if (unlikely(rc == -1)) { if (errno == EINTR) { continue; } @@ -40,3 +41,34 @@ } while (count > 0); return count_save; } + +int xclose(int fd) +{ + int r = close(fd); + if (likely(r == 0 || errno != EINTR)) { + return r; + } + + // If the first close() call failed with EINTR, retry until + // it succeeds or fails with a different error. + do { + r = close(fd); + } while (r && errno == EINTR); + + // On some systems, when close() fails with EINTR, the descriptor + // still gets closed anyway and calling close() again then fails + // with EBADF. This is not really an "error" that can be handled + // meaningfully, so we just return as if successful. + // + // Note that this is only safe to do in a single-threaded context, + // where there's no risk of a concurrent open() call reusing the + // same file descriptor. + // + // * http://www.daemonology.net/blog/2011-12-17-POSIX-close-is-broken.html + // * https://sourceware.org/bugzilla/show_bug.cgi?id=14627 + if (r && errno == EBADF) { + r = 0; + } + + return r; +} diff -Nru dte-1.9.1/src/util/xreadwrite.h dte-1.10/src/util/xreadwrite.h --- dte-1.9.1/src/util/xreadwrite.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/xreadwrite.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,24 @@ #ifndef UTIL_XREADWRITE_H #define UTIL_XREADWRITE_H +#include +#include #include #include "macros.h" +NONNULL_ARGS +static inline int xopen(const char *path, int flags, mode_t mode) +{ + int fd; + do { + fd = open(path, flags, mode); + } while (fd < 0 && errno == EINTR); + + return fd; +} + ssize_t xread(int fd, void *buf, size_t count) NONNULL_ARGS; -ssize_t xwrite(int fd, const void *buf, size_t count) NONNULL_ARGS; +ssize_t xwrite(int fd, const void *buf, size_t count); +int xclose(int fd); #endif diff -Nru dte-1.9.1/src/util/xsnprintf.c dte-1.10/src/util/xsnprintf.c --- dte-1.9.1/src/util/xsnprintf.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/util/xsnprintf.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,7 +2,7 @@ #include #include #include "xsnprintf.h" -#include "../debug.h" +#include "debug.h" size_t xvsnprintf ( char *restrict buf, diff -Nru dte-1.9.1/src/util/xstdio.h dte-1.10/src/util/xstdio.h --- dte-1.9.1/src/util/xstdio.h 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/src/util/xstdio.h 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,41 @@ +#ifndef UTIL_XSTDIO_H +#define UTIL_XSTDIO_H + +#include +#include +#include "macros.h" +#include "xreadwrite.h" + +static inline FILE *xfopen(const char *path, const char *mode, int flags, mode_t mask) +{ + BUG_ON(mode[0] == '\0'); + bool plus = (mode[1] == '+'); + BUG_ON(mode[plus ? 2 : 1] != '\0'); + + switch (mode[0]) { + case 'a': + flags |= (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_APPEND; + break; + case 'r': + flags |= (plus ? O_RDWR : O_RDONLY); + break; + case 'w': + flags |= (plus ? O_RDWR : O_WRONLY) | O_CREAT | O_TRUNC; + break; + default: + BUG("Invalid fopen() mode string: '%s'", mode); + } + + int fd = xopen(path, flags, mask); + if (fd < 0) { + return NULL; + } + + FILE *file = fdopen(fd, mode); + if (!file) { + xclose(fd); + } + return file; +} + +#endif diff -Nru dte-1.9.1/src/view.c dte-1.10/src/view.c --- dte-1.9.1/src/view.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/view.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,9 @@ #include "view.h" #include "buffer.h" -#include "debug.h" #include "util/ascii.h" +#include "util/debug.h" #include "util/str-util.h" #include "util/utf8.h" -#include "util/xmalloc.h" #include "window.h" View *view; @@ -14,7 +13,6 @@ Buffer *b = v->buffer; Block *blk; size_t nl = 0; - block_for_each(blk, &b->blocks) { if (blk == v->cursor.blk) { nl += count_nl(blk->data, v->cursor.offset); @@ -23,23 +21,23 @@ } nl += blk->nl; } - BUG_ON(1); + BUG("unreachable"); } void view_update_cursor_x(View *v) { unsigned int tw = v->buffer->options.tab_width; size_t idx = 0; - LineRef lr; + StringView line; long c = 0; long w = 0; - v->cx = fetch_this_line(&v->cursor, &lr); + v->cx = fetch_this_line(&v->cursor, &line); while (idx < v->cx) { - CodePoint u = lr.line[idx++]; + CodePoint u = line.data[idx++]; c++; - if (u < 0x80) { - if (!ascii_iscntrl(u)) { + if (likely(u < 0x80)) { + if (likely(!ascii_iscntrl(u))) { w++; } else if (u == '\t') { w = (w + tw) / tw * tw; @@ -48,7 +46,7 @@ } } else { idx--; - u = u_get_nonascii(lr.line, lr.size, &idx); + u = u_get_nonascii(line.data, line.length, &idx); w += u_char_width(u); } } @@ -142,36 +140,38 @@ return v->buffer->views.count > 1; } -char *view_get_word_under_cursor(const View *v) +StringView view_get_word_under_cursor(const View *v) { - LineRef lr; - size_t i, ei, si = fetch_this_line(&v->cursor, &lr); - - while (si < lr.size) { - i = si; - if (u_is_word_char(u_get_char(lr.line, lr.size, &i))) { + StringView line; + size_t si = fetch_this_line(&v->cursor, &line); + while (si < line.length) { + size_t i = si; + if (u_is_word_char(u_get_char(line.data, line.length, &i))) { break; } si = i; } - if (si == lr.size) { - return NULL; + + if (si == line.length) { + return string_view(NULL, 0); } - ei = si; + size_t ei = si; while (si > 0) { - i = si; - if (!u_is_word_char(u_prev_char(lr.line, &i))) { + size_t i = si; + if (!u_is_word_char(u_prev_char(line.data, &i))) { break; } si = i; } - while (ei < lr.size) { - i = ei; - if (!u_is_word_char(u_get_char(lr.line, lr.size, &i))) { + + while (ei < line.length) { + size_t i = ei; + if (!u_is_word_char(u_get_char(line.data, line.length, &i))) { break; } ei = i; } - return xstrslice(lr.line, si, ei); + + return string_view(line.data + si, ei - si); } diff -Nru dte-1.9.1/src/view.h dte-1.10/src/view.h --- dte-1.9.1/src/view.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/view.h 2021-04-03 21:08:53.000000000 +0000 @@ -4,6 +4,7 @@ #include #include #include "block-iter.h" +#include "util/string-view.h" typedef enum { SELECT_NONE, @@ -69,6 +70,6 @@ void view_update(View *v); long view_get_preferred_x(View *v); bool view_can_close(const View *v); -char *view_get_word_under_cursor(const View *v); +StringView view_get_word_under_cursor(const View *v); #endif diff -Nru dte-1.9.1/src/window.c dte-1.10/src/window.c --- dte-1.9.1/src/window.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/window.c 2021-04-03 21:08:53.000000000 +0000 @@ -22,14 +22,13 @@ View *window_add_buffer(Window *w, Buffer *b) { View *v = xnew0(View, 1); - v->buffer = b; v->window = w; v->cursor.head = &b->blocks; v->cursor.blk = BLOCK(b->blocks.next); - ptr_array_add(&b->views, v); - ptr_array_add(&w->views, v); + ptr_array_append(&b->views, v); + ptr_array_append(&w->views, v); w->update_tabbar = true; return v; } @@ -45,30 +44,29 @@ bool must_exist, const Encoding *encoding ) { - char *absolute; - bool dir_missing = false; - Buffer *b = NULL; - if (filename[0] == '\0') { error_msg("Empty filename not allowed"); return NULL; } - absolute = path_absolute(filename); - if (absolute == NULL) { - // Let load_buffer() create error message - dir_missing = errno == ENOENT; - } else { + + bool dir_missing = false; + char *absolute = path_absolute(filename); + if (absolute) { // Already open? - b = find_buffer(absolute); - } - if (b) { - if (!streq(absolute, b->abs_filename)) { - char *s = short_filename(absolute); - info_msg("%s and %s are the same file", s, b->display_filename); - free(s); + Buffer *b = find_buffer(absolute); + if (b) { + if (!streq(absolute, b->abs_filename)) { + const char *bufname = buffer_filename(b); + char *s = short_filename(absolute); + info_msg("%s and %s are the same file", s, bufname); + free(s); + } + free(absolute); + return window_get_view(w, b); } - free(absolute); - return window_get_view(w, b); + } else { + // Let load_buffer() create error message + dir_missing = (errno == ENOENT); } /* @@ -90,45 +88,49 @@ # this should still succeed dte /proc/$(pidof tail)/fd/3 */ - b = buffer_new(encoding); - if (load_buffer(b, must_exist, filename)) { + + Buffer *b = buffer_new(encoding); + if (!load_buffer(b, must_exist, filename)) { free_buffer(b); free(absolute); return NULL; } - if (b->st.st_mode == 0 && dir_missing) { + if (b->file.mode == 0 && dir_missing) { // New file in non-existing directory. This is usually a mistake. error_msg("Error opening %s: Directory does not exist", filename); free_buffer(b); free(absolute); return NULL; } - b->abs_filename = absolute; - if (b->abs_filename == NULL) { + + if (absolute) { + b->abs_filename = absolute; + } else { // FIXME: obviously wrong b->abs_filename = xstrdup(filename); } update_short_filename(b); if (editor.options.lock_files) { - if (lock_file(b->abs_filename)) { + if (!lock_file(b->abs_filename)) { b->readonly = true; } else { b->locked = true; } } - if (b->st.st_mode != 0 && !b->readonly && access(filename, W_OK)) { + + if (b->file.mode != 0 && !b->readonly && access(filename, W_OK)) { error_msg("No write permission to %s, marking read-only.", filename); b->readonly = true; } + return window_add_buffer(w, b); } View *window_get_view(Window *w, Buffer *b) { View *v = window_find_view(w, b); - - if (v == NULL) { + if (!v) { // Open the buffer in other window to this window v = window_add_buffer(w, b); v->cursor = ((View *)b->views.ptrs[0])->cursor; @@ -148,15 +150,15 @@ return NULL; } -View *window_find_unclosable_view(Window *w, bool (*can_close)(const View *)) +View *window_find_unclosable_view(Window *w) { // Check active view first - if (w->view != NULL && !can_close(w->view)) { + if (w->view && !view_can_close(w->view)) { return w->view; } for (size_t i = 0, n = w->views.count; i < n; i++) { View *v = w->views.ptrs[i]; - if (!can_close(v)) { + if (!view_can_close(v)) { return v; } } @@ -181,23 +183,23 @@ } // Remove view from v->window and v->buffer->views and free it. -void remove_view(View *v) +size_t remove_view(View *v) { Window *w = v->window; - Buffer *b = v->buffer; - if (v == w->prev_view) { w->prev_view = NULL; } - // FIXME: globals if (v == view) { view = NULL; buffer = NULL; } - ptr_array_remove(&w->views, v); + size_t idx = ptr_array_idx(&w->views, v); + BUG_ON(idx >= w->views.count); + ptr_array_remove_idx(&w->views, idx); w->update_tabbar = true; + Buffer *b = v->buffer; ptr_array_remove(&b->views, v); if (b->views.count == 0) { if (b->options.file_history && b->abs_filename) { @@ -205,35 +207,15 @@ } free_buffer(b); } - free(v); -} - -void window_close_current(void) -{ - Window *next; - - if (window->frame->parent == NULL) { - // Don't close last window - window_remove_views(window); - set_view(window_open_empty_buffer(window)); - return; - } - - next = next_window(window); - remove_frame(window->frame); - window = NULL; - set_view(next->view); - mark_everything_changed(); - debug_frames(); + free(v); + return idx; } void window_close_current_view(Window *w) { - size_t idx = ptr_array_idx(&w->views, w->view); - - remove_view(w->view); - if (w->prev_view != NULL) { + size_t idx = remove_view(w->view); + if (w->prev_view) { w->view = w->prev_view; w->prev_view = NULL; return; @@ -263,22 +245,18 @@ } // Forget previous view when changing view using any other command but open - if (window != NULL) { + if (window) { window->prev_view = NULL; } view = v; buffer = v->buffer; window = v->window; - window->view = v; if (!v->buffer->setup) { buffer_setup(v->buffer); - if ( - v->buffer->options.file_history - && v->buffer->abs_filename != NULL - ) { + if (v->buffer->options.file_history && v->buffer->abs_filename) { restore_cursor_from_history(v); } } @@ -305,32 +283,24 @@ { View *prev = w->view; View *v = window_open_empty_buffer(w); - - // FIXME: should not call set_view() set_view(v); w->prev_view = prev; return v; } -/* -If window contains only one buffer and it is untouched then it will be -closed after opening another file. This is done because closing last -buffer causes an empty buffer to be opened (window must contain at least -one buffer). -*/ -static bool is_useless_empty_view(View *v) +// If window contains only one untouched buffer it'll be closed after +// opening another file. This is done because closing the last buffer +// causes an empty buffer to be opened (windows must contain at least +// one buffer). +static bool is_useless_empty_view(const View *v) { - if (v == NULL) { + if (v == NULL || v->window->views.count != 1) { return false; } - if (v->window->views.count != 1) { + if (v->buffer->abs_filename || v->buffer->change_head.nr_prev != 0) { return false; } - // Touched? - if ( - v->buffer->abs_filename != NULL - || v->buffer->change_head.nr_prev != 0 - ) { + if (v->buffer->display_filename) { return false; } return true; @@ -341,17 +311,13 @@ View *prev = w->view; bool useless = is_useless_empty_view(prev); View *v = window_open_buffer(w, filename, false, encoding); - - if (v == NULL) { - return NULL; - } - - // FIXME: should not call set_view() - set_view(v); - if (useless) { - remove_view(prev); - } else { - w->prev_view = prev; + if (v) { + set_view(v); + if (useless) { + remove_view(prev); + } else { + w->prev_view = prev; + } } return v; } @@ -364,7 +330,6 @@ for (size_t i = 0; filenames[i]; i++) { View *v = window_open_buffer(w, filenames[i], false, encoding); if (v && first) { - // FIXME: should not call set_view() set_view(v); first = false; } @@ -382,59 +347,13 @@ } } -static int calc_vertical_tabbar_width(const Window *win) -{ - // Line numbers are included in min_edit_w - int min_edit_w = 80; - int w = editor.options.tab_bar_width; - - if (win->w - w < min_edit_w) { - w = win->w - min_edit_w; - } - if (w < TAB_BAR_MIN_WIDTH) { - w = 0; - } - return w; -} - -TabBarMode tabbar_visibility(const Window *win) -{ - switch (editor.options.tab_bar) { - case TAB_BAR_HIDDEN: - case TAB_BAR_HORIZONTAL: - return editor.options.tab_bar; - case TAB_BAR_VERTICAL: - if (calc_vertical_tabbar_width(win) == 0) { - // Not enough space - return TAB_BAR_HIDDEN; - } - return TAB_BAR_VERTICAL; - case TAB_BAR_AUTO: - if (calc_vertical_tabbar_width(win) == 0) { - // Not enough space - return TAB_BAR_HORIZONTAL; - } - return TAB_BAR_VERTICAL; - } - return 0; -} - -int vertical_tabbar_width(const Window *win) -{ - if (tabbar_visibility(win) == TAB_BAR_VERTICAL) { - return calc_vertical_tabbar_width(win); - } - return 0; -} - static int line_numbers_width(const Window *win) { int w = 0; if (editor.options.show_line_numbers && win->view) { - const int min_w = 5; - w = number_width(win->view->buffer->nl) + 1; - if (w < min_w) { - w = min_w; + w = size_str_width(win->view->buffer->nl) + 1; + if (w < LINE_NUMBERS_MIN_WIDTH) { + w = LINE_NUMBERS_MIN_WIDTH; } } return w; @@ -442,21 +361,18 @@ static int edit_x_offset(const Window *win) { - return line_numbers_width(win) + vertical_tabbar_width(win); + return line_numbers_width(win); } -static int edit_y_offset(const Window *win) +static int edit_y_offset(void) { - if (tabbar_visibility(win) == TAB_BAR_HORIZONTAL) { - return 1; - } - return 0; + return editor.options.tab_bar ? 1 : 0; } static void set_edit_size(Window *win) { int xo = edit_x_offset(win); - int yo = edit_y_offset(win); + int yo = edit_y_offset(); win->edit_w = win->w - xo; win->edit_h = win->h - yo - 1; // statusline @@ -481,7 +397,7 @@ win->x = x; win->y = y; win->edit_x = x + edit_x_offset(win); - win->edit_y = y + edit_y_offset(win); + win->edit_y = y + edit_y_offset(); } void set_window_size(Window *win, int w, int h) @@ -506,11 +422,11 @@ void (*func)(Window *, void *), void *data ) { - if (f->window != NULL) { + if (f->window) { func(f->window, data); return; } - for (size_t i = 0; i < f->frames.count; i++) { + for (size_t i = 0, n = f->frames.count; i < n; i++) { frame_for_each_window(f->frames.ptrs[i], func, data); } } @@ -540,25 +456,70 @@ UNIGNORE_WARNINGS -static void collect_window(Window *w, void *data) -{ - ptr_array_add(data, w); +typedef struct { + const Window *const target; // Window to search for (set at init.) + Window *first; // Window passed in first callback invocation + Window *last; // Window passed in last callback invocation + Window *prev; // Window immediately before target (if any) + Window *next; // Window immediately after target (if any) + bool found; // Set to true when target is found +} WindowCallbackData; + +static void find_prev_and_next(Window *w, void *ud) +{ + WindowCallbackData *data = ud; + data->last = w; + if (data->found) { + if (!data->next) { + data->next = w; + } + return; + } + if (!data->first) { + data->first = w; + } + if (w == data->target) { + data->found = true; + return; + } + data->prev = w; } Window *prev_window(Window *w) { - PointerArray windows = PTR_ARRAY_INIT; - for_each_window_data(collect_window, &windows); - w = ptr_array_prev(&windows, w); - free(windows.ptrs); - return w; + WindowCallbackData data = {.target = w}; + for_each_window_data(find_prev_and_next, &data); + BUG_ON(!data.found); + return data.prev ? data.prev : data.last; } Window *next_window(Window *w) { - PointerArray windows = PTR_ARRAY_INIT; - for_each_window_data(collect_window, &windows); - w = ptr_array_next(&windows, w); - free(windows.ptrs); - return w; + WindowCallbackData data = {.target = w}; + for_each_window_data(find_prev_and_next, &data); + BUG_ON(!data.found); + return data.next ? data.next : data.first; +} + +void window_close_current(void) +{ + if (!window->frame->parent) { + // Don't close last window + window_remove_views(window); + set_view(window_open_empty_buffer(window)); + return; + } + + WindowCallbackData data = {.target = window}; + for_each_window_data(find_prev_and_next, &data); + BUG_ON(!data.found); + Window *next_or_prev = data.next ? data.next : data.prev; + BUG_ON(!next_or_prev); + + remove_frame(window->frame); + window = NULL; + set_view(next_or_prev->view); + + mark_everything_changed(); + debug_frames(); } diff -Nru dte-1.9.1/src/window.h dte-1.10/src/window.h --- dte-1.9.1/src/window.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/src/window.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,12 @@ #ifndef WINDOW_H #define WINDOW_H +#include +#include #include "buffer.h" +#include "encoding.h" #include "frame.h" +#include "util/ptr-array.h" #include "view.h" typedef struct Window { @@ -36,15 +40,20 @@ extern Window *window; +enum { + // Minimum width of line numbers bar (including padding) + LINE_NUMBERS_MIN_WIDTH = 5 +}; + Window *new_window(void); View *window_add_buffer(Window *w, Buffer *b); View *window_open_empty_buffer(Window *w); View *window_open_buffer(Window *w, const char *filename, bool must_exist, const Encoding *encoding); View *window_get_view(Window *w, Buffer *b); View *window_find_view(Window *w, Buffer *b); -View *window_find_unclosable_view(Window *w, bool (*can_close)(const View *)); +View *window_find_unclosable_view(Window *w); void window_free(Window *w); -void remove_view(View *v); +size_t remove_view(View *v); void window_close_current(void); void window_close_current_view(Window *w); void set_view(View *v); @@ -52,8 +61,6 @@ View *window_open_file(Window *w, const char *filename, const Encoding *encoding); void window_open_files(Window *w, char **filenames, const Encoding *encoding); void mark_buffer_tabbars_changed(Buffer *b); -TabBarMode tabbar_visibility(const Window *win); -int vertical_tabbar_width(const Window *win); void calculate_line_numbers(Window *win); void set_window_coordinates(Window *win, int x, int y); void set_window_size(Window *win, int w, int h); diff -Nru dte-1.9.1/test/bind.c dte-1.10/test/bind.c --- dte-1.9.1/test/bind.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/bind.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,68 @@ +#include "test.h" +#include "bind.h" +#include "block.h" +#include "buffer.h" +#include "change.h" +#include "commands.h" +#include "util/str-util.h" + +static void test_add_binding(void) +{ + KeyCode key = MOD_CTRL | MOD_SHIFT | KEY_F12; + const KeyBinding *bind = lookup_binding(key); + EXPECT_NULL(bind); + + const Command *insert_cmd = find_normal_command("insert"); + ASSERT_NONNULL(insert_cmd); + + const char *key_str = "C-S-F12"; + const char *cmd_str = "insert xyz"; + add_binding(key_str, cmd_str); + bind = lookup_binding(key); + ASSERT_NONNULL(bind); + EXPECT_PTREQ(bind->cmd, insert_cmd); + EXPECT_EQ(bind->a.nr_args, 1); + EXPECT_EQ(bind->a.nr_flags, 0); + EXPECT_STREQ(bind->cmd_str, cmd_str); + + remove_binding(key_str); + EXPECT_NULL(lookup_binding(key)); +} + +static void test_handle_binding(void) +{ + const Command *insert = find_normal_command("insert"); + ASSERT_NONNULL(insert); + + handle_command(&commands, "bind C-S-F11 'insert zzz'; open", false); + + // Bound command should be cached + KeyCode key = MOD_CTRL | MOD_SHIFT | KEY_F11; + const KeyBinding *binding = lookup_binding(key); + ASSERT_NONNULL(binding); + EXPECT_PTREQ(binding->cmd, insert); + EXPECT_EQ(binding->a.nr_flags, 0); + EXPECT_EQ(binding->a.nr_args, 1); + EXPECT_STREQ(binding->a.args[0], "zzz"); + EXPECT_NULL(binding->a.args[1]); + + handle_binding(key); + const Block *block = BLOCK(buffer->blocks.next); + ASSERT_NONNULL(block); + ASSERT_EQ(block->size, 4); + EXPECT_EQ(block->nl, 1); + EXPECT_TRUE(mem_equal(block->data, "zzz\n", 4)); + + EXPECT_TRUE(undo()); + EXPECT_EQ(block->size, 0); + EXPECT_EQ(block->nl, 0); + EXPECT_FALSE(undo()); + handle_command(&commands, "close", false); +} + +static const TestEntry tests[] = { + TEST(test_add_binding), + TEST(test_handle_binding), +}; + +const TestGroup bind_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/check-opts.sh dte-1.10/test/check-opts.sh --- dte-1.9.1/test/check-opts.sh 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/check-opts.sh 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,36 @@ +#!/bin/sh +set -e + +# Bash doesn't expand aliases in non-interactive shells, unless the +# expand_aliases option is enabled or it's running in "POSIX mode": +export POSIXLY_CORRECT=1 + +error() { + echo "$0:$1: Error: $2" >&2 + exit 1 +} + +assert_streq() { + if test "$2" != "$3"; then + error "$1" "strings not equal: '$2', '$3'" + fi +} + +alias abort='error "$LINENO"' +alias check='assert_streq "$LINENO"' + +if test "$#" != 2; then + abort 'exactly 2 arguments required' +fi + +dte="$1" +ver="$2" + +test -x "$dte" || abort "argument #1 ('$dte') not executable" + +check "$($dte -V | head -n1)" "dte $ver" +check "$($dte -h | head -n1)" "Usage: $dte [OPTIONS] [[+LINE] FILE]..." +check "$($dte -B | head -n1)" "rc" +check "$($dte -b rc)" "$(cat config/rc)" + +$dte -s config/syntax/dte >/dev/null diff -Nru dte-1.9.1/test/cmdline.c dte-1.10/test/cmdline.c --- dte-1.9.1/test/cmdline.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/cmdline.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,8 @@ #include #include "test.h" -#include "../src/cmdline.h" -#include "../src/completion.h" -#include "../src/editor.h" +#include "cmdline.h" +#include "completion.h" +#include "editor.h" #define EXPECT_STRING_EQ(s, cstr) \ EXPECT_STREQ(string_borrow_cstring(&(s)), (cstr)) @@ -10,7 +10,7 @@ static void test_cmdline_handle_key(void) { CommandLine *c = &editor.cmdline; - PointerArray *h = &editor.command_history; + History *h = &editor.command_history; int ret = cmdline_handle_key(c, h, 'a'); EXPECT_EQ(ret, CMDLINE_KEY_HANDLED); @@ -126,37 +126,87 @@ static void test_complete_command(void) { - complete_command(); + complete_command_next(); EXPECT_STRING_EQ(editor.cmdline.buf, "alias"); reset_completion(); cmdline_set_text(&editor.cmdline, "wrap"); - complete_command(); + complete_command_next(); EXPECT_STRING_EQ(editor.cmdline.buf, "wrap-paragraph "); reset_completion(); cmdline_set_text(&editor.cmdline, "open test/data/.e"); - complete_command(); + complete_command_next(); EXPECT_STRING_EQ(editor.cmdline.buf, "open test/data/.editorconfig "); reset_completion(); cmdline_set_text(&editor.cmdline, "toggle "); - complete_command(); + complete_command_next(); EXPECT_STRING_EQ(editor.cmdline.buf, "toggle auto-indent"); reset_completion(); + cmdline_set_text(&editor.cmdline, "set expand-tab f"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set expand-tab false "); + reset_completion(); + + cmdline_set_text(&editor.cmdline, "set case-sensitive-search a"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set case-sensitive-search auto "); + reset_completion(); + ASSERT_EQ(setenv(ENV_VAR_NAME, "xyz", true), 0); cmdline_set_text(&editor.cmdline, "insert $" ENV_VAR_PREFIX); - complete_command(); + complete_command_next(); EXPECT_STRING_EQ(editor.cmdline.buf, "insert $" ENV_VAR_NAME); reset_completion(); ASSERT_EQ(unsetenv(ENV_VAR_NAME), 0); -} -DISABLE_WARNING("-Wmissing-prototypes") + cmdline_clear(&editor.cmdline); + complete_command_prev(); + EXPECT_STRING_EQ(editor.cmdline.buf, "alias"); + complete_command_prev(); + EXPECT_STRING_EQ(editor.cmdline.buf, "wswap"); + complete_command_prev(); + EXPECT_STRING_EQ(editor.cmdline.buf, "wsplit"); + reset_completion(); -void test_cmdline(void) -{ - test_cmdline_handle_key(); - test_complete_command(); + cmdline_set_text(&editor.cmdline, "hi default "); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "hi default black"); + complete_command_prev(); + EXPECT_STRING_EQ(editor.cmdline.buf, "hi default yellow"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "hi default black"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "hi default blink"); + reset_completion(); + + cmdline_set_text(&editor.cmdline, "show op"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "show option "); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "show option auto-indent"); + reset_completion(); + + cmdline_set_text(&editor.cmdline, "set ws-error tr"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set ws-error trailing "); + reset_completion(); + + cmdline_set_text(&editor.cmdline, "set ws-error special,tab-"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set ws-error special,tab-after-indent"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set ws-error special,tab-indent"); + complete_command_next(); + EXPECT_STRING_EQ(editor.cmdline.buf, "set ws-error special,tab-after-indent"); + reset_completion(); } + +static const TestEntry tests[] = { + TEST(test_cmdline_handle_key), + TEST(test_complete_command), +}; + +const TestGroup cmdline_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/command.c dte-1.10/test/command.c --- dte-1.9.1/test/command.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/command.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,38 +1,135 @@ +#include #include "test.h" -#include "../src/command.h" -#include "../src/debug.h" -#include "../src/parse-args.h" +#include "command/args.h" +#include "command/env.h" +#include "command/parse.h" +#include "command/run.h" +#include "command/serialize.h" +#include "commands.h" +#include "editor.h" +#include "util/ascii.h" +#include "util/debug.h" static void test_parse_command_arg(void) { - #define PARSE_ARG_LITERAL(s) parse_command_arg(s, STRLEN(s), false) + // Single, unquoted argument + char *arg = parse_command_arg(STRN("arg"), false); + EXPECT_STREQ(arg, "arg"); + free(arg); + + // Two unquoted, space-separated arguments + arg = parse_command_arg(STRN("hello world"), false); + EXPECT_STREQ(arg, "hello"); + free(arg); - char *arg = PARSE_ARG_LITERAL(""); + // Two unquoted, tab-separated arguments + arg = parse_command_arg(STRN("hello\tworld"), false); + EXPECT_STREQ(arg, "hello"); + free(arg); + + // Unquoted argument preceded by whitespace + arg = parse_command_arg(STRN(" x"), false); EXPECT_STREQ(arg, ""); free(arg); - arg = PARSE_ARG_LITERAL("\"\\u148A\"xyz'foo'\"\\x5A\"\\;\tbar"); - EXPECT_STREQ(arg, "\xE1\x92\x8AxyzfooZ;"); + // Single-quoted argument, including whitespace + arg = parse_command_arg(STRN("' foo ' .."), false); + EXPECT_STREQ(arg, " foo "); + free(arg); + + // Several adjacent, quoted strings forming a single argument + arg = parse_command_arg(STRN("\"foo\"'bar'' baz '\"etc\"."), false); + EXPECT_STREQ(arg, "foobar baz etc."); + free(arg); + + // Control character escapes in a double-quoted string + arg = parse_command_arg(STRN("\"\\a\\b\\t\\n\\v\\f\\r\""), false); + EXPECT_STREQ(arg, "\a\b\t\n\v\f\r"); + free(arg); + + // Backslash escape sequence in a double-quoted string + arg = parse_command_arg(STRN("\"\\\\\""), false); + EXPECT_STREQ(arg, "\\"); + free(arg); + + // Double-quote escape sequence in a double-quoted string + arg = parse_command_arg(STRN("\"\\\"\""), false); + EXPECT_STREQ(arg, "\""); + free(arg); + + // Escape character escape sequence in a double-quoted string + arg = parse_command_arg(STRN("\"\\e[0m\""), false); + EXPECT_STREQ(arg, "\033[0m"); + free(arg); + + // Unrecognized escape sequence in a double-quoted string + arg = parse_command_arg(STRN("\"\\z\""), false); + EXPECT_STREQ(arg, "\\z"); + free(arg); + + // Hexadecimal escape sequences in a double-quoted string + arg = parse_command_arg(STRN("\"\\x1B[31m\\x7E\\x2f\\x1b[0m\""), false); + EXPECT_STREQ(arg, "\x1B[31m~/\x1B[0m"); + free(arg); + + // 4-digit Unicode escape sequence + arg = parse_command_arg(STRN("\"\\u148A\""), false); + EXPECT_STREQ(arg, "\xE1\x92\x8A"); free(arg); - #undef PARSE_ARG_LITERAL + // 8-digit Unicode escape sequence + arg = parse_command_arg(STRN("\"\\U0001F4A4\""), false); + EXPECT_STREQ(arg, "\xF0\x9F\x92\xA4"); + free(arg); + + // "\U" escape sequence terminated by non-hexadecimal character + arg = parse_command_arg(STRN("\"\\U1F4A4...\""), false); + EXPECT_STREQ(arg, "\xF0\x9F\x92\xA4..."); + free(arg); + + // Unsupported, escape-like sequences in a single-quoted string + arg = parse_command_arg(STRN("'\\t\\n'"), false); + EXPECT_STREQ(arg, "\\t\\n"); + free(arg); + + // Single-quoted, empty string + arg = parse_command_arg(STRN("''"), false); + EXPECT_STREQ(arg, ""); + free(arg); + + // Double-quoted, empty string + arg = parse_command_arg(STRN("\"\""), false); + EXPECT_STREQ(arg, ""); + free(arg); + + // NULL input with zero length + arg = parse_command_arg(NULL, 0, false); + EXPECT_STREQ(arg, ""); + free(arg); + + // Empty input + arg = parse_command_arg("", 1, false); + EXPECT_STREQ(arg, ""); + free(arg); + + arg = parse_command_arg(STRN("\"\\u148A\"xyz'foo'\"\\x5A\"\\;\t."), false); + EXPECT_STREQ(arg, "\xE1\x92\x8AxyzfooZ;"); + free(arg); } static void test_parse_commands(void) { PointerArray array = PTR_ARRAY_INIT; - CommandParseError err = 0; - EXPECT_TRUE(parse_commands(&array, " left -c;;", &err)); + EXPECT_EQ(parse_commands(&array, " left -c;;"), CMDERR_NONE); EXPECT_EQ(array.count, 5); EXPECT_STREQ(array.ptrs[0], "left"); EXPECT_STREQ(array.ptrs[1], "-c"); EXPECT_NULL(array.ptrs[2]); EXPECT_NULL(array.ptrs[3]); EXPECT_NULL(array.ptrs[4]); - EXPECT_EQ(err, 0); ptr_array_free(&array); - EXPECT_TRUE(parse_commands(&array, "save -e UTF-8 file.c; close -q", &err)); + EXPECT_EQ(parse_commands(&array, "save -e UTF-8 file.c; close -q"), CMDERR_NONE); EXPECT_EQ(array.count, 8); EXPECT_STREQ(array.ptrs[0], "save"); EXPECT_STREQ(array.ptrs[1], "-e"); @@ -42,111 +139,298 @@ EXPECT_STREQ(array.ptrs[5], "close"); EXPECT_STREQ(array.ptrs[6], "-q"); EXPECT_NULL(array.ptrs[7]); - EXPECT_EQ(err, 0); ptr_array_free(&array); - EXPECT_TRUE(parse_commands(&array, "\n ; ; \t\n ", &err)); + EXPECT_EQ(parse_commands(&array, "\n ; ; \t\n "), CMDERR_NONE); EXPECT_EQ(array.count, 3); EXPECT_NULL(array.ptrs[0]); EXPECT_NULL(array.ptrs[1]); EXPECT_NULL(array.ptrs[2]); - EXPECT_EQ(err, 0); ptr_array_free(&array); - EXPECT_TRUE(parse_commands(&array, "", &err)); + EXPECT_EQ(parse_commands(&array, ""), CMDERR_NONE); EXPECT_EQ(array.count, 1); EXPECT_NULL(array.ptrs[0]); - EXPECT_EQ(err, 0); ptr_array_free(&array); - EXPECT_FALSE(parse_commands(&array, "insert '... ", &err)); - EXPECT_EQ(err, CMDERR_UNCLOSED_SINGLE_QUOTE); + EXPECT_EQ(parse_commands(&array, "insert '... "), CMDERR_UNCLOSED_SQUOTE); ptr_array_free(&array); - err = 0; - EXPECT_FALSE(parse_commands(&array, "insert \" ", &err)); - EXPECT_EQ(err, CMDERR_UNCLOSED_DOUBLE_QUOTE); + EXPECT_EQ(parse_commands(&array, "insert \" "), CMDERR_UNCLOSED_DQUOTE); ptr_array_free(&array); - err = 0; - EXPECT_FALSE(parse_commands(&array, "insert \"\\\" ", &err)); - EXPECT_EQ(err, CMDERR_UNCLOSED_DOUBLE_QUOTE); + EXPECT_EQ(parse_commands(&array, "insert \"\\\" "), CMDERR_UNCLOSED_DQUOTE); ptr_array_free(&array); - err = 0; - EXPECT_FALSE(parse_commands(&array, "insert \\", &err)); - EXPECT_EQ(err, CMDERR_UNEXPECTED_EOF); + EXPECT_EQ(parse_commands(&array, "insert \\"), CMDERR_UNEXPECTED_EOF); ptr_array_free(&array); } +static void test_command_parse_error_to_string(void) +{ + const char *str = command_parse_error_to_string(CMDERR_UNCLOSED_SQUOTE); + EXPECT_STREQ(str, "unclosed '"); + str = command_parse_error_to_string(CMDERR_UNCLOSED_DQUOTE); + EXPECT_STREQ(str, "unclosed \""); + str = command_parse_error_to_string(CMDERR_UNEXPECTED_EOF); + EXPECT_STREQ(str, "unexpected EOF"); +} + +static void test_expand_builtin_env(void) +{ + char *value = NULL; + EXPECT_TRUE(expand_builtin_env("DTE_HOME", &value)); + EXPECT_STREQ(value, editor.user_config_dir); + free(value); + + EXPECT_TRUE(expand_builtin_env("FILE", &value)); + EXPECT_NULL(value); + EXPECT_TRUE(expand_builtin_env("FILETYPE", &value)); + EXPECT_NULL(value); + EXPECT_TRUE(expand_builtin_env("LINENO", &value)); + EXPECT_NULL(value); + EXPECT_TRUE(expand_builtin_env("WORD", &value)); + EXPECT_NULL(value); +} + +static void test_find_normal_command(void) +{ + const Command *cmd = find_normal_command("alias"); + ASSERT_NONNULL(cmd); + EXPECT_STREQ(cmd->name, "alias"); + + cmd = find_normal_command("bind"); + ASSERT_NONNULL(cmd); + EXPECT_STREQ(cmd->name, "bind"); + + cmd = find_normal_command("wswap"); + ASSERT_NONNULL(cmd); + EXPECT_STREQ(cmd->name, "wswap"); + + EXPECT_NULL(find_normal_command("alia")); + EXPECT_NULL(find_normal_command("aliass")); + EXPECT_NULL(find_normal_command("Alias")); + EXPECT_NULL(find_normal_command("bin ")); + EXPECT_NULL(find_normal_command("bind ")); + EXPECT_NULL(find_normal_command(" bind")); + EXPECT_NULL(find_normal_command("bind!")); + EXPECT_NULL(find_normal_command("bind\n")); +} + static void test_parse_args(void) { const char *cmd_str = "open -g file.c file.h *.mk -e UTF-8"; PointerArray array = PTR_ARRAY_INIT; - CommandParseError err = 0; - ASSERT_TRUE(parse_commands(&array, cmd_str, &err)); + ASSERT_EQ(parse_commands(&array, cmd_str), CMDERR_NONE); ASSERT_EQ(array.count, 8); - const Command *cmd = find_command(commands, array.ptrs[0]); + const Command *cmd = find_normal_command(array.ptrs[0]); ASSERT_NONNULL(cmd); EXPECT_STREQ(cmd->name, "open"); CommandArgs a = {.args = (char**)array.ptrs + 1}; - ASSERT_TRUE(parse_args(cmd, &a)); + ASSERT_EQ(do_parse_args(cmd, &a), 0); EXPECT_EQ(a.nr_flags, 2); EXPECT_EQ(a.flags[0], 'g'); EXPECT_EQ(a.flags[1], 'e'); EXPECT_EQ(a.flags[2], '\0'); - EXPECT_EQ(a.nr_args, 3); + EXPECT_TRUE(cmdargs_has_flag(&a, 'g')); + EXPECT_TRUE(cmdargs_has_flag(&a, 'e')); + EXPECT_FALSE(cmdargs_has_flag(&a, 'f')); + EXPECT_FALSE(cmdargs_has_flag(&a, 'E')); + EXPECT_FALSE(cmdargs_has_flag(&a, '0')); + EXPECT_EQ(a.nr_flag_args, 1); EXPECT_STREQ(a.args[0], "UTF-8"); + EXPECT_EQ(a.nr_args, 3); EXPECT_STREQ(a.args[1], "file.c"); EXPECT_STREQ(a.args[2], "file.h"); EXPECT_STREQ(a.args[3], "*.mk"); EXPECT_NULL(a.args[4]); ptr_array_free(&array); -} + EXPECT_NULL(array.ptrs); + EXPECT_EQ(array.alloc, 0); + EXPECT_EQ(array.count, 0); -static void test_commands_array(void) -{ - static const size_t name_size = ARRAY_COUNT(commands[0].name); - static const size_t flags_size = ARRAY_COUNT(commands[0].flags); + cmd_str = "bind 1 2 3 4 5 -6"; + ASSERT_EQ(parse_commands(&array, cmd_str), CMDERR_NONE); + ASSERT_EQ(array.count, 8); + EXPECT_STREQ(array.ptrs[0], "bind"); + EXPECT_STREQ(array.ptrs[1], "1"); + EXPECT_STREQ(array.ptrs[6], "-6"); + EXPECT_NULL(array.ptrs[7]); - size_t n = 0; - while (commands[n].cmd) { - n++; - BUG_ON(n > 500); - } + cmd = find_normal_command(array.ptrs[0]); + ASSERT_NONNULL(cmd); + EXPECT_STREQ(cmd->name, "bind"); + EXPECT_EQ(cmd->max_args, 2); + EXPECT_EQ(cmd->flags[0], '-'); + + a = (CommandArgs){.args = (char**)array.ptrs + 1, .flags = "TEST"}; + ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_TOO_MANY_ARGUMENTS); + EXPECT_EQ(a.nr_args, 6); + EXPECT_EQ(a.nr_flags, 0); + EXPECT_EQ(a.nr_flag_args, 0); + EXPECT_EQ(a.flags[0], '\0'); + EXPECT_EQ(a.flag_set, 0); + ptr_array_free(&array); - for (size_t i = 1; i < n; i++) { - const Command *const cmd = &commands[i]; + cmd_str = "open \"-\\xff\""; + ASSERT_EQ(parse_commands(&array, cmd_str), CMDERR_NONE); + ASSERT_EQ(array.count, 3); + EXPECT_STREQ(array.ptrs[0], "open"); + EXPECT_STREQ(array.ptrs[1], "-\xff"); + EXPECT_NULL(array.ptrs[2]); - // Check that fixed-size arrays are null-terminated within bounds - ASSERT_EQ(cmd->name[name_size - 1], '\0'); - ASSERT_EQ(cmd->flags[flags_size - 1], '\0'); + cmd = find_normal_command(array.ptrs[0]); + ASSERT_NONNULL(cmd); + EXPECT_STREQ(cmd->name, "open"); - // Check that array is sorted by name field, in binary searchable order - IEXPECT_TRUE(strcmp(cmd->name, commands[i - 1].name) > 0); - } + a = (CommandArgs){.args = (char**)array.ptrs + 1}; + ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_INVALID_OPTION | 0xFF00); + EXPECT_EQ(a.nr_args, 0); + EXPECT_EQ(a.nr_flags, 0); + EXPECT_EQ(a.nr_flag_args, 0); + EXPECT_EQ(a.flags[0], '\0'); + EXPECT_EQ(a.flag_set, 0); + ptr_array_free(&array); + + ASSERT_EQ(parse_commands(&array, "save -e"), CMDERR_NONE); + ASSERT_EQ(array.count, 3); + cmd = find_normal_command(array.ptrs[0]); + ASSERT_NONNULL(cmd); + a = (CommandArgs){.args = (char**)array.ptrs + 1}; + ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_OPTION_ARGUMENT_MISSING | 0x6500); + ptr_array_free(&array); + + ASSERT_EQ(parse_commands(&array, "save -eUTF-8"), CMDERR_NONE); + ASSERT_EQ(array.count, 3); + cmd = find_normal_command(array.ptrs[0]); + ASSERT_NONNULL(cmd); + a = (CommandArgs){.args = (char**)array.ptrs + 1}; + ASSERT_EQ(do_parse_args(cmd, &a), ARGERR_OPTION_ARGUMENT_NOT_SEPARATE | 0x6500); + ptr_array_free(&array); +} + +static void test_escape_command_arg(void) +{ + char *str = escape_command_arg("arg", false); + EXPECT_STREQ(str, "arg"); + free(str); + + str = escape_command_arg("arg-x.y:z", false); + EXPECT_STREQ(str, "arg-x.y:z"); + free(str); + + str = escape_command_arg("", false); + EXPECT_STREQ(str, "''"); + free(str); + + str = escape_command_arg(" ", false); + EXPECT_STREQ(str, "' '"); + free(str); + + str = escape_command_arg("hello world", false); + EXPECT_STREQ(str, "'hello world'"); + free(str); + + str = escape_command_arg("line1\nline2\n", false); + EXPECT_STREQ(str, "\"line1\\nline2\\n\""); + free(str); + + str = escape_command_arg(" \t\r\n\x1F\x7F", false); + EXPECT_STREQ(str, "\" \\t\\r\\n\\x1F\\x7F\""); + free(str); + + str = escape_command_arg("x ' y", false); + EXPECT_STREQ(str, "\"x ' y\""); + free(str); + + str = escape_command_arg("\033[P", false); + EXPECT_STREQ(str, "\"\\e[P\""); + free(str); + + str = escape_command_arg("\"''\"", false); + EXPECT_STREQ(str, "\"\\\"''\\\"\""); + free(str); + + str = escape_command_arg("~/file with spaces", false); + EXPECT_STREQ(str, "~/'file with spaces'"); + free(str); + str = escape_command_arg("~/file with spaces", true); + EXPECT_STREQ(str, "'~/file with spaces'"); + free(str); + + str = escape_command_arg("~/need \t\ndquotes", false); + EXPECT_STREQ(str, "~/\"need \\t\\ndquotes\""); + free(str); + str = escape_command_arg("~/need \t\ndquotes", true); + EXPECT_STREQ(str, "\"~/need \\t\\ndquotes\""); + free(str); + + str = escape_command_arg("~/file-with-no-spaces", false); + EXPECT_STREQ(str, "~/file-with-no-spaces"); + free(str); + str = escape_command_arg("~/file-with-no-spaces", true); + EXPECT_STREQ(str, "\\~/file-with-no-spaces"); + free(str); + + str = escape_command_arg("~/", false); + EXPECT_STREQ(str, "~/"); + free(str); + str = escape_command_arg("~/", true); + EXPECT_STREQ(str, "\\~/"); + free(str); + + String s = STRING_INIT; + string_append_escaped_arg(&s, "~/", true); + str = string_steal_cstring(&s); + EXPECT_STREQ(str, "\\~/"); + free(str); } static void test_command_struct_layout(void) { - const Command *cmd = find_command(commands, "filter"); + const Command *cmd = find_normal_command("filter"); EXPECT_STREQ(cmd->name, "filter"); - EXPECT_STREQ(cmd->flags, "-"); - EXPECT_EQ(cmd->min_args, 1); - EXPECT_EQ(cmd->max_args, CMD_ARG_MAX); + EXPECT_STREQ(cmd->flags, "-l"); + EXPECT_UINT_EQ(cmd->min_args, 1); + EXPECT_UINT_EQ(cmd->max_args, 0xFF); + + IGNORE_WARNING("-Wpedantic") EXPECT_NONNULL(cmd->cmd); + UNIGNORE_WARNINGS } -DISABLE_WARNING("-Wmissing-prototypes") - -void test_command(void) +static void test_cmdargs_flagset_idx(void) { - test_parse_command_arg(); - test_parse_commands(); - test_parse_args(); - test_commands_array(); - test_command_struct_layout(); + EXPECT_EQ(cmdargs_flagset_idx('A'), 1); + EXPECT_EQ(cmdargs_flagset_idx('Z'), 26); + EXPECT_EQ(cmdargs_flagset_idx('a'), 27); + EXPECT_EQ(cmdargs_flagset_idx('z'), 52); + EXPECT_EQ(cmdargs_flagset_idx('0'), 53); + EXPECT_EQ(cmdargs_flagset_idx('1'), 54); + EXPECT_EQ(cmdargs_flagset_idx('9'), 62); + + for (unsigned char c = '0', z = 'z'; c <= z; c++) { + if (ascii_isalnum(c)) { + const unsigned int idx = cmdargs_flagset_idx(c); + EXPECT_TRUE(idx < 63); + EXPECT_EQ(idx, base64_decode(c) + 1); + } + } } + +static const TestEntry tests[] = { + TEST(test_parse_command_arg), + TEST(test_parse_commands), + TEST(test_command_parse_error_to_string), + TEST(test_expand_builtin_env), + TEST(test_find_normal_command), + TEST(test_parse_args), + TEST(test_escape_command_arg), + TEST(test_command_struct_layout), + TEST(test_cmdargs_flagset_idx), +}; + +const TestGroup command_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/config.c dte-1.10/test/config.c --- dte-1.9.1/test/config.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/config.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,46 +2,58 @@ #include #include #include "test.h" -#include "../src/config.h" -#include "../src/debug.h" -#include "../src/editor.h" -#include "../src/frame.h" -#include "../src/encoding/convert.h" -#include "../src/terminal/no-op.h" -#include "../src/terminal/terminal.h" -#include "../src/util/readfile.h" -#include "../src/util/str-util.h" -#include "../src/util/string-view.h" -#include "../src/window.h" +#include "config.h" +#include "command/macro.h" +#include "command/run.h" +#include "commands.h" +#include "convert.h" +#include "edit.h" +#include "editor.h" +#include "error.h" +#include "frame.h" +#include "mode.h" +#include "syntax/state.h" +#include "syntax/syntax.h" +#include "terminal/terminal.h" +#include "util/debug.h" +#include "util/path.h" +#include "util/readfile.h" +#include "util/str-util.h" +#include "util/string-view.h" +#include "util/xsnprintf.h" +#include "window.h" #include "../build/test/data.h" -DISABLE_WARNING("-Wmissing-prototypes") - -static const char extra_rc[] = - "set lock-files false\n" - // Regression test for unquoted variables in rc files - "bind M-p \"insert \"$WORD\n" - "bind M-p \"insert \"$FILE\n" -; - -void init_headless_mode(void) +static void test_builtin_configs(void) { - MEMZERO(&terminal.control_codes); - terminal.cooked = &no_op; - terminal.raw = &no_op; - editor.resize = &no_op; - editor.ui_end = &no_op; - - exec_reset_colors_rc(); - read_config(commands, "rc", CFG_MUST_EXIST | CFG_BUILTIN); - fill_builtin_colors(); - - window = new_window(); - root_frame = new_root_frame(window); - - exec_config(commands, extra_rc, sizeof(extra_rc) - 1); - - set_view(window_open_empty_buffer(window)); + size_t n; + const BuiltinConfig *editor_builtin_configs = get_builtin_configs_array(&n); + for (size_t i = 0; i < n; i++) { + const BuiltinConfig cfg = editor_builtin_configs[i]; + if (str_has_prefix(cfg.name, "syntax/")) { + if (str_has_prefix(cfg.name, "syntax/inc/")) { + continue; + } + // Check that built-in syntax files load without errors + EXPECT_NULL(find_syntax(path_basename(cfg.name))); + int err; + ConfigFlags flags = CFG_BUILTIN | CFG_MUST_EXIST; + unsigned int saved_nr_errs = get_nr_errors(); + EXPECT_NONNULL(load_syntax_file(cfg.name, flags, &err)); + EXPECT_EQ(get_nr_errors(), saved_nr_errs); + EXPECT_NONNULL(find_syntax(path_basename(cfg.name))); + } else { + // Check that built-in configs are identical to their source files + char path[4096]; + xsnprintf(path, sizeof path, "config/%s", cfg.name); + char *src; + ssize_t size = read_file(path, &src); + ASSERT_EQ(size, cfg.text.length); + EXPECT_TRUE(mem_equal(src, cfg.text.data, size)); + free(src); + } + } + update_all_syntax_colors(); } static void expect_files_equal(const char *path1, const char *path2) @@ -52,6 +64,7 @@ TEST_FAIL("Error reading '%s': %s", path1, strerror(errno)); return; } + passed++; char *buf2; ssize_t size2 = read_file(path2, &buf2); @@ -60,32 +73,169 @@ TEST_FAIL("Error reading '%s': %s", path2, strerror(errno)); return; } + passed++; - if (size1 != size2 || memcmp(buf1, buf2, size1) != 0) { + if (size1 != size2 || !mem_equal(buf1, buf2, size1)) { TEST_FAIL("Files differ: '%s', '%s'", path1, path2); + } else { + passed++; } free(buf1); free(buf2); } -void test_exec_config(void) +static void test_exec_config(void) { - ASSERT_NONNULL(window); + static const char *const outfiles[] = { + "env.txt", + "crlf.txt", + "thai-utf8.txt", + "pipe-from.txt", + "pipe-to.txt", + "redo1.txt", + "redo2.txt", + }; + + // Delete output files left over from previous runs + unlink("build/test/thai-tis620.txt"); + FOR_EACH_I(i, outfiles) { + char out[64]; + xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]); + unlink(out); + } + + // Execute *.dterc files FOR_EACH_I(i, builtin_configs) { const BuiltinConfig config = builtin_configs[i]; - exec_config(commands, config.text.data, config.text.length); + exec_config(&commands, config.text.data, config.text.length); } - expect_files_equal("build/test/env.txt", "test/data/env.txt"); - expect_files_equal("build/test/crlf.txt", "test/data/crlf.txt"); - expect_files_equal("build/test/thai-utf8.txt", "test/data/thai-utf8.txt"); - EXPECT_EQ(unlink("build/test/env.txt"), 0); - EXPECT_EQ(unlink("build/test/crlf.txt"), 0); - EXPECT_EQ(unlink("build/test/thai-utf8.txt"), 0); + // Check that output files have expected contents + FOR_EACH_I(i, outfiles) { + char out[64], ref[64]; + xsnprintf(out, sizeof out, "build/test/%s", outfiles[i]); + xsnprintf(ref, sizeof ref, "test/data/%s", outfiles[i]); + expect_files_equal(out, ref); + } - if (encoding_supported_by_iconv("TIS-620")) { + if (conversion_supported_by_iconv("UTF-8", "TIS-620")) { expect_files_equal("build/test/thai-tis620.txt", "test/data/thai-tis620.txt"); - EXPECT_EQ(unlink("build/test/thai-tis620.txt"), 0); } } + +static void test_detect_indent(void) +{ + EXPECT_FALSE(editor.options.detect_indent); + EXPECT_FALSE(editor.options.expand_tab); + EXPECT_EQ(editor.options.indent_width, 8); + + handle_command ( + &commands, + "option -r '/test/data/detect-indent\\.ini$' detect-indent 2,4,8;" + "open test/data/detect-indent.ini", + false + ); + + EXPECT_EQ(buffer->options.detect_indent, 1 << 1 | 1 << 3 | 1 << 7); + EXPECT_TRUE(buffer->options.expand_tab); + EXPECT_EQ(buffer->options.indent_width, 2); + + handle_command(&commands, "close", false); +} + +static void test_global_state(void) +{ + ASSERT_NONNULL(window); + ASSERT_NONNULL(root_frame); + ASSERT_NONNULL(buffer); + ASSERT_NONNULL(view); + ASSERT_PTREQ(window->view, view); + ASSERT_PTREQ(window->frame, root_frame); + ASSERT_PTREQ(view->buffer, buffer); + ASSERT_PTREQ(view->window, window); + ASSERT_PTREQ(root_frame->window, window); + ASSERT_PTREQ(root_frame->parent, NULL); + + ASSERT_EQ(window->views.count, 1); + ASSERT_EQ(buffer->views.count, 1); + ASSERT_EQ(editor.buffers.count, 1); + ASSERT_PTREQ(window->views.ptrs[0], view); + ASSERT_PTREQ(buffer->views.ptrs[0], view); + ASSERT_PTREQ(editor.buffers.ptrs[0], buffer); + + ASSERT_NONNULL(buffer->encoding.name); + ASSERT_NONNULL(buffer->blocks.next); + ASSERT_PTREQ(&buffer->blocks, view->cursor.head); + ASSERT_PTREQ(buffer->blocks.next, view->cursor.blk); + ASSERT_PTREQ(buffer->cur_change, &buffer->change_head); + ASSERT_PTREQ(buffer->saved_change, buffer->cur_change); + EXPECT_NULL(buffer->display_filename); + EXPECT_EQ(buffer->id, 1); +} + +static void test_macro_record(void) +{ + EXPECT_EQ(editor.input_mode, INPUT_NORMAL); + EXPECT_FALSE(macro_is_recording()); + macro_record(); + EXPECT_TRUE(macro_is_recording()); + + handle_command(&commands, "open", false); + handle_input('x'); + handle_input('y'); + handle_command(&commands, "bol", true); + handle_input('-'); + handle_input('z'); + handle_command(&commands, "eol; right; insert -m .; new-line", true); + + const StringView t1 = STRING_VIEW("test 1\n"); + insert_text(t1.data, t1.length, true); + macro_insert_text_hook(t1.data, t1.length); + + const StringView t2 = STRING_VIEW("-- test 2"); + insert_text(t2.data, t2.length, true); + macro_insert_text_hook(t2.data, t2.length); + + EXPECT_TRUE(macro_is_recording()); + macro_stop(); + EXPECT_FALSE(macro_is_recording()); + + handle_command ( + &commands, + "save -f build/test/macro-rec.txt;" + "close -f;" + "open;" + "macro play;" + "save -f build/test/macro-out.txt;" + "close -f;" + "show macro;" + "close -f;", + true + ); + + expect_files_equal("build/test/macro-rec.txt", "build/test/macro-out.txt"); +} + +static const TestEntry tests[] = { + TEST(test_global_state), + TEST(test_builtin_configs), + TEST(test_exec_config), + TEST(test_detect_indent), + TEST(test_macro_record), +}; + +const TestGroup config_tests = TEST_GROUP(tests); + +DISABLE_WARNING("-Wmissing-prototypes") + +void init_headless_mode(void) +{ + MEMZERO(&terminal.control_codes); + exec_builtin_rc(); + update_all_syntax_colors(); + editor.options.lock_files = false; + window = new_window(); + root_frame = new_root_frame(window); + set_view(window_open_empty_buffer(window)); +} diff -Nru dte-1.9.1/test/data/env.dterc dte-1.10/test/data/env.dterc --- dte-1.9.1/test/data/env.dterc 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/data/env.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -11,4 +11,8 @@ insert -m $."_Z\n" insert -m $1A save -f build/test/env.txt +setenv z +setenv _Z +setenv test_123 +setenv 1A close diff -Nru dte-1.9.1/test/data/fuzz1.dterc dte-1.10/test/data/fuzz1.dterc --- dte-1.9.1/test/data/fuzz1.dterc 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/data/fuzz1.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -1,4 +1,4 @@ -run mkdir -p build/test/ +run -s mkdir -p build/test/ cd build/test/ insert -km "1\n2\n3\n" bof @@ -22,8 +22,6 @@ filter sort -r ft ext filetype hi default red black bold -include -b binding/shift-select -insert-builtin syntax/dte bof select eof @@ -46,21 +44,23 @@ insert "hello world\n" repeat 2 replace l r right -run true +run -s true scroll-down scroll-pgdown scroll-pgup scroll-up -search -H '^ *b' +search -H '^ *h' search -n search -p select repeat 5 right cut paste -c +set syntax true set set-window-title true setenv SETENVTEST 1111 insert $SETENVTEST +setenv SETENVTEST shift +2 clear #tag @@ -76,14 +76,12 @@ wsplit wswap save -f out -pipe-from echo pipe-from-test select pgdown unselect repeat 1000 undo redo undo -compile -1s gitgrep sh -c 'grep -n ^# ../../mk/*' close wclose repeat 20 open diff -Nru dte-1.9.1/test/data/fuzz2.dterc dte-1.10/test/data/fuzz2.dterc --- dte-1.9.1/test/data/fuzz2.dterc 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/fuzz2.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,22 @@ +set statusline-left '%f %m %r %y %Y %x %X %p %E %M %N %n %s %t %u %%' +toggle auto-indent +toggle editorconfig +toggle emulate-tab +toggle expand-tab +toggle file-history +toggle fsync +toggle syntax +toggle brace-indent +toggle display-invisible +toggle display-special +toggle set-window-title +toggle show-line-numbers +toggle tab-bar +option 'x' syntax true +option ',x' syntax true +option 'x,y' syntax true +option 'x,y,' syntax true +option '' syntax true +option ',' syntax true +option ',-' syntax true +option ',,zzz,,zzz,' syntax true diff -Nru dte-1.9.1/test/data/pipe.dterc dte-1.10/test/data/pipe.dterc --- dte-1.9.1/test/data/pipe.dterc 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/pipe.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,9 @@ +open +insert "pipe-to\ntest" +pipe-to sh -c 'cat > build/test/pipe-to.txt' +close -f + +open +pipe-from sh -c 'printf "pipe-from\ntest\n"' +save -f build/test/pipe-from.txt +close -f diff -Nru dte-1.9.1/test/data/pipe-from.txt dte-1.10/test/data/pipe-from.txt --- dte-1.9.1/test/data/pipe-from.txt 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/pipe-from.txt 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,2 @@ +pipe-from +test diff -Nru dte-1.9.1/test/data/pipe-to.txt dte-1.10/test/data/pipe-to.txt --- dte-1.9.1/test/data/pipe-to.txt 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/pipe-to.txt 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,2 @@ +pipe-to +test diff -Nru dte-1.9.1/test/data/redo1.txt dte-1.10/test/data/redo1.txt --- dte-1.9.1/test/data/redo1.txt 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/redo1.txt 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,2 @@ +line #1 +line #2 diff -Nru dte-1.9.1/test/data/redo2.txt dte-1.10/test/data/redo2.txt --- dte-1.9.1/test/data/redo2.txt 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/redo2.txt 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,2 @@ +line #1 +... diff -Nru dte-1.9.1/test/data/redo.dterc dte-1.10/test/data/redo.dterc --- dte-1.9.1/test/data/redo.dterc 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/redo.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,14 @@ +open +insert -m "line #1\n" +insert -m "line #2\n" +insert -m "line #3\n" +undo +undo +insert -m "...\n" +undo +redo 1 +save -f build/test/redo1.txt +undo +redo 2 +save -f build/test/redo2.txt +close diff -Nru dte-1.9.1/test/data/test.c dte-1.10/test/data/test.c --- dte-1.9.1/test/data/test.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/data/test.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,7 @@ +#include + +int c_syntax_test(int argc, char *argv[]) +{ + fprintf(stderr, "test: %s %c %d\n", "str", 'x', 42); + return 0; +} diff -Nru dte-1.9.1/test/data/thai.dterc dte-1.10/test/data/thai.dterc --- dte-1.9.1/test/data/thai.dterc 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/data/thai.dterc 2021-04-03 21:08:53.000000000 +0000 @@ -7,7 +7,7 @@ bol delete-eol insert "Testing" -run mkdir -p build/test/ +run -s mkdir -p build/test/ save -f -e UTF-8 build/test/thai-utf8.txt save -f -e TIS-620 build/test/thai-tis620.txt close diff -Nru dte-1.9.1/test/dump.c dte-1.10/test/dump.c --- dte-1.9.1/test/dump.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/dump.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,78 @@ +#include "test.h" +#include "alias.h" +#include "bind.h" +#include "command/args.h" +#include "command/parse.h" +#include "commands.h" +#include "compiler.h" +#include "config.h" +#include "filetype.h" +#include "frame.h" +#include "options.h" +#include "syntax/color.h" +#include "util/str-util.h" + +static const struct { + const char name[10]; + bool check_parse; + bool check_name; + String (*dump)(void); +} handlers[] = { + {"alias", true, true, dump_aliases}, + {"bind", true, true, dump_bindings}, + {"errorfmt", true, true, dump_compilers}, + {"ft", true, true, dump_ft}, + {"hi", true, true, dump_hl_colors}, + {"include", false, false, dump_builtin_configs}, + {"option", true, false, dump_options}, + {"wsplit", false, false, dump_frames}, +}; + +static void test_dump_handlers(void) +{ + for (size_t i = 0; i < ARRAY_COUNT(handlers); i++) { + String str = handlers[i].dump(); + size_t pos = 0; + while (pos < str.len) { + const char *line = buf_next_line(str.buffer, &pos, str.len); + ASSERT_NONNULL(line); + char c = line[0]; + if (c == '\0' || c == '#' || !handlers[i].check_parse) { + continue; + } + + PointerArray arr = PTR_ARRAY_INIT; + CommandParseError parse_err = parse_commands(&arr, line); + EXPECT_EQ(parse_err, CMDERR_NONE); + EXPECT_TRUE(arr.count >= 2); + if (parse_err != CMDERR_NONE || arr.count < 2) { + continue; + } + + if (handlers[i].check_name) { + EXPECT_STREQ(arr.ptrs[0], handlers[i].name); + } + + const Command *cmd = find_normal_command(arr.ptrs[0]); + EXPECT_NONNULL(cmd); + if (!cmd) { + continue; + } + + CommandArgs a = {.args = (char**)arr.ptrs + 1}; + ArgParseError arg_err = do_parse_args(cmd, &a); + EXPECT_EQ(arg_err, 0); + if (arg_err != 0) { + continue; + } + ptr_array_free(&arr); + } + string_free(&str); + } +} + +static const TestEntry tests[] = { + TEST(test_dump_handlers), +}; + +const TestGroup dump_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/editorconfig.c dte-1.10/test/editorconfig.c --- dte-1.9.1/test/editorconfig.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/editorconfig.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,8 +1,8 @@ #include #include "test.h" -#include "../src/editorconfig/editorconfig.h" -#include "../src/editorconfig/match.h" -#include "../src/util/path.h" +#include "editorconfig/editorconfig.h" +#include "editorconfig/match.h" +#include "util/path.h" static void test_editorconfig_pattern_match(void) { @@ -41,10 +41,20 @@ EXPECT_TRUE(patmatch("file.{,,x,,y,,}", "file.")); EXPECT_FALSE(patmatch("file.{,,x,,y,,}", "file.z")); + EXPECT_TRUE(patmatch("*.x,y,z", "file.x,y,z")); + EXPECT_TRUE(patmatch("*.{x,y,z}", "file.y")); + EXPECT_FALSE(patmatch("*.{x,y,z}", "file.x,y,z")); + EXPECT_FALSE(patmatch("*.{x,y,z}", "file.{x,y,z}")); + EXPECT_TRUE(patmatch("file.{{{a,b,{c,,d}}}}", "file.d")); EXPECT_TRUE(patmatch("file.{{{a,b,{c,,d}}}}", "file.")); EXPECT_FALSE(patmatch("file.{{{a,b,{c,d}}}}", "file.")); + EXPECT_TRUE(patmatch("file.{c[vl]d,inc}", "file.cvd")); + EXPECT_TRUE(patmatch("file.{c[vl]d,inc}", "file.cld")); + EXPECT_TRUE(patmatch("file.{c[vl]d,inc}", "file.inc")); + EXPECT_FALSE(patmatch("file.{c[vl]d,inc}", "file.cd")); + EXPECT_TRUE(patmatch("a?b.c", "a_b.c")); EXPECT_FALSE(patmatch("a?b.c", "a/b.c")); @@ -58,6 +68,11 @@ EXPECT_TRUE(patmatch("{{{a}}}", "a")); EXPECT_FALSE(patmatch("{{{a}}", "a")); + // It's debatable whether this edge case behavior is sensible, + // but it's tested here anyway for the sake of UBSan coverage + EXPECT_TRUE(patmatch("*.xyz\\", "file.xyz\\")); + EXPECT_FALSE(patmatch("*.xyz\\", "file.xyz")); + #undef patmatch } @@ -85,10 +100,9 @@ EXPECT_FALSE(opts.indent_size_is_tab); } -DISABLE_WARNING("-Wmissing-prototypes") +static const TestEntry tests[] = { + TEST(test_editorconfig_pattern_match), + TEST(test_get_editorconfig_options), +}; -void test_editorconfig(void) -{ - test_editorconfig_pattern_match(); - test_get_editorconfig_options(); -} +const TestGroup editorconfig_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/encoding.c dte-1.10/test/encoding.c --- dte-1.9.1/test/encoding.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/encoding.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,6 +1,5 @@ #include "test.h" -#include "../src/encoding/bom.h" -#include "../src/encoding/encoding.h" +#include "encoding.h" static void test_detect_encoding_from_bom(void) { @@ -9,13 +8,23 @@ const unsigned char *text; size_t size; } tests[] = { - {UTF8, "\xef\xbb\xbfHello", 8}, - {UTF32BE, "\x00\x00\xfe\xffHello", 9}, - {UTF32LE, "\xff\xfe\x00\x00Hello", 9}, - {UTF16BE, "\xfe\xffHello", 7}, - {UTF16LE, "\xff\xfeHello", 7}, - {UNKNOWN_ENCODING, "\x00\xef\xbb\xbfHello", 9}, - {UNKNOWN_ENCODING, "\xef\xbb", 2}, + {UTF8, STRN("\xef\xbb\xbfHello")}, + {UTF32BE, STRN("\x00\x00\xfe\xffHello")}, + {UTF32LE, STRN("\xff\xfe\x00\x00Hello")}, + {UTF16BE, STRN("\xfe\xffHello")}, + {UTF16LE, STRN("\xff\xfeHello")}, + {UTF16LE, STRN("\xff\xfe")}, + {UNKNOWN_ENCODING, STRN("\xff\xff\x00\x00Hello")}, + {UNKNOWN_ENCODING, STRN("\x00\xef\xbb\xbfHello")}, + {UNKNOWN_ENCODING, STRN("\xef\xbb")}, + {UNKNOWN_ENCODING, STRN("\xef\xbb#")}, + {UNKNOWN_ENCODING, STRN("\xee\xbb\xbf")}, + {UNKNOWN_ENCODING, STRN("\xff\xbb\xbf")}, + {UNKNOWN_ENCODING, STRN("\xbf\xbb\xef")}, + {UNKNOWN_ENCODING, STRN("\x00\x00\xfe")}, + {UNKNOWN_ENCODING, STRN("\x00")}, + {UNKNOWN_ENCODING, "", 0}, + {UNKNOWN_ENCODING, NULL, 0}, }; FOR_EACH_I(i, tests) { const struct bom_test *t = &tests[i]; @@ -35,9 +44,15 @@ {UTF8, "utf-8"}, {UTF8, "utf8"}, {UTF8, "Utf8"}, - {UTF16, "UTF16"}, + {UTF16BE, "UTF16"}, + {UTF16BE, "UTF-16"}, + {UTF32BE, "utf32"}, + {UTF32BE, "utf-32"}, {UTF32LE, "utf-32le"}, {UTF32LE, "ucs-4le"}, + {UTF32BE, "ucs-4BE"}, + {UTF32BE, "ucs-4"}, + {UTF32BE, "ucs4"}, {UNKNOWN_ENCODING, "UTF8_"}, {UNKNOWN_ENCODING, "UTF"}, }; @@ -47,10 +62,20 @@ } } -DISABLE_WARNING("-Wmissing-prototypes") - -void test_encoding(void) +static void test_encoding_from_type(void) { - test_detect_encoding_from_bom(); - test_lookup_encoding(); + const Encoding a = encoding_from_type(UTF8); + EXPECT_EQ(a.type, UTF8); + EXPECT_STREQ(a.name, "UTF-8"); + // Ensure Encoding::name is an interned string + EXPECT_PTREQ(encoding_from_type(UTF8).name, a.name); + EXPECT_PTREQ(encoding_from_name("utf8").name, a.name); } + +static const TestEntry tests[] = { + TEST(test_detect_encoding_from_bom), + TEST(test_lookup_encoding), + TEST(test_encoding_from_type), +}; + +const TestGroup encoding_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/filetype.c dte-1.10/test/filetype.c --- dte-1.9.1/test/filetype.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/filetype.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,11 +1,12 @@ #include #include "test.h" -#include "../src/filetype.h" +#include "filetype.h" static void test_find_ft_filename(void) { - static const struct ft_filename_test { - const char *filename, *expected_filetype; + static const struct { + const char *filename; + const char *expected_filetype; } tests[] = { {"/usr/local/include/lib.h", "c"}, {"test.cc~", "c"}, @@ -15,6 +16,7 @@ {"test.rs", "rust"}, {"test.C", "c"}, {"test.H", "c"}, + {"test.re", "c"}, {"test.S", "asm"}, {"test.s", "asm"}, {"test.d", "d"}, @@ -22,6 +24,12 @@ {"test.m", "objc"}, {"test.v", "verilog"}, {"test.y", "yacc"}, + {"test.kt", "kotlin"}, + {"test.kts", "kotlin"}, + {"test.csv", "csv"}, + {"test.tsv", "tsv"}, + {"test.opml", "xml"}, + {"test.gcode", "gcode"}, {"makefile", "make"}, {"GNUmakefile", "make"}, {".file.yml", "yaml"}, @@ -48,9 +56,11 @@ {".gitmodules", "ini"}, {".jshintrc", "json"}, {".zshrc", "sh"}, + {".XCompose", "config"}, + {".tmux.conf", "tmux"}, {"zshrc", "sh"}, + {"gnus", "lisp"}, {"file.flatpakref", "ini"}, - {"file.flatpakrepo", "ini"}, {"file.automount", "ini"}, {"file.nginxconf", "nginx"}, {"meson_options.txt", "meson"}, @@ -60,6 +70,7 @@ {"Makefile.a_", NULL}, {"Makefile._m", NULL}, {"M_______.am", NULL}, + {".Makefile", NULL}, {"file.glslf", "glsl"}, {"file.glslv", "glsl"}, {"file.gl_lv", NULL}, @@ -83,6 +94,8 @@ {"test.c.pacsave", "c"}, {"test.c.dpkg-dist", "c"}, {"test.c.dpkg-old", "c"}, + {"test.c.dpkg-backup", "c"}, + {"test.c.dpkg-remove", "c"}, {"test.c.rpmnew", "c"}, {"test.c.rpmsave", "c"}, {".c", NULL}, @@ -91,16 +104,16 @@ }; const StringView empty_line = STRING_VIEW_INIT; FOR_EACH_I(i, tests) { - const struct ft_filename_test *t = &tests[i]; - const char *result = find_ft(t->filename, empty_line); - IEXPECT_STREQ(result, t->expected_filetype); + const char *ft = find_ft(tests[i].filename, empty_line); + IEXPECT_STREQ(ft, tests[i].expected_filetype); } } static void test_find_ft_firstline(void) { - static const struct ft_firstline_test { - const char *line, *expected_filetype; + static const struct { + const char *line; + const char *expected_filetype; } tests[] = { {"", "html"}, {"line)); - IEXPECT_STREQ(result, t->expected_filetype); + const char *ft = find_ft(NULL, strview_from_cstring(tests[i].line)); + IEXPECT_STREQ(ft, tests[i].expected_filetype); } } -DISABLE_WARNING("-Wmissing-prototypes") - -void test_filetype(void) +static void test_is_ft(void) { - test_find_ft_filename(); - test_find_ft_firstline(); + EXPECT_TRUE(is_ft("ada")); + EXPECT_TRUE(is_ft("asm")); + EXPECT_TRUE(is_ft("awk")); + EXPECT_TRUE(is_ft("c")); + EXPECT_TRUE(is_ft("d")); + EXPECT_TRUE(is_ft("dte")); + EXPECT_TRUE(is_ft("java")); + EXPECT_TRUE(is_ft("javascript")); + EXPECT_TRUE(is_ft("lua")); + EXPECT_TRUE(is_ft("mail")); + EXPECT_TRUE(is_ft("make")); + EXPECT_TRUE(is_ft("pkg-config")); + EXPECT_TRUE(is_ft("rst")); + EXPECT_TRUE(is_ft("sh")); + EXPECT_TRUE(is_ft("yaml")); + EXPECT_TRUE(is_ft("zig")); + + EXPECT_FALSE(is_ft("")); + EXPECT_FALSE(is_ft("-")); + EXPECT_FALSE(is_ft("a")); + EXPECT_FALSE(is_ft("C")); + EXPECT_FALSE(is_ft("MAKE")); } + +static const TestEntry tests[] = { + TEST(test_find_ft_filename), + TEST(test_find_ft_firstline), + TEST(test_is_ft), +}; + +const TestGroup filetype_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/history.c dte-1.10/test/history.c --- dte-1.9.1/test/history.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/history.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,130 @@ +#include +#include "test.h" +#include "history.h" +#include "util/xsnprintf.h" + +static void test_history_add(void) +{ + History h = {.max_entries = 7}; + history_add(&h, "A"); + EXPECT_EQ(h.entries.count, 1); + EXPECT_STREQ(h.first->text, "A"); + EXPECT_NULL(h.first->prev); + EXPECT_NULL(h.first->next); + EXPECT_PTREQ(h.first, h.last); + + history_add(&h, "A"); + EXPECT_EQ(h.entries.count, 1); + EXPECT_PTREQ(h.first, h.last); + + history_add(&h, "B"); + EXPECT_EQ(h.entries.count, 2); + EXPECT_STREQ(h.first->text, "A"); + EXPECT_STREQ(h.last->text, "B"); + EXPECT_NULL(h.first->prev); + EXPECT_NULL(h.last->next); + EXPECT_PTREQ(h.first->next, h.last); + EXPECT_PTREQ(h.last->prev, h.first); + + history_add(&h, "C"); + EXPECT_EQ(h.entries.count, 3); + EXPECT_STREQ(h.first->text, "A"); + EXPECT_STREQ(h.first->next->text, "B"); + EXPECT_STREQ(h.last->prev->text, "B"); + EXPECT_STREQ(h.last->text, "C"); + EXPECT_NULL(h.first->prev); + EXPECT_NULL(h.last->next); + + history_add(&h, "A"); + EXPECT_EQ(h.entries.count, 3); + EXPECT_STREQ(h.last->text, "A"); + EXPECT_STREQ(h.first->text, "B"); + EXPECT_NULL(h.first->prev); + EXPECT_NULL(h.last->next); + EXPECT_STREQ(h.first->next->text, "C"); + EXPECT_STREQ(h.last->prev->text, "C"); + + history_add(&h, "C"); + EXPECT_EQ(h.entries.count, 3); + EXPECT_STREQ(h.last->text, "C"); + EXPECT_STREQ(h.first->text, "B"); + EXPECT_NULL(h.first->prev); + EXPECT_NULL(h.last->next); + EXPECT_STREQ(h.first->next->text, "A"); + EXPECT_STREQ(h.last->prev->text, "A"); + + history_add(&h, "B"); + history_add(&h, "C"); + EXPECT_EQ(h.entries.count, 3); + EXPECT_STREQ(h.first->text, "A"); + EXPECT_STREQ(h.first->next->text, "B"); + EXPECT_STREQ(h.last->prev->text, "B"); + EXPECT_STREQ(h.last->text, "C"); + + history_add(&h, "D"); + history_add(&h, "E"); + history_add(&h, "F"); + history_add(&h, "G"); + EXPECT_EQ(h.entries.count, 7); + EXPECT_STREQ(h.last->text, "G"); + EXPECT_STREQ(h.first->text, "A"); + EXPECT_STREQ(h.last->prev->text, "F"); + + history_add(&h, "H"); + EXPECT_EQ(h.entries.count, 7); + EXPECT_STREQ(h.last->text, "H"); + EXPECT_STREQ(h.first->text, "B"); + EXPECT_STREQ(h.last->prev->text, "G"); + + history_add(&h, "I"); + EXPECT_EQ(h.entries.count, 7); + EXPECT_STREQ(h.last->text, "I"); + EXPECT_STREQ(h.first->text, "C"); + EXPECT_STREQ(h.last->prev->text, "H"); + + hashmap_free(&h.entries, free); + h = (History){.max_entries = 2}; + EXPECT_EQ(h.entries.count, 0); + + history_add(&h, "1"); + EXPECT_EQ(h.entries.count, 1); + EXPECT_STREQ(h.last->text, "1"); + EXPECT_STREQ(h.first->text, "1"); + + history_add(&h, "2"); + EXPECT_EQ(h.entries.count, 2); + EXPECT_STREQ(h.last->text, "2"); + EXPECT_STREQ(h.first->text, "1"); + + history_add(&h, "3"); + EXPECT_EQ(h.entries.count, 2); + EXPECT_STREQ(h.last->text, "3"); + EXPECT_STREQ(h.first->text, "2"); + + hashmap_free(&h.entries, free); +} + +// This test is done to ensure the HashMap can handle the constant +// churn from insertions and removals (i.e. that it rehashes the +// table to clean out tombstones, even when the number of real +// entries stops growing). +static void test_history_tombstone_pressure(void) +{ + History h = {.max_entries = 512}; + for (unsigned int i = 0; i < 12000; i++) { + char str[8]; + xsnprintf(str, sizeof(str), "%u", i); + history_add(&h, str); + } + + EXPECT_EQ(h.entries.count, h.max_entries); + EXPECT_TRUE(h.entries.mask + 1 <= 2048); + hashmap_free(&h.entries, free); +} + +static const TestEntry tests[] = { + TEST(test_history_add), + TEST(test_history_tombstone_pressure), +}; + +const TestGroup history_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/main.c dte-1.10/test/main.c --- dte-1.9.1/test/main.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/main.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,137 +1,126 @@ -#include #include -#include -#include +#include +#include +#include #include "test.h" -#include "../src/bind.h" -#include "../src/buffer.h" -#include "../src/command.h" -#include "../src/editor.h" -#include "../src/regexp.h" -#include "../src/util/path.h" -#include "../src/util/xmalloc.h" - -void test_cmdline(void); -void test_command(void); -void test_editorconfig(void); -void test_encoding(void); -void test_filetype(void); -void test_syntax(void); -void test_terminal(void); -void test_util(void); +#include "editor.h" +#include "syntax/syntax.h" +#include "util/path.h" + void init_headless_mode(void); -void test_exec_config(void); +extern const TestGroup bind_tests; +extern const TestGroup cmdline_tests; +extern const TestGroup command_tests; +extern const TestGroup config_tests; +extern const TestGroup dump_tests; +extern const TestGroup editorconfig_tests; +extern const TestGroup encoding_tests; +extern const TestGroup filetype_tests; +extern const TestGroup history_tests; +extern const TestGroup option_tests; +extern const TestGroup syntax_tests; +extern const TestGroup terminal_tests; +extern const TestGroup util_tests; -static void test_relative_filename(void) +static void test_posix_sanity(void) { - static const struct rel_test { - const char *cwd, *path, *result; - } tests[] = { // NOTE: at most 2 ".." components allowed in relative name - { "/", "/", "/" }, - { "/", "/file", "file" }, - { "/a/b/c/d", "/a/b/file", "../../file" }, - { "/a/b/c/d/e", "/a/b/file", "/a/b/file" }, - { "/a/foobar", "/a/foo/file", "../foo/file" }, - }; - FOR_EACH_I(i, tests) { - const struct rel_test *t = &tests[i]; - char *result = relative_filename(t->path, t->cwd); - IEXPECT_STREQ(t->result, result); - free(result); - } -} + // This is not guaranteed by ISO C99, but it is required by POSIX + // and is relied upon by this codebase: + ASSERT_EQ(CHAR_BIT, 8); + ASSERT_TRUE(sizeof(int) >= 4); -static void test_detect_indent(void) -{ - EXPECT_FALSE(editor.options.detect_indent); - EXPECT_FALSE(editor.options.expand_tab); - EXPECT_EQ(editor.options.indent_width, 8); - - handle_command ( - commands, - "option -r '/test/data/detect-indent\\.ini$' detect-indent 2,4,8;" - "open test/data/detect-indent.ini" - ); - - EXPECT_EQ(buffer->options.detect_indent, 1 << 1 | 1 << 3 | 1 << 7); - EXPECT_TRUE(buffer->options.expand_tab); - EXPECT_EQ(buffer->options.indent_width, 2); + IGNORE_WARNING("-Wformat-truncation") - handle_command(commands, "close"); -} + // Some snprintf(3) implementations historically returned -1 in case of + // truncation. C99 and POSIX 2001 both require that it return the full + // size of the formatted string, as if there had been enough space. + char buf[8] = "........"; + ASSERT_EQ(snprintf(buf, 8, "0123456789"), 10); + ASSERT_EQ(buf[7], '\0'); + EXPECT_STREQ(buf, "0123456"); -static void test_handle_binding(void) -{ - handle_command(commands, "bind ^A 'insert zzz'; open"); + // C99 and POSIX 2001 also require the same behavior as above when the + // size argument is 0 (and allow the buffer argument to be NULL). + ASSERT_EQ(snprintf(NULL, 0, "987654321"), 9); - // Bound command should be cached - const KeyBinding *binding = lookup_binding(MOD_CTRL | 'A'); - ASSERT_NONNULL(binding); - EXPECT_PTREQ(binding->cmd, find_command(commands, "insert")); - EXPECT_EQ(binding->a.nr_flags, 0); - EXPECT_EQ(binding->a.nr_args, 1); - EXPECT_STREQ(binding->a.args[0], "zzz"); - EXPECT_NULL(binding->a.args[1]); - - handle_binding(MOD_CTRL | 'A'); - const Block *block = BLOCK(buffer->blocks.next); - ASSERT_NONNULL(block); - ASSERT_EQ(block->size, 4); - EXPECT_EQ(block->nl, 1); - EXPECT_EQ(memcmp(block->data, "zzz\n", 4), 0); - EXPECT_TRUE(undo()); - EXPECT_EQ(block->size, 0); - EXPECT_EQ(block->nl, 0); - EXPECT_FALSE(undo()); - handle_command(commands, "close"); + UNIGNORE_WARNINGS } -static void test_regexp_match(void) +static void test_init(void) { - static const char buf[] = "fn(a);\n"; + char *home = path_absolute("build/test/HOME"); + char *dte_home = path_absolute("build/test/DTE_HOME"); + ASSERT_NONNULL(home); + ASSERT_NONNULL(dte_home); - PointerArray a = PTR_ARRAY_INIT; - bool matched = regexp_match("^[a-z]+\\(", buf, sizeof(buf) - 1, &a); - EXPECT_TRUE(matched); - EXPECT_EQ(a.count, 1); - EXPECT_STREQ(a.ptrs[0], "fn("); - ptr_array_free(&a); - - ptr_array_init(&a, 0); - matched = regexp_match("^[a-z]+\\([0-9]", buf, sizeof(buf) - 1, &a); - EXPECT_FALSE(matched); - EXPECT_EQ(a.count, 0); - ptr_array_free(&a); -} + ASSERT_TRUE(mkdir(home, 0755) == 0 || errno == EEXIST); + ASSERT_TRUE(mkdir(dte_home, 0755) == 0 || errno == EEXIST); -static void test_posix_sanity(void) -{ - // This is not guaranteed by ISO C99, but it is required by POSIX - // and is relied upon by this codebase: - ASSERT_EQ(CHAR_BIT, 8); -} + ASSERT_EQ(setenv("HOME", home, true), 0); + ASSERT_EQ(setenv("DTE_HOME", dte_home, true), 0); + ASSERT_EQ(setenv("XDG_RUNTIME_DIR", dte_home, true), 0); + + free(home); + free(dte_home); + + ASSERT_EQ(unsetenv("TERM"), 0); + ASSERT_EQ(unsetenv("COLORTERM"), 0); -int main(void) -{ - test_posix_sanity(); init_editor_state(); - test_relative_filename(); + const char *ver = getenv("DTE_VERSION"); + EXPECT_NONNULL(ver); + EXPECT_STREQ(ver, editor.version); +} + +static void run_tests(const TestGroup *g) +{ + ASSERT_TRUE(g->nr_tests != 0); + ASSERT_NONNULL(g->tests); + + for (const TestEntry *t = g->tests, *end = t + g->nr_tests; t < end; t++) { + ASSERT_NONNULL(t->func); + ASSERT_NONNULL(t->name); + unsigned int prev_failed = failed; + unsigned int prev_passed = passed; + t->func(); + unsigned int f = failed - prev_failed; + unsigned int p = passed - prev_passed; + fprintf(stderr, " CHECK %-35s %4u passed", t->name, p); + if (unlikely(f > 0)) { + fprintf(stderr, " %4u FAILED", f); + } + fputc('\n', stderr); + } +} + +static const TestEntry tests[] = { + TEST(test_posix_sanity), + TEST(test_init), +}; - test_command(); - test_editorconfig(); - test_encoding(); - test_filetype(); - test_util(); - test_terminal(); - test_cmdline(); - test_regexp_match(); - test_syntax(); +const TestGroup init_tests = TEST_GROUP(tests); + +int main(void) +{ + run_tests(&init_tests); + run_tests(&command_tests); + run_tests(&option_tests); + run_tests(&editorconfig_tests); + run_tests(&encoding_tests); + run_tests(&filetype_tests); + run_tests(&util_tests); + run_tests(&terminal_tests); + run_tests(&cmdline_tests); + run_tests(&history_tests); init_headless_mode(); - test_exec_config(); - test_detect_indent(); - test_handle_binding(); + run_tests(&config_tests); + run_tests(&bind_tests); + run_tests(&syntax_tests); + run_tests(&dump_tests); + free_syntaxes(); + fprintf(stderr, "\n TOTAL %u passed, %u failed\n\n", passed, failed); return failed ? 1 : 0; } diff -Nru dte-1.9.1/test/options.c dte-1.10/test/options.c --- dte-1.9.1/test/options.c 1970-01-01 00:00:00.000000000 +0000 +++ dte-1.10/test/options.c 2021-04-03 21:08:53.000000000 +0000 @@ -0,0 +1,28 @@ +#include "test.h" +#include "options.h" + +static void test_common_options_offsets(void) +{ + #define GLOBAL_OFFSET(m) offsetof(GlobalOptions, m) + #define LOCAL_OFFSET(m) offsetof(LocalOptions, m) + #define CHECK_OFFSETS(m) EXPECT_UINT_EQ(GLOBAL_OFFSET(m), LOCAL_OFFSET(m)) + + CHECK_OFFSETS(detect_indent); + CHECK_OFFSETS(indent_width); + CHECK_OFFSETS(tab_width); + CHECK_OFFSETS(text_width); + CHECK_OFFSETS(ws_error); + CHECK_OFFSETS(auto_indent); + CHECK_OFFSETS(editorconfig); + CHECK_OFFSETS(emulate_tab); + CHECK_OFFSETS(expand_tab); + CHECK_OFFSETS(file_history); + CHECK_OFFSETS(fsync); + CHECK_OFFSETS(syntax); +} + +static const TestEntry tests[] = { + TEST(test_common_options_offsets), +}; + +const TestGroup option_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/syntax.c dte-1.10/test/syntax.c --- dte-1.9.1/test/syntax.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/syntax.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,27 +1,143 @@ #include #include "test.h" -#include "../src/syntax/bitset.h" +#include "block-iter.h" +#include "config.h" +#include "syntax/bitset.h" +#include "syntax/highlight.h" +#include "util/debug.h" +#include "util/utf8.h" +#include "window.h" -static void test_bitset_invert(void) +static void test_bitset(void) { - BitSet set = {0}; - ASSERT_EQ(sizeof(set), 32); - ASSERT_EQ(ARRAY_COUNT(set), 32); + BitSetWord set[BITSET_NR_WORDS(256)]; + ASSERT_TRUE(sizeof(set) >= 32); memset(set, 0, sizeof(set)); - for (size_t i = 0; i < ARRAY_COUNT(set); i++) { + EXPECT_FALSE(bitset_contains(set, '0')); + EXPECT_FALSE(bitset_contains(set, 'a')); + EXPECT_FALSE(bitset_contains(set, 'z')); + EXPECT_FALSE(bitset_contains(set, '!')); + EXPECT_FALSE(bitset_contains(set, '\0')); + + bitset_add_char_range(set, "0-9a-fxy"); + EXPECT_TRUE(bitset_contains(set, '0')); + EXPECT_TRUE(bitset_contains(set, '8')); + EXPECT_TRUE(bitset_contains(set, '9')); + EXPECT_TRUE(bitset_contains(set, 'a')); + EXPECT_TRUE(bitset_contains(set, 'b')); + EXPECT_TRUE(bitset_contains(set, 'f')); + EXPECT_TRUE(bitset_contains(set, 'x')); + EXPECT_TRUE(bitset_contains(set, 'y')); + EXPECT_FALSE(bitset_contains(set, 'g')); + EXPECT_FALSE(bitset_contains(set, 'z')); + EXPECT_FALSE(bitset_contains(set, 'A')); + EXPECT_FALSE(bitset_contains(set, 'F')); + EXPECT_FALSE(bitset_contains(set, 'X')); + EXPECT_FALSE(bitset_contains(set, 'Z')); + EXPECT_FALSE(bitset_contains(set, '{')); + EXPECT_FALSE(bitset_contains(set, '`')); + EXPECT_FALSE(bitset_contains(set, '/')); + EXPECT_FALSE(bitset_contains(set, ':')); + EXPECT_FALSE(bitset_contains(set, '\0')); + + BITSET_INVERT(set); + EXPECT_FALSE(bitset_contains(set, '0')); + EXPECT_FALSE(bitset_contains(set, '8')); + EXPECT_FALSE(bitset_contains(set, '9')); + EXPECT_FALSE(bitset_contains(set, 'a')); + EXPECT_FALSE(bitset_contains(set, 'b')); + EXPECT_FALSE(bitset_contains(set, 'f')); + EXPECT_FALSE(bitset_contains(set, 'x')); + EXPECT_FALSE(bitset_contains(set, 'y')); + EXPECT_TRUE(bitset_contains(set, 'g')); + EXPECT_TRUE(bitset_contains(set, 'z')); + EXPECT_TRUE(bitset_contains(set, 'A')); + EXPECT_TRUE(bitset_contains(set, 'F')); + EXPECT_TRUE(bitset_contains(set, 'X')); + EXPECT_TRUE(bitset_contains(set, 'Z')); + EXPECT_TRUE(bitset_contains(set, '{')); + EXPECT_TRUE(bitset_contains(set, '`')); + EXPECT_TRUE(bitset_contains(set, '/')); + EXPECT_TRUE(bitset_contains(set, ':')); + EXPECT_TRUE(bitset_contains(set, '\0')); + + memset(set, 0, sizeof(set)); + FOR_EACH_I(i, set) { EXPECT_UINT_EQ(set[i], 0); } - - bitset_invert(set); - for (size_t i = 0; i < ARRAY_COUNT(set); i++) { - EXPECT_UINT_EQ(set[i], UINT8_MAX); + BITSET_INVERT(set); + FOR_EACH_I(i, set) { + EXPECT_UINT_EQ(set[i], ((BitSetWord)-1)); } } -DISABLE_WARNING("-Wmissing-prototypes") - -void test_syntax(void) +static void test_hl_line(void) { - test_bitset_invert(); + if (!get_builtin_config("syntax/c")) { + DEBUG_LOG("syntax/c not available; skipping %s()", __func__); + return; + } + + const Encoding enc = encoding_from_type(UTF8); + EXPECT_EQ(enc.type, UTF8); + EXPECT_STREQ(enc.name, "UTF-8"); + View *v = window_open_file(window, "test/data/test.c", &enc); + ASSERT_NONNULL(v); + + const size_t line_nr = 5; + ASSERT_TRUE(v->buffer->nl >= line_nr); + hl_fill_start_states(v->buffer, v->buffer->nl); + block_iter_goto_line(&v->cursor, line_nr - 1); + view_update_cursor_x(v); + view_update_cursor_y(v); + view_update(v); + ASSERT_EQ(v->cx, 0); + ASSERT_EQ(v->cy, line_nr - 1); + + StringView line; + fetch_this_line(&v->cursor, &line); + ASSERT_EQ(line.length, 56); + + bool next_changed; + TermColor **colors = hl_line(buffer, &line, line_nr, &next_changed); + EXPECT_TRUE(next_changed); + + const TermColor *t = find_color("text"); + const TermColor *c = find_color("constant"); + const TermColor *s = find_color("string"); + const TermColor *x = find_color("special"); + const TermColor *n = find_color("numeric"); + ASSERT_NONNULL(t); + ASSERT_NONNULL(c); + ASSERT_NONNULL(s); + ASSERT_NONNULL(x); + ASSERT_NONNULL(n); + + const TermColor *const expected_colors[] = { + t, t, t, t, t, t, t, t, t, t, t, t, c, c, c, c, + c, c, t, t, s, s, s, s, s, s, s, s, s, s, s, s, + s, s, s, x, x, s, t, t, s, s, s, s, s, t, t, x, + x, x, t, t, n, n, t, t + }; + + size_t i = 0; + for (size_t pos = 0; pos < line.length; i++) { + CodePoint u = u_get_char(line.data, line.length, &pos); + IEXPECT_EQ(u, line.data[i]); + if (i >= ARRAY_COUNT(expected_colors)) { + continue; + } + IEXPECT_TRUE(same_color(colors[i], expected_colors[i])); + } + + EXPECT_EQ(i, ARRAY_COUNT(expected_colors)); + window_close_current(); } + +static const TestEntry tests[] = { + TEST(test_bitset), + TEST(test_hl_line), +}; + +const TestGroup syntax_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/terminal.c dte-1.10/test/terminal.c --- dte-1.9.1/test/terminal.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/terminal.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,10 +1,15 @@ #include #include "test.h" -#include "../src/debug.h" -#include "../src/terminal/color.h" -#include "../src/terminal/key.h" -#include "../src/terminal/xterm.h" -#include "../src/util/unicode.h" +#include "terminal/color.h" +#include "terminal/ecma48.h" +#include "terminal/key.h" +#include "terminal/output.h" +#include "terminal/rxvt.h" +#include "terminal/terminal.h" +#include "terminal/xterm.h" +#include "util/debug.h" +#include "util/unicode.h" +#include "util/xsnprintf.h" static void test_parse_term_color(void) { @@ -25,6 +30,7 @@ {{"bold", "blink"}, {-1, -1, ATTR_BOLD | ATTR_BLINK}}, {{"0", "255", "underline"}, {COLOR_BLACK, 255, ATTR_UNDERLINE}}, {{"white", "green", "dim"}, {COLOR_WHITE, COLOR_GREEN, ATTR_DIM}}, + {{"white", "green", "lowintensity"}, {COLOR_WHITE, COLOR_GREEN, ATTR_DIM}}, {{"lightred", "lightyellow"}, {COLOR_LIGHTRED, COLOR_LIGHTYELLOW, 0}}, {{"darkgray", "lightgreen"}, {COLOR_DARKGRAY, COLOR_LIGHTGREEN, 0}}, {{"lightblue", "lightcyan"}, {COLOR_LIGHTBLUE, COLOR_LIGHTCYAN, 0}}, @@ -42,6 +48,7 @@ IEXPECT_EQ(parsed.fg, expected.fg); IEXPECT_EQ(parsed.bg, expected.bg); IEXPECT_EQ(parsed.attr, expected.attr); + IEXPECT_TRUE(same_color(&parsed, &expected)); } } } @@ -98,9 +105,12 @@ {COLOR_RGB(0x00FF88), COLOR_RGB(0x00FF88), 48, COLOR_LIGHTGREEN}, {COLOR_RGB(0xFF0001), COLOR_RGB(0xFF0001), 196, COLOR_LIGHTRED}, {COLOR_RGB(0xAABBCC), COLOR_RGB(0xAABBCC), 146, COLOR_LIGHTBLUE}, + {COLOR_RGB(0x010101), COLOR_RGB(0x010101), 16, COLOR_BLACK}, + {COLOR_RGB(0x070707), COLOR_RGB(0x070707), 232, COLOR_BLACK}, {COLOR_RGB(0x080809), COLOR_RGB(0x080809), 232, COLOR_BLACK}, {COLOR_RGB(0xBABABA), COLOR_RGB(0xBABABA), 250, COLOR_WHITE}, {COLOR_RGB(0xEEEEED), COLOR_RGB(0xEEEEED), 255, COLOR_WHITE}, + {COLOR_RGB(0xEFEFEF), COLOR_RGB(0xEFEFEF), 255, COLOR_WHITE}, }; FOR_EACH_I(i, tests) { const int32_t c = tests[i].input; @@ -112,6 +122,35 @@ } } +static void test_term_color_to_string(void) +{ + static const struct { + const char *expected_string; + const TermColor color; + } tests[] = { + {"red yellow bold", {COLOR_RED, COLOR_YELLOW, ATTR_BOLD}}, + {"#ff0000", {COLOR_RGB(0xff0000), -1, 0}}, + {"#f00a9c reverse", {COLOR_RGB(0xf00a9c), -1, ATTR_REVERSE}}, + {"black #00ffff", {COLOR_BLACK, COLOR_RGB(0x00ffff), 0}}, + {"#010900", {COLOR_RGB(0x010900), COLOR_DEFAULT, 0}}, + {"red strikethrough", {COLOR_RED, -1, ATTR_STRIKETHROUGH}}, + {"231", {231, COLOR_DEFAULT, 0}}, + {"70 48 italic", {70, 48, ATTR_ITALIC}}, + {"default keep", {COLOR_DEFAULT, COLOR_KEEP, 0}}, + {"keep red keep", {COLOR_KEEP, COLOR_RED, ATTR_KEEP}}, + {"88 16 blink bold", {88, 16, ATTR_BOLD | ATTR_BLINK}}, + {"black 255 underline", {COLOR_BLACK, 255, ATTR_UNDERLINE}}, + {"white green dim", {COLOR_WHITE, COLOR_GREEN, ATTR_DIM}}, + {"darkgray lightgreen", {COLOR_DARKGRAY, COLOR_LIGHTGREEN, 0}}, + {"lightmagenta", {COLOR_LIGHTMAGENTA, COLOR_DEFAULT, 0}}, + {"keep 254 keep", {COLOR_KEEP, 254, ATTR_KEEP}}, + }; + FOR_EACH_I(i, tests) { + const char *str = term_color_to_string(&tests[i].color); + EXPECT_STREQ(str, tests[i].expected_string); + } +} + static void test_xterm_parse_key(void) { static const struct xterm_key_test { @@ -119,11 +158,12 @@ ssize_t expected_length; KeyCode expected_key; } tests[] = { - {"\033[Z", 3, MOD_SHIFT | '\t'}, + {"\033[Z", 3, MOD_SHIFT | KEY_TAB}, {"\033[1;2A", 6, MOD_SHIFT | KEY_UP}, {"\033[1;2B", 6, MOD_SHIFT | KEY_DOWN}, {"\033[1;2C", 6, MOD_SHIFT | KEY_RIGHT}, {"\033[1;2D", 6, MOD_SHIFT | KEY_LEFT}, + {"\033[1;2E", 6, MOD_SHIFT | KEY_BEGIN}, {"\033[1;2F", 6, MOD_SHIFT | KEY_END}, {"\033[1;2H", 6, MOD_SHIFT | KEY_HOME}, {"\033[1;8H", 6, MOD_SHIFT | MOD_META | MOD_CTRL | KEY_HOME}, @@ -147,6 +187,7 @@ {"\033[B", 3, KEY_DOWN}, {"\033[C", 3, KEY_RIGHT}, {"\033[D", 3, KEY_LEFT}, + {"\033[E", 3, KEY_BEGIN}, {"\033[F", 3, KEY_END}, {"\033[H", 3, KEY_HOME}, {"\033[L", 3, KEY_INSERT}, @@ -156,14 +197,15 @@ {"\033[4~", 4, KEY_END}, {"\033[5~", 4, KEY_PAGE_UP}, {"\033[6~", 4, KEY_PAGE_DOWN}, - {"\033O ", 3, ' '}, + {"\033O ", 3, KEY_SPACE}, {"\033OA", 3, KEY_UP}, {"\033OB", 3, KEY_DOWN}, {"\033OC", 3, KEY_RIGHT}, {"\033OD", 3, KEY_LEFT}, + {"\033OE", 3, KEY_BEGIN}, {"\033OF", 3, KEY_END}, {"\033OH", 3, KEY_HOME}, - {"\033OI", 3, '\t'}, + {"\033OI", 3, KEY_TAB}, {"\033OM", 3, KEY_ENTER}, {"\033OP", 3, KEY_F1}, {"\033OQ", 3, KEY_F2}, @@ -221,7 +263,7 @@ {"\033[c", 3, MOD_SHIFT | KEY_RIGHT}, {"\033[d", 3, MOD_SHIFT | KEY_LEFT}, // xterm + `modifyOtherKeys` option - {"\033[27;5;9~", 9, MOD_CTRL | '\t'}, + {"\033[27;5;9~", 9, MOD_CTRL | KEY_TAB}, {"\033[27;5;13~", 10, MOD_CTRL | KEY_ENTER}, {"\033[27;6;13~", 10, MOD_CTRL | MOD_SHIFT |KEY_ENTER}, {"\033[27;2;127~", 11, MOD_CTRL | '?'}, @@ -239,7 +281,7 @@ {"\033[27;123;99999999999999999~", 0, 0}, // www.leonerd.org.uk/hacks/fixterms/ {"\033[13;3u", 7, MOD_META | KEY_ENTER}, - {"\033[9;5u", 6, MOD_CTRL | '\t'}, + {"\033[9;5u", 6, MOD_CTRL | KEY_TAB}, {"\033[65;3u", 7, MOD_META | 'A'}, {"\033[108;5u", 8, MOD_CTRL | 'L'}, {"\033[127765;3u", 11, MOD_META | 127765ul}, @@ -253,6 +295,32 @@ {"\033[4294967296;3u", 0, 0}, // UINT32_MAX + 1 {"\033[-1;3u", 0, 0}, {"\033[-2;3u", 0, 0}, + // kitty "full mode" + {"\033_KpA>\033\\", 8, KEY_F6}, + {"\033_KtA>\033\\", 8, KEY_F6}, // 't' = repeat + {"\033_KrA>\033\\", 8, KEY_IGNORE}, // 'r' = release + {"\033_KzA>\033\\", 0, 0}, // 'z' = invalid event type + {"\033_KpH>\033\\", 8, MOD_CTRL | MOD_META | MOD_SHIFT | KEY_F6}, + {"\033_KpI>\033\\", 8, KEY_IGNORE}, // Super bit (0x8) set in mods ('I') + {"\033_KpA>?\\", 0, 0}, // Invalid string terminator + {"\033_KpA>\033?", 0, 0}, // Invalid string terminator + {"\033_KpFe\033\\", 8, MOD_CTRL | 'M'}, // Ctrl+Shift+m (Shift stripped) + {"\033_KpAB\033\\", 8, '\''}, + {"\033_KpA-\033\\", 8, KEY_END}, + {"\033_KpA:\033\\", 8, KEY_IGNORE}, + {"\033_KpA'\033\\", 0, 0}, + {"\033_KpA~\033\\", 0, 0}, + {"\033_KpABA\033\\", 9, KEY_IGNORE}, + {"\033_KpABJ\033\\", 9, '0'}, + {"\033_KpABS\033\\", 9, '9'}, + {"\033_KpABT\033\\", 9, '.'}, + {"\033_KpABU\033\\", 9, '/'}, + {"\033_KpABV\033\\", 9, '*'}, + {"\033_KpABW\033\\", 9, '-'}, + {"\033_KpABX\033\\", 9, '+'}, + {"\033_KpABY\033\\", 9, KEY_ENTER}, + {"\033_KpABZ\033\\", 9, '='}, + {"\033_KpAB1\033\\", 0, 0}, }; FOR_EACH_I(i, tests) { const char *seq = tests[i].escape_sequence; @@ -281,68 +349,94 @@ static void test_xterm_parse_key_combo(void) { static const struct { - char escape_sequence[8]; + char key_str[3]; + char final_byte; KeyCode key; } templates[] = { - {"\033[1;_A", KEY_UP}, - {"\033[1;_B", KEY_DOWN}, - {"\033[1;_C", KEY_RIGHT}, - {"\033[1;_D", KEY_LEFT}, - {"\033[1;_F", KEY_END}, - {"\033[1;_H", KEY_HOME}, - {"\033[2;_~", KEY_INSERT}, - {"\033[3;_~", KEY_DELETE}, - {"\033[5;_~", KEY_PAGE_UP}, - {"\033[6;_~", KEY_PAGE_DOWN}, - {"\033[1;_P", KEY_F1}, - {"\033[1;_Q", KEY_F2}, - {"\033[1;_R", KEY_F3}, - {"\033[1;_S", KEY_F4}, - {"\033[15;_~", KEY_F5}, - {"\033[17;_~", KEY_F6}, - {"\033[18;_~", KEY_F7}, - {"\033[19;_~", KEY_F8}, - {"\033[20;_~", KEY_F9}, - {"\033[21;_~", KEY_F10}, - {"\033[23;_~", KEY_F11}, - {"\033[24;_~", KEY_F12}, + {"1", 'A', KEY_UP}, + {"1", 'B', KEY_DOWN}, + {"1", 'C', KEY_RIGHT}, + {"1", 'D', KEY_LEFT}, + {"1", 'E', KEY_BEGIN}, + {"1", 'F', KEY_END}, + {"1", 'H', KEY_HOME}, + {"1", 'P', KEY_F1}, + {"1", 'Q', KEY_F2}, + {"1", 'R', KEY_F3}, + {"1", 'S', KEY_F4}, + {"2", '~', KEY_INSERT}, + {"3", '~', KEY_DELETE}, + {"5", '~', KEY_PAGE_UP}, + {"6", '~', KEY_PAGE_DOWN}, + {"11", '~', KEY_F1}, + {"12", '~', KEY_F2}, + {"13", '~', KEY_F3}, + {"14", '~', KEY_F4}, + {"15", '~', KEY_F5}, + {"17", '~', KEY_F6}, + {"18", '~', KEY_F7}, + {"19", '~', KEY_F8}, + {"20", '~', KEY_F9}, + {"21", '~', KEY_F10}, + {"23", '~', KEY_F11}, + {"24", '~', KEY_F12}, }; static const struct { - char ch; + char mod_str[4]; KeyCode mask; } modifiers[] = { - {'2', MOD_SHIFT}, - {'3', MOD_META}, - {'4', MOD_SHIFT | MOD_META}, - {'5', MOD_CTRL}, - {'6', MOD_SHIFT | MOD_CTRL}, - {'7', MOD_META | MOD_CTRL}, - {'8', MOD_SHIFT | MOD_META | MOD_CTRL} + {"0", 0}, + {"1", 0}, + {"2", MOD_SHIFT}, + {"3", MOD_META}, + {"4", MOD_SHIFT | MOD_META}, + {"5", MOD_CTRL}, + {"6", MOD_SHIFT | MOD_CTRL}, + {"7", MOD_META | MOD_CTRL}, + {"8", MOD_SHIFT | MOD_META | MOD_CTRL}, + {"9", MOD_META}, + {"10", MOD_META | MOD_SHIFT}, + {"11", MOD_META}, + {"12", MOD_META | MOD_SHIFT}, + {"13", MOD_META | MOD_CTRL}, + {"14", MOD_META | MOD_CTRL | MOD_SHIFT}, + {"15", MOD_META | MOD_CTRL}, + {"16", MOD_META | MOD_CTRL | MOD_SHIFT}, + {"17", 0}, + {"18", 0}, + {"400", 0}, }; FOR_EACH_I(i, templates) { FOR_EACH_I(j, modifiers) { - char seq[8]; - memcpy(seq, templates[i].escape_sequence, 8); - BUG_ON(seq[7] != '\0'); - char *underscore = strchr(seq, '_'); - ASSERT_NONNULL(underscore); - *underscore = modifiers[j].ch; - size_t seq_length = strlen(seq); - KeyCode key; + char seq[16]; + size_t seq_length = xsnprintf ( + seq, + sizeof seq, + "\033[%s;%s%c", + templates[i].key_str, + modifiers[j].mod_str, + templates[i].final_byte + ); + KeyCode key = 24; ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key); + if (modifiers[j].mask == 0) { + EXPECT_EQ(parsed_length, 0); + EXPECT_EQ(key, 24); + continue; + } EXPECT_EQ(parsed_length, seq_length); EXPECT_EQ(key, modifiers[j].mask | templates[i].key); // Truncated - key = 0x18; + key = 25; for (size_t n = seq_length - 1; n != 0; n--) { parsed_length = xterm_parse_key(seq, n, &key); EXPECT_EQ(parsed_length, -1); - EXPECT_EQ(key, 0x18); + EXPECT_EQ(key, 25); } // Overlength - key = 0x18; + key = 26; seq[seq_length] = '~'; parsed_length = xterm_parse_key(seq, seq_length + 1, &key); EXPECT_EQ(parsed_length, seq_length); @@ -351,17 +445,18 @@ } } -static void test_xterm_parse_key_combo_rxvt(void) +static void test_rxvt_parse_key(void) { static const struct { char escape_sequence[8]; KeyCode key; } templates[] = { - {"\033[3_", KEY_DELETE}, - {"\033[5_", KEY_PAGE_UP}, - {"\033[6_", KEY_PAGE_DOWN}, - {"\033[7_", KEY_HOME}, - {"\033[8_", KEY_END}, + {"\033\033[2_", KEY_INSERT}, + {"\033\033[3_", KEY_DELETE}, + {"\033\033[5_", KEY_PAGE_UP}, + {"\033\033[6_", KEY_PAGE_DOWN}, + {"\033\033[7_", KEY_HOME}, + {"\033\033[8_", KEY_END}, }; static const struct { @@ -369,41 +464,48 @@ KeyCode mask; } modifiers[] = { {'~', 0}, - /* - Note: the tests for these non-standard rxvt modifiers have been - disabled, because using '$' as a final byte is a violation of the - ECMA-48 spec: - {'^', MOD_CTRL}, {'$', MOD_SHIFT}, {'@', MOD_SHIFT | MOD_CTRL}, - - For the rxvt developers to invent a new key encoding schemes where - a perfectly good, de facto standard (xterm) already existed was - foolish. To then violate the spec in the process was pure stupidity. - */ }; FOR_EACH_I(i, templates) { FOR_EACH_I(j, modifiers) { char seq[8]; memcpy(seq, templates[i].escape_sequence, 8); - BUG_ON(seq[7] != '\0'); + ASSERT_EQ(seq[7], '\0'); char *underscore = strchr(seq, '_'); ASSERT_NONNULL(underscore); *underscore = modifiers[j].ch; size_t seq_length = strlen(seq); KeyCode key; - ssize_t parsed_length = xterm_parse_key(seq, seq_length, &key); + + ssize_t parsed_length = rxvt_parse_key(seq, seq_length, &key); EXPECT_EQ(parsed_length, seq_length); + EXPECT_EQ(key, modifiers[j].mask | templates[i].key | MOD_META); + + parsed_length = rxvt_parse_key(seq + 1, seq_length - 1, &key); + EXPECT_EQ(parsed_length, seq_length - 1); EXPECT_EQ(key, modifiers[j].mask | templates[i].key); + + parsed_length = rxvt_parse_key(seq, seq_length - 1, &key); + EXPECT_EQ(parsed_length, -1); + + parsed_length = rxvt_parse_key(seq + 1, seq_length - 2, &key); + EXPECT_EQ(parsed_length, -1); } } + + // Check that rxvt_parse_key() falls back to xterm_parse_key() + KeyCode key; + ssize_t n = rxvt_parse_key(STRN("\033[1;5A"), &key); + EXPECT_EQ(n, 6); + EXPECT_EQ(key, MOD_CTRL | KEY_UP); } -static void test_key_to_string(void) +static void test_keycode_to_string(void) { - static const struct key_to_string_test { + static const struct keycode_to_string_test { const char *str; KeyCode key; } tests[] = { @@ -411,15 +513,16 @@ {"Z", 'Z'}, {"0", '0'}, {"{", '{'}, - {"space", ' '}, + {"space", KEY_SPACE}, {"enter", KEY_ENTER}, - {"tab", '\t'}, + {"tab", KEY_TAB}, {"insert", KEY_INSERT}, {"delete", KEY_DELETE}, {"home", KEY_HOME}, {"end", KEY_END}, {"pgup", KEY_PAGE_UP}, {"pgdown", KEY_PAGE_DOWN}, + {"begin", KEY_BEGIN}, {"left", KEY_LEFT}, {"right", KEY_RIGHT}, {"up", KEY_UP}, @@ -430,27 +533,185 @@ {"F1", KEY_F1}, {"F12", KEY_F12}, {"M-enter", MOD_META | KEY_ENTER}, - {"M-space", MOD_META | ' '}, - {"S-tab", MOD_SHIFT | '\t'}, + {"M-space", MOD_META | KEY_SPACE}, + {"S-tab", MOD_SHIFT | KEY_TAB}, {"C-M-S-F12", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_F12}, {"C-M-S-up", MOD_CTRL | MOD_META | MOD_SHIFT | KEY_UP}, {"C-M-delete", MOD_CTRL | MOD_META | KEY_DELETE}, {"C-home", MOD_CTRL | KEY_HOME}, }; FOR_EACH_I(i, tests) { - const char *str = key_to_string(tests[i].key); + const char *str = keycode_to_string(tests[i].key); IEXPECT_STREQ(str, tests[i].str); + KeyCode key = 0; + IEXPECT_TRUE(parse_key_string(&key, tests[i].str)); + IEXPECT_EQ(key, tests[i].key); } + EXPECT_STREQ(keycode_to_string(KEY_PASTE), "INVALID (0x08000000)"); + EXPECT_STREQ(keycode_to_string(UINT32_MAX), "INVALID (0xFFFFFFFF)"); +} + +static void test_parse_key_string(void) +{ + KeyCode key = 0; + EXPECT_TRUE(parse_key_string(&key, "^I")); + EXPECT_EQ(key, KEY_TAB); + EXPECT_TRUE(parse_key_string(&key, "^M")); + EXPECT_EQ(key, KEY_ENTER); + EXPECT_TRUE(parse_key_string(&key, "C-I")); + EXPECT_EQ(key, KEY_TAB); + EXPECT_TRUE(parse_key_string(&key, "C-M")); + EXPECT_EQ(key, KEY_ENTER); + + key = 0x18; + EXPECT_FALSE(parse_key_string(&key, "C-")); + EXPECT_EQ(key, 0x18); + EXPECT_FALSE(parse_key_string(&key, "C-M-")); + EXPECT_EQ(key, 0x18); + EXPECT_FALSE(parse_key_string(&key, "paste")); + EXPECT_EQ(key, 0x18); + EXPECT_FALSE(parse_key_string(&key, "???")); + EXPECT_EQ(key, 0x18); +} + +static void clear_obuf(void) +{ + ASSERT_TRUE(obuf_avail() > 8); + memset(obuf.buf, '\0', obuf.count + 8); + obuf.count = 0; + obuf.x = 0; +} + +static void test_term_add_str(void) +{ + EXPECT_EQ(obuf.tab, TAB_NORMAL); + EXPECT_EQ(obuf.count, 0); + EXPECT_EQ(obuf.x, 0); + EXPECT_EQ(obuf.scroll_x, 0); + EXPECT_EQ(obuf.width, 0); + + term_add_str("this should write nothing because obuf.width == 0"); + EXPECT_EQ(obuf.count, 0); + EXPECT_EQ(obuf.x, 0); + + term_output_reset(0, 80, 0); + EXPECT_EQ(obuf.tab, TAB_CONTROL); + EXPECT_EQ(obuf.tab_width, 8); + EXPECT_EQ(obuf.x, 0); + EXPECT_EQ(obuf.width, 80); + EXPECT_EQ(obuf.scroll_x, 0); + EXPECT_EQ(terminal.width, 80); + EXPECT_EQ(obuf.can_clear, true); + + term_add_str("1\xF0\x9F\xA7\xB2 \t xyz \t\r \xC2\xB6"); + EXPECT_EQ(obuf.count, 20); + EXPECT_EQ(obuf.x, 17); + EXPECT_STREQ(obuf.buf, "1\xF0\x9F\xA7\xB2 ^I xyz ^I^M \xC2\xB6"); + + EXPECT_TRUE(term_put_char(0x10FFFF)); + EXPECT_EQ(obuf.count, 24); + EXPECT_EQ(obuf.x, 21); + EXPECT_STREQ(obuf.buf + 20, "<" "??" ">"); + clear_obuf(); +} + +static void test_ecma48_clear_to_eol(void) +{ + ecma48_clear_to_eol(); + EXPECT_EQ(obuf.count, 3); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[K", 3); + clear_obuf(); +} + +static void test_ecma48_move_cursor(void) +{ + ecma48_move_cursor(12, 5); + EXPECT_EQ(obuf.count, 7); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[6;13H", 7); + clear_obuf(); + + ecma48_move_cursor(0, 22); + EXPECT_EQ(obuf.count, 5); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[23H", 5); + clear_obuf(); } -DISABLE_WARNING("-Wmissing-prototypes") +static void test_ecma48_repeat_byte(void) +{ + ecma48_repeat_byte('x', 40); + EXPECT_EQ(obuf.count, 6); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "x\033[39b", 6); + clear_obuf(); + + ecma48_repeat_byte('-', 5); + EXPECT_EQ(obuf.count, 5); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "-----", 5); + clear_obuf(); + + ecma48_repeat_byte('\n', 8); + EXPECT_EQ(obuf.count, 8); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\n\n\n\n\n\n\n\n", 8); + clear_obuf(); +} -void test_terminal(void) +static void test_ecma48_set_color(void) { - test_parse_term_color(); - test_color_to_nearest(); - test_xterm_parse_key(); - test_xterm_parse_key_combo(); - test_xterm_parse_key_combo_rxvt(); - test_key_to_string(); + const TermColorCapabilityType color_type = terminal.color_type; + EXPECT_EQ(color_type, TERM_8_COLOR); + EXPECT_EQ(terminal.ncv_attributes, 0); + terminal.color_type = TERM_TRUE_COLOR; + + TermColor c = { + .fg = COLOR_RED, + .bg = COLOR_YELLOW, + .attr = ATTR_BOLD | ATTR_REVERSE, + }; + + ecma48_set_color(&c); + EXPECT_EQ(obuf.count, 14); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[0;1;7;31;43m", 14); + clear_obuf(); + + c.attr = 0; + c.fg = COLOR_RGB(0x12ef46); + ecma48_set_color(&c); + EXPECT_EQ(obuf.count, 22); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[0;38;2;18;239;70;43m", 22); + clear_obuf(); + + c.fg = 144; + c.bg = COLOR_DEFAULT; + ecma48_set_color(&c); + EXPECT_EQ(obuf.count, 13); + EXPECT_EQ(obuf.x, 0); + EXPECT_MEMEQ(obuf.buf, "\033[0;38;5;144m", 13); + clear_obuf(); + + terminal.color_type = color_type; } + +static const TestEntry tests[] = { + TEST(test_parse_term_color), + TEST(test_color_to_nearest), + TEST(test_term_color_to_string), + TEST(test_xterm_parse_key), + TEST(test_xterm_parse_key_combo), + TEST(test_rxvt_parse_key), + TEST(test_keycode_to_string), + TEST(test_parse_key_string), + TEST(test_term_add_str), + TEST(test_ecma48_clear_to_eol), + TEST(test_ecma48_move_cursor), + TEST(test_ecma48_repeat_byte), + TEST(test_ecma48_set_color), +}; + +const TestGroup terminal_tests = TEST_GROUP(tests); diff -Nru dte-1.9.1/test/test.c dte-1.10/test/test.c --- dte-1.9.1/test/test.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/test.c 2021-04-03 21:08:53.000000000 +0000 @@ -2,9 +2,9 @@ #include #include #include "test.h" -#include "../src/util/str-util.h" +#include "util/str-util.h" -unsigned int failed; +unsigned int passed, failed; void test_fail(const char *file, int line, const char *format, ...) { @@ -24,6 +24,8 @@ s1 = s1 ? s1 : "(null)"; s2 = s2 ? s2 : "(null)"; test_fail(file, line, "Strings not equal: '%s', '%s'", s1, s2); + } else { + passed++; } } @@ -31,6 +33,17 @@ { if (unlikely(p1 != p2)) { test_fail(file, line, "Pointers not equal: %p, %p", p1, p2); + } else { + passed++; + } +} + +void expect_memeq(const char *file, int line, const void *m1, const void *m2, size_t len) +{ + if (unlikely(!mem_equal(m1, m2, len))) { + test_fail(file, line, "Bytes not equal"); + } else { + passed++; } } @@ -38,13 +51,17 @@ { if (unlikely(a != b)) { test_fail(file, line, "Values not equal: %jd, %jd", a, b); + } else { + passed++; } } void expect_uint_eq(const char *file, int line, uintmax_t a, uintmax_t b) { if (unlikely(a != b)) { - test_fail(file, line, "Values not equal: %ju, %ju", a, b); + test_fail(file, line, "Values not equal: 0x%jx, 0x%jx", a, b); + } else { + passed++; } } @@ -52,6 +69,8 @@ { if (unlikely(!x)) { test_fail(file, line, "Unexpected false value"); + } else { + passed++; } } @@ -59,6 +78,8 @@ { if (unlikely(x)) { test_fail(file, line, "Unexpected true value"); + } else { + passed++; } } @@ -66,6 +87,8 @@ { if (unlikely(ptr != NULL)) { test_fail(file, line, "Expected NULL, but got: %p", ptr); + } else { + passed++; } } @@ -73,6 +96,8 @@ { if (unlikely(ptr == NULL)) { test_fail(file, line, "Unexpected NULL pointer"); + } else { + passed++; } } @@ -83,6 +108,8 @@ b = b ? b : "(null)"; i++; test_fail(f, l, "Test #%zu: strings not equal: '%s', '%s'", i, a, b); + } else { + passed++; } } @@ -91,6 +118,8 @@ if (unlikely(a != b)) { i++; test_fail(file, line, "Test #%zu: values not equal: %jd, %jd", i, a, b); + } else { + passed++; } } @@ -99,6 +128,18 @@ if (unlikely(!x)) { i++; test_fail(file, line, "Test #%zu: unexpected false value", i); + } else { + passed++; + } +} + +void assert_ptreq(const char *file, int line, const void *p1, const void *p2) +{ + if (unlikely(p1 != p2)) { + test_fail(file, line, "ERROR: Pointers not equal: %p, %p", p1, p2); + abort(); + } else { + passed++; } } @@ -107,6 +148,8 @@ if (unlikely(a != b)) { test_fail(file, line, "ERROR: Values not equal: %jd, %jd", a, b); abort(); + } else { + passed++; } } @@ -115,6 +158,8 @@ if (unlikely(!x)) { test_fail(file, line, "ERROR: Unexpected false value"); abort(); + } else { + passed++; } } @@ -123,5 +168,7 @@ if (unlikely(ptr == NULL)) { test_fail(file, line, "ERROR: Unexpected NULL pointer"); abort(); + } else { + passed++; } } diff -Nru dte-1.9.1/test/test.h dte-1.10/test/test.h --- dte-1.9.1/test/test.h 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/test.h 2021-04-03 21:08:53.000000000 +0000 @@ -1,50 +1,73 @@ #ifndef TEST_TEST_H #define TEST_TEST_H +#include #include #include #include -#include "../src/util/macros.h" +#include "util/macros.h" + +extern unsigned int passed, failed; + +typedef struct { + const char *name; + void (*func)(void); +} TestEntry; + +typedef struct { + const TestEntry *tests; + size_t nr_tests; +} TestGroup; + +#define TEST(e) (TestEntry) { \ + .name = #e, \ + .func = e \ +} + +#define TEST_GROUP(t) { \ + .tests = t, \ + .nr_tests = ARRAY_COUNT(t) \ +} #define FOR_EACH_I(i, array) \ for (size_t i = 0; i < ARRAY_COUNT(array); i++) #define TEST_FAIL(...) test_fail(__FILE__, __LINE__, __VA_ARGS__) - -#define EXPECT_STREQ(s1, s2) expect_streq(__FILE__, __LINE__, s1, s2) -#define EXPECT_PTREQ(p1, p2) expect_ptreq(__FILE__, __LINE__, p1, p2) -#define EXPECT_EQ(a, b) expect_eq(__FILE__, __LINE__, a, b) -#define EXPECT_UINT_EQ(a, b) expect_uint_eq(__FILE__, __LINE__, a, b) -#define EXPECT_NULL(p) expect_null(__FILE__, __LINE__, p) -#define EXPECT_NONNULL(p) expect_nonnull(__FILE__, __LINE__, p) -#define EXPECT_TRUE(x) expect_true(__FILE__, __LINE__, x) -#define EXPECT_FALSE(x) expect_false(__FILE__, __LINE__, x) - -#define IEXPECT_EQ(a, b) iexpect_eq(__FILE__, __LINE__, i, a, b) -#define IEXPECT_STREQ(s1, s2) iexpect_streq(__FILE__, __LINE__, i, s1, s2) -#define IEXPECT_TRUE(x) iexpect_true(__FILE__, __LINE__, i, x) - -#define ASSERT_EQ(a, b) assert_eq(__FILE__, __LINE__, a, b) -#define ASSERT_TRUE(x) assert_true(__FILE__, __LINE__, x) -#define ASSERT_NONNULL(ptr) assert_nonnull(__FILE__, __LINE__, ptr) - -extern unsigned int failed; +#define EXPECT(fn, ...) expect_##fn(__FILE__, __LINE__, __VA_ARGS__) +#define IEXPECT(fn, ...) iexpect_##fn(__FILE__, __LINE__, i, __VA_ARGS__) +#define ASSERT(fn, ...) assert_##fn(__FILE__, __LINE__, __VA_ARGS__) + +#define EXPECT_STREQ(s1, s2) EXPECT(streq, s1, s2) +#define EXPECT_PTREQ(p1, p2) EXPECT(ptreq, p1, p2) +#define EXPECT_MEMEQ(m1, m2, len) EXPECT(memeq, m1, m2, len) +#define EXPECT_EQ(a, b) EXPECT(eq, a, b) +#define EXPECT_UINT_EQ(a, b) EXPECT(uint_eq, a, b) +#define EXPECT_NULL(p) EXPECT(null, p) +#define EXPECT_NONNULL(p) EXPECT(nonnull, p) +#define EXPECT_TRUE(x) EXPECT(true, x) +#define EXPECT_FALSE(x) EXPECT(false, x) +#define IEXPECT_EQ(a, b) IEXPECT(eq, a, b) +#define IEXPECT_STREQ(s1, s2) IEXPECT(streq, s1, s2) +#define IEXPECT_TRUE(x) IEXPECT(true, x) +#define ASSERT_PTREQ(p1, p2) ASSERT(ptreq, p1, p2) +#define ASSERT_EQ(a, b) ASSERT(eq, a, b) +#define ASSERT_TRUE(x) ASSERT(true, x) +#define ASSERT_NONNULL(ptr) ASSERT(nonnull, ptr) void test_fail(const char *file, int line, const char *format, ...) PRINTF(3); - void expect_streq(const char *file, int line, const char *s1, const char *s2); void expect_ptreq(const char *file, int line, const void *p1, const void *p2); +void expect_memeq(const char *file, int line, const void *m1, const void *m2, size_t len); void expect_eq(const char *file, int line, intmax_t a, intmax_t b); void expect_uint_eq(const char *file, int line, uintmax_t a, uintmax_t b); void expect_true(const char *file, int line, bool x); void expect_false(const char *file, int line, bool x); void expect_null(const char *file, int line, const void *p); void expect_nonnull(const char *file, int line, const void *p); - void iexpect_streq(const char *file, int line, size_t i, const char *s1, const char *s2); void iexpect_eq(const char *file, int line, size_t i, intmax_t a, intmax_t b); void iexpect_true(const char *file, int line, size_t i, bool x); - +void assert_ptreq(const char *file, int line, const void *p1, const void *p2); void assert_eq(const char *file, int line, intmax_t a, intmax_t b); void assert_true(const char *file, int line, bool x); void assert_nonnull(const char *file, int line, const void *ptr); diff -Nru dte-1.9.1/test/util.c dte-1.10/test/util.c --- dte-1.9.1/test/util.c 2019-09-29 15:51:55.000000000 +0000 +++ dte-1.10/test/util.c 2021-04-03 21:08:53.000000000 +0000 @@ -1,26 +1,32 @@ #include #include #include +#include #include +#include #include #include "test.h" -#include "../src/util/ascii.h" -#include "../src/util/bit.h" -#include "../src/util/checked-arith.h" -#include "../src/util/hashset.h" -#include "../src/util/path.h" -#include "../src/util/ptr-array.h" -#include "../src/util/readfile.h" -#include "../src/util/str-util.h" -#include "../src/util/string-view.h" -#include "../src/util/string.h" -#include "../src/util/strtonum.h" -#include "../src/util/unicode.h" -#include "../src/util/utf8.h" -#include "../src/util/xmalloc.h" -#include "../src/util/xsnprintf.h" +#include "util/ascii.h" +#include "util/base64.h" +#include "util/checked-arith.h" +#include "util/hash.h" +#include "util/hashmap.h" +#include "util/hashset.h" +#include "util/numtostr.h" +#include "util/path.h" +#include "util/ptr-array.h" +#include "util/readfile.h" +#include "util/str-util.h" +#include "util/string-view.h" +#include "util/string.h" +#include "util/strtonum.h" +#include "util/unicode.h" +#include "util/utf8.h" +#include "util/xmalloc.h" +#include "util/xsnprintf.h" +#include "util/xstdio.h" -static void test_macros(void) +static void test_util_macros(void) { EXPECT_EQ(STRLEN(""), 0); EXPECT_EQ(STRLEN("a"), 1); @@ -30,10 +36,31 @@ EXPECT_EQ(ARRAY_COUNT("a"), 2); EXPECT_EQ(ARRAY_COUNT("123456789"), 10); + const UNUSED char a2[] = {1, 2}; + const UNUSED int a3[] = {1, 2, 3}; + const UNUSED long long a4[] = {1, 2, 3, 4}; + EXPECT_EQ(ARRAY_COUNT(a2), 2); + EXPECT_EQ(ARRAY_COUNT(a3), 3); + EXPECT_EQ(ARRAY_COUNT(a4), 4); + EXPECT_EQ(MIN(0, 1), 0); EXPECT_EQ(MIN(99, 100), 99); EXPECT_EQ(MIN(-10, 10), -10); + EXPECT_EQ(MAX(0, 1), 1); + EXPECT_EQ(MAX(99, 100), 100); + EXPECT_EQ(MAX(-10, 10), 10); + + int n = snprintf(NULL, 0, "%d", INT_MIN); + EXPECT_TRUE(n >= STRLEN("-2147483647")); + EXPECT_TRUE(DECIMAL_STR_MAX(int) > n); + n = snprintf(NULL, 0, "%llu", ULLONG_MAX); + EXPECT_TRUE(n >= STRLEN("18446744073709551615")); + EXPECT_TRUE(DECIMAL_STR_MAX(unsigned long long) > n); +} + +static void test_IS_POWER_OF_2(void) +{ EXPECT_TRUE(IS_POWER_OF_2(1)); EXPECT_TRUE(IS_POWER_OF_2(2)); EXPECT_TRUE(IS_POWER_OF_2(4)); @@ -41,10 +68,62 @@ EXPECT_TRUE(IS_POWER_OF_2(4096)); EXPECT_TRUE(IS_POWER_OF_2(8192)); EXPECT_TRUE(IS_POWER_OF_2(1ULL << 63)); + EXPECT_FALSE(IS_POWER_OF_2(0)); EXPECT_FALSE(IS_POWER_OF_2(3)); + EXPECT_FALSE(IS_POWER_OF_2(5)); + EXPECT_FALSE(IS_POWER_OF_2(6)); + EXPECT_FALSE(IS_POWER_OF_2(7)); EXPECT_FALSE(IS_POWER_OF_2(12)); + EXPECT_FALSE(IS_POWER_OF_2(15)); + EXPECT_FALSE(IS_POWER_OF_2(-2)); EXPECT_FALSE(IS_POWER_OF_2(-10)); + + const uintmax_t max_pow2 = UINTMAX_MAX & ~(UINTMAX_MAX >> 1); + EXPECT_TRUE(max_pow2 >= 1ULL << 63); + EXPECT_TRUE(IS_POWER_OF_2(max_pow2)); + EXPECT_UINT_EQ(max_pow2 << 1, 0); + + for (uintmax_t i = max_pow2; i > 4; i >>= 1) { + EXPECT_TRUE(IS_POWER_OF_2(i)); + for (uintmax_t j = 1; j < 4; j++) { + EXPECT_FALSE(IS_POWER_OF_2(i + j)); + EXPECT_FALSE(IS_POWER_OF_2(i - j)); + } + } +} + +static void test_xstreq(void) +{ + EXPECT_TRUE(xstreq("foo", "foo")); + EXPECT_TRUE(xstreq("foo\0\n", "foo\0\0")); + EXPECT_TRUE(xstreq("\0foo", "\0bar")); + EXPECT_TRUE(xstreq(NULL, NULL)); + EXPECT_FALSE(xstreq("foo", "bar")); + EXPECT_FALSE(xstreq("abc", "abcd")); + EXPECT_FALSE(xstreq("abcd", "abc")); + EXPECT_FALSE(xstreq(NULL, "")); + EXPECT_FALSE(xstreq("", NULL)); +} + +static void test_hex_decode(void) +{ + EXPECT_EQ(hex_decode('0'), 0); + EXPECT_EQ(hex_decode('1'), 1); + EXPECT_EQ(hex_decode('9'), 9); + EXPECT_EQ(hex_decode('a'), 10); + EXPECT_EQ(hex_decode('A'), 10); + EXPECT_EQ(hex_decode('f'), 15); + EXPECT_EQ(hex_decode('F'), 15); + EXPECT_EQ(hex_decode('g'), HEX_INVALID); + EXPECT_EQ(hex_decode('G'), HEX_INVALID); + EXPECT_EQ(hex_decode('@'), HEX_INVALID); + EXPECT_EQ(hex_decode('/'), HEX_INVALID); + EXPECT_EQ(hex_decode(':'), HEX_INVALID); + EXPECT_EQ(hex_decode(' '), HEX_INVALID); + EXPECT_EQ(hex_decode('~'), HEX_INVALID); + EXPECT_EQ(hex_decode('\0'), HEX_INVALID); + EXPECT_EQ(hex_decode(0xFF), HEX_INVALID); } static void test_ascii(void) @@ -105,21 +184,6 @@ EXPECT_FALSE(ascii_isdigit('\0')); EXPECT_FALSE(ascii_isdigit(0xFF)); - EXPECT_TRUE(ascii_isxdigit('0')); - EXPECT_TRUE(ascii_isxdigit('1')); - EXPECT_TRUE(ascii_isxdigit('9')); - EXPECT_TRUE(ascii_isxdigit('a')); - EXPECT_TRUE(ascii_isxdigit('A')); - EXPECT_TRUE(ascii_isxdigit('f')); - EXPECT_TRUE(ascii_isxdigit('F')); - EXPECT_FALSE(ascii_isxdigit('g')); - EXPECT_FALSE(ascii_isxdigit('G')); - EXPECT_FALSE(ascii_isxdigit('@')); - EXPECT_FALSE(ascii_isxdigit('/')); - EXPECT_FALSE(ascii_isxdigit(':')); - EXPECT_FALSE(ascii_isxdigit('\0')); - EXPECT_FALSE(ascii_isxdigit(0xFF)); - EXPECT_TRUE(ascii_is_nonspace_cntrl('\0')); EXPECT_TRUE(ascii_is_nonspace_cntrl('\a')); EXPECT_TRUE(ascii_is_nonspace_cntrl('\b')); @@ -194,18 +258,6 @@ EXPECT_FALSE(is_regex_special_char(0x80)); EXPECT_FALSE(is_regex_special_char(0xFF)); - EXPECT_EQ(hex_decode('0'), 0); - EXPECT_EQ(hex_decode('9'), 9); - EXPECT_EQ(hex_decode('a'), 10); - EXPECT_EQ(hex_decode('A'), 10); - EXPECT_EQ(hex_decode('f'), 15); - EXPECT_EQ(hex_decode('F'), 15); - EXPECT_EQ(hex_decode('g'), -1); - EXPECT_EQ(hex_decode('G'), -1); - EXPECT_EQ(hex_decode(' '), -1); - EXPECT_EQ(hex_decode('\0'), -1); - EXPECT_EQ(hex_decode('~'), -1); - EXPECT_TRUE(ascii_streq_icase("", "")); EXPECT_TRUE(ascii_streq_icase("a", "a")); EXPECT_TRUE(ascii_streq_icase("a", "A")); @@ -232,45 +284,98 @@ EXPECT_FALSE(mem_equal_icase(s1, s2, 7)); EXPECT_FALSE(mem_equal_icase(s1, s2, 8)); - char *saved_locale = xstrdup(setlocale(LC_CTYPE, NULL)); - setlocale(LC_CTYPE, "C"); + // Query the current locale + const char *locale = setlocale(LC_CTYPE, NULL); + ASSERT_NONNULL(locale); + + // Copy the locale string (which may be in static storage) + char *saved_locale = xstrdup(locale); + + // Check that the ascii_is*() functions behave like their corresponding + // macros, when in the standard "C" locale + ASSERT_NONNULL(setlocale(LC_CTYPE, "C")); for (int i = -1; i < 256; i++) { - EXPECT_EQ(!!ascii_isalpha(i), !!isalpha(i)); - EXPECT_EQ(!!ascii_isalnum(i), !!isalnum(i)); - EXPECT_EQ(!!ascii_islower(i), !!islower(i)); - EXPECT_EQ(!!ascii_isupper(i), !!isupper(i)); - EXPECT_EQ(!!ascii_iscntrl(i), !!iscntrl(i)); - EXPECT_EQ(!!ascii_isdigit(i), !!isdigit(i)); - EXPECT_EQ(!!ascii_isblank(i), !!isblank(i)); - EXPECT_EQ(!!ascii_isprint(i), !!isprint(i)); - EXPECT_EQ(!!ascii_isxdigit(i), !!isxdigit(i)); + EXPECT_EQ(ascii_isalpha(i), !!isalpha(i)); + EXPECT_EQ(ascii_isalnum(i), !!isalnum(i)); + EXPECT_EQ(ascii_islower(i), !!islower(i)); + EXPECT_EQ(ascii_isupper(i), !!isupper(i)); + EXPECT_EQ(ascii_iscntrl(i), !!iscntrl(i)); + EXPECT_EQ(ascii_isdigit(i), !!isdigit(i)); + EXPECT_EQ(ascii_isblank(i), !!isblank(i)); + EXPECT_EQ(ascii_isprint(i), !!isprint(i)); + EXPECT_EQ(hex_decode(i) <= 0xF, !!isxdigit(i)); + EXPECT_EQ(is_alpha_or_underscore(i), !!isalpha(i) || i == '_'); + EXPECT_EQ(is_alnum_or_underscore(i), !!isalnum(i) || i == '_'); if (i != '\v' && i != '\f') { - EXPECT_EQ(!!ascii_isspace(i), !!isspace(i)); + EXPECT_EQ(ascii_isspace(i), !!isspace(i)); + EXPECT_EQ(ascii_is_nonspace_cntrl(i), !!iscntrl(i) && !isspace(i)); + } + if (i != -1) { + EXPECT_EQ(ascii_tolower(i), tolower(i)); + EXPECT_EQ(ascii_toupper(i), toupper(i)); } } + + // Restore the original locale setlocale(LC_CTYPE, saved_locale); free(saved_locale); } +static void test_base64(void) +{ + EXPECT_EQ(base64_decode('A'), 0); + EXPECT_EQ(base64_decode('Z'), 25); + EXPECT_EQ(base64_decode('a'), 26); + EXPECT_EQ(base64_decode('z'), 51); + EXPECT_EQ(base64_decode('0'), 52); + EXPECT_EQ(base64_decode('9'), 61); + EXPECT_EQ(base64_decode('+'), 62); + EXPECT_EQ(base64_decode('/'), 63); + EXPECT_EQ(base64_decode('='), BASE64_PADDING); + EXPECT_EQ(base64_decode(0x00), BASE64_INVALID); + EXPECT_EQ(base64_decode(0xFF), BASE64_INVALID); + + for (unsigned int i = 'A'; i <= 'Z'; i++) { + IEXPECT_EQ(base64_decode(i), i - 'A'); + } + + for (unsigned int i = 'a'; i <= 'z'; i++) { + IEXPECT_EQ(base64_decode(i), (i - 'a') + 26); + } + + for (unsigned int i = '0'; i <= '9'; i++) { + IEXPECT_EQ(base64_decode(i), (i - '0') + 52); + } + + for (unsigned int i = 0; i < 256; i++) { + unsigned int val = base64_decode(i); + if (ascii_isalnum(i) || i == '+' || i == '/') { + IEXPECT_EQ(val, val & 63); + } else { + IEXPECT_EQ(val, val & 192); + } + } +} + static void test_string(void) { String s = STRING_INIT; EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); - EXPECT_PTREQ(s.buffer, NULL); + EXPECT_NULL(s.buffer); char *cstr = string_clone_cstring(&s); EXPECT_STREQ(cstr, ""); free(cstr); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); - EXPECT_PTREQ(s.buffer, NULL); + EXPECT_NULL(s.buffer); string_insert_ch(&s, 0, 0x1F4AF); EXPECT_EQ(s.len, 4); EXPECT_STREQ(string_borrow_cstring(&s), "\xF0\x9F\x92\xAF"); - string_add_str(&s, "test"); + string_append_cstring(&s, "test"); EXPECT_EQ(s.len, 8); EXPECT_STREQ(string_borrow_cstring(&s), "\xF0\x9F\x92\xAFtest"); @@ -278,9 +383,8 @@ EXPECT_EQ(s.len, 3); EXPECT_STREQ(string_borrow_cstring(&s), "est"); - string_make_space(&s, 0, 1); + string_insert_ch(&s, 0, 't'); EXPECT_EQ(s.len, 4); - s.buffer[0] = 't'; EXPECT_STREQ(string_borrow_cstring(&s), "test"); string_clear(&s); @@ -297,58 +401,116 @@ string_free(&s); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); - EXPECT_PTREQ(s.buffer, NULL); + EXPECT_NULL(s.buffer); for (size_t i = 0; i < 40; i++) { - string_add_byte(&s, 'a'); + string_append_byte(&s, 'a'); } EXPECT_EQ(s.len, 40); cstr = string_steal_cstring(&s); EXPECT_EQ(s.len, 0); EXPECT_EQ(s.alloc, 0); - EXPECT_PTREQ(s.buffer, NULL); + EXPECT_NULL(s.buffer); EXPECT_STREQ(cstr, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); free(cstr); + + s = string_new(12); + EXPECT_EQ(s.len, 0); + EXPECT_EQ(s.alloc, 16); + ASSERT_NONNULL(s.buffer); + + string_append_cstring(&s, "123"); + EXPECT_STREQ(string_borrow_cstring(&s), "123"); + EXPECT_EQ(s.len, 3); + + string_append_string(&s, &s); + EXPECT_STREQ(string_borrow_cstring(&s), "123123"); + EXPECT_EQ(s.len, 6); + + string_insert_buf(&s, 2, STRN("foo")); + EXPECT_STREQ(string_borrow_cstring(&s), "12foo3123"); + EXPECT_EQ(s.len, 9); + + cstr = string_clone_cstring(&s); + EXPECT_STREQ(cstr, "12foo3123"); + + EXPECT_EQ(string_insert_ch(&s, 0, '>'), 1); + EXPECT_STREQ(string_borrow_cstring(&s), ">12foo3123"); + EXPECT_EQ(s.len, 10); + + string_free(&s); + EXPECT_NULL(s.buffer); + EXPECT_EQ(s.len, 0); + EXPECT_EQ(s.alloc, 0); + EXPECT_STREQ(cstr, "12foo3123"); + free(cstr); } static void test_string_view(void) { const StringView sv1 = STRING_VIEW("testing"); - EXPECT_TRUE(string_view_equal_cstr(&sv1, "testing")); - EXPECT_FALSE(string_view_equal_cstr(&sv1, "testin")); - EXPECT_FALSE(string_view_equal_cstr(&sv1, "TESTING")); - EXPECT_TRUE(string_view_has_literal_prefix(&sv1, "test")); - EXPECT_TRUE(string_view_has_literal_prefix_icase(&sv1, "TEst")); - EXPECT_FALSE(string_view_has_literal_prefix(&sv1, "TEst")); - EXPECT_FALSE(string_view_has_literal_prefix_icase(&sv1, "TEst_")); + EXPECT_TRUE(strview_equal_cstring(&sv1, "testing")); + EXPECT_FALSE(strview_equal_cstring(&sv1, "testin")); + EXPECT_FALSE(strview_equal_cstring(&sv1, "TESTING")); + EXPECT_TRUE(strview_has_prefix(&sv1, "test")); + EXPECT_TRUE(strview_has_prefix_icase(&sv1, "TEst")); + EXPECT_FALSE(strview_has_prefix(&sv1, "TEst")); + EXPECT_FALSE(strview_has_prefix_icase(&sv1, "TEst_")); const StringView sv2 = string_view(sv1.data, sv1.length); - EXPECT_TRUE(string_view_equal(&sv1, &sv2)); + EXPECT_TRUE(strview_equal(&sv1, &sv2)); const StringView sv3 = STRING_VIEW("\0test\0 ..."); - EXPECT_TRUE(string_view_equal_strn(&sv3, "\0test\0 ...", 10)); - EXPECT_TRUE(string_view_equal_literal(&sv3, "\0test\0 ...")); - EXPECT_TRUE(string_view_has_prefix(&sv3, "\0test", 5)); - EXPECT_TRUE(string_view_has_literal_prefix(&sv3, "\0test\0")); - EXPECT_FALSE(string_view_equal_cstr(&sv3, "\0test\0 ...")); + EXPECT_TRUE(strview_equal_strn(&sv3, "\0test\0 ...", 10)); const StringView sv4 = sv3; - EXPECT_TRUE(string_view_equal(&sv4, &sv3)); + EXPECT_TRUE(strview_equal(&sv4, &sv3)); + + const StringView sv5 = strview_from_cstring("foobar"); + EXPECT_TRUE(strview_equal_cstring(&sv5, "foobar")); + EXPECT_TRUE(strview_has_prefix(&sv5, "foo")); + EXPECT_FALSE(strview_equal_cstring(&sv5, "foo")); + + StringView sv6 = strview_from_cstring("\t \t\t "); + EXPECT_TRUE(strview_isblank(&sv6)); + sv6.length = 0; + EXPECT_TRUE(strview_isblank(&sv6)); + sv6 = strview_from_cstring(" \t . "); + EXPECT_FALSE(strview_isblank(&sv6)); + sv6 = strview_from_cstring("\n"); + EXPECT_FALSE(strview_isblank(&sv6)); + sv6 = strview_from_cstring(" \r "); + EXPECT_FALSE(strview_isblank(&sv6)); +} + +static void test_get_delim(void) +{ + static const char input[] = "-x-y-foo--bar--"; + static const char parts[][4] = {"", "x", "y", "foo", "", "bar", "", ""}; + const size_t nparts = ARRAY_COUNT(parts); + const size_t part_size = ARRAY_COUNT(parts[0]); + + size_t idx = 0; + for (size_t pos = 0, len = sizeof(input) - 1; pos < len; idx++) { + const StringView sv = get_delim(input, &pos, len, '-'); + ASSERT_TRUE(idx < nparts); + ASSERT_TRUE(parts[idx][part_size - 1] == '\0'); + EXPECT_TRUE(strview_equal_cstring(&sv, parts[idx])); + } - const StringView sv5 = string_view_from_cstring("foobar"); - EXPECT_TRUE(string_view_equal_literal(&sv5, "foobar")); - EXPECT_TRUE(string_view_has_literal_prefix(&sv5, "foo")); - EXPECT_FALSE(string_view_equal_cstr(&sv5, "foo")); + EXPECT_EQ(idx, nparts - 1); } -static void test_number_width(void) +static void test_size_str_width(void) { - EXPECT_EQ(number_width(0), 1); - EXPECT_EQ(number_width(-1), 2); - EXPECT_EQ(number_width(420), 3); - EXPECT_EQ(number_width(2147483647), 10); - EXPECT_EQ(number_width(-2147483647), 11); + EXPECT_EQ(size_str_width(0), 1); + EXPECT_EQ(size_str_width(1), 1); + EXPECT_EQ(size_str_width(9), 1); + EXPECT_EQ(size_str_width(19), 2); + EXPECT_EQ(size_str_width(425), 3); + EXPECT_EQ(size_str_width(12345), 5); + EXPECT_EQ(size_str_width(2147483647), 10); } static void test_buf_parse_ulong(void) @@ -414,6 +576,63 @@ EXPECT_FALSE(str_to_size("99999999999999999999999999999999", &val)); } +static void test_umax_to_str(void) +{ + EXPECT_STREQ(umax_to_str(0), "0"); + EXPECT_STREQ(umax_to_str(1), "1"); + EXPECT_STREQ(umax_to_str(7), "7"); + EXPECT_STREQ(umax_to_str(99), "99"); + EXPECT_STREQ(umax_to_str(111), "111"); + EXPECT_STREQ(umax_to_str(1000), "1000"); + EXPECT_STREQ(umax_to_str(20998), "20998"); + + uintmax_t x = UINTMAX_MAX; + char ref[DECIMAL_STR_MAX(x)]; + xsnprintf(ref, sizeof ref, "%ju", x); + EXPECT_STREQ(umax_to_str(x), ref); + x--; + xsnprintf(ref, sizeof ref, "%ju", x); + EXPECT_STREQ(umax_to_str(x), ref); +} + +static void test_uint_to_str(void) +{ + EXPECT_STREQ(uint_to_str(0), "0"); + EXPECT_STREQ(uint_to_str(1), "1"); + EXPECT_STREQ(uint_to_str(9), "9"); + EXPECT_STREQ(uint_to_str(10), "10"); + EXPECT_STREQ(uint_to_str(11), "11"); + EXPECT_STREQ(uint_to_str(99), "99"); + EXPECT_STREQ(uint_to_str(100), "100"); + EXPECT_STREQ(uint_to_str(101), "101"); + EXPECT_STREQ(uint_to_str(21904), "21904"); + + // See test_posix_sanity() + static_assert(sizeof(unsigned int) >= 4); + static_assert(CHAR_BIT == 8); + static_assert(UINT_MAX >= 4294967295u); + static_assert(4294967295u == 0xFFFFFFFF); + EXPECT_STREQ(uint_to_str(4294967295u), "4294967295"); +} + +static void test_buf_uint_to_str(void) +{ + char buf[DECIMAL_STR_MAX(unsigned int)]; + EXPECT_EQ(buf_uint_to_str(0, buf), 1); + EXPECT_STREQ(buf, "0"); + EXPECT_EQ(buf_uint_to_str(1, buf), 1); + EXPECT_STREQ(buf, "1"); + EXPECT_EQ(buf_uint_to_str(9, buf), 1); + EXPECT_STREQ(buf, "9"); + EXPECT_EQ(buf_uint_to_str(129, buf), 3); + EXPECT_STREQ(buf, "129"); + EXPECT_EQ(buf_uint_to_str(21904, buf), 5); + EXPECT_STREQ(buf, "21904"); + static_assert(sizeof(buf) > 10); + EXPECT_EQ(buf_uint_to_str(4294967295u, buf), 10); + EXPECT_STREQ(buf, "4294967295"); +} + static void test_u_char_width(void) { // ASCII (1 column) @@ -444,9 +663,9 @@ // Double width (2 columns) EXPECT_EQ(u_char_width(0x2757), 2); EXPECT_EQ(u_char_width(0x312F), 2); + EXPECT_EQ(u_char_width(0x30000), 2); // Double width but unassigned (rendered as -- 4 columns) - EXPECT_EQ(u_char_width(0x30000), 4); EXPECT_EQ(u_char_width(0x3A009), 4); EXPECT_EQ(u_char_width(0x3FFFD), 4); @@ -465,6 +684,45 @@ EXPECT_EQ(u_to_lower('\0'), '\0'); } +static void test_u_to_upper(void) +{ + EXPECT_EQ(u_to_upper('a'), 'A'); + EXPECT_EQ(u_to_upper('z'), 'Z'); + EXPECT_EQ(u_to_upper('A'), 'A'); + EXPECT_EQ(u_to_upper('0'), '0'); + EXPECT_EQ(u_to_upper('~'), '~'); + EXPECT_EQ(u_to_upper('@'), '@'); + EXPECT_EQ(u_to_upper('\0'), '\0'); +} + +static void test_u_is_lower(void) +{ + EXPECT_TRUE(u_is_lower('a')); + EXPECT_TRUE(u_is_lower('z')); + EXPECT_FALSE(u_is_lower('A')); + EXPECT_FALSE(u_is_lower('Z')); + EXPECT_FALSE(u_is_lower('0')); + EXPECT_FALSE(u_is_lower('9')); + EXPECT_FALSE(u_is_lower('@')); + EXPECT_FALSE(u_is_lower('[')); + EXPECT_FALSE(u_is_lower('{')); + EXPECT_FALSE(u_is_lower('\0')); + EXPECT_FALSE(u_is_lower('\t')); + EXPECT_FALSE(u_is_lower(' ')); + EXPECT_FALSE(u_is_lower(0x1F315)); + EXPECT_FALSE(u_is_lower(0x10ffff)); + + /* + Even if SANE_WCTYPE is defined, we still can't make many assumptions + about the iswlower(3) implementation, since it depends on all kinds + of factors out of our control. Otherwise it'd be reasonable to test + something like: + + EXPECT_TRUE(u_is_lower(0x00E0)); + EXPECT_TRUE(u_is_lower(0x00E7)); + */ +} + static void test_u_is_upper(void) { EXPECT_TRUE(u_is_upper('A')); @@ -479,11 +737,10 @@ EXPECT_FALSE(u_is_upper('\0')); EXPECT_FALSE(u_is_upper('\t')); EXPECT_FALSE(u_is_upper(' ')); - EXPECT_FALSE(u_is_upper(0x00E0)); - EXPECT_FALSE(u_is_upper(0x00E7)); - EXPECT_FALSE(u_is_upper(0x1D499)); EXPECT_FALSE(u_is_upper(0x1F315)); EXPECT_FALSE(u_is_upper(0x10ffff)); + EXPECT_FALSE(u_is_upper(0x00E0)); + EXPECT_FALSE(u_is_upper(0x00E7)); } static void test_u_is_cntrl(void) @@ -566,7 +823,7 @@ EXPECT_TRUE(u_is_unprintable(0xF0000)); EXPECT_TRUE(u_is_unprintable(0xFFFFD)); EXPECT_TRUE(u_is_unprintable(0x100000)); - EXPECT_TRUE(u_is_unprintable(0x10FFFd)); + EXPECT_TRUE(u_is_unprintable(0x10FFFD)); // Non-characters -------------------------------------------------- // (https://www.unicode.org/faq/private_use.html#noncharacters) @@ -608,9 +865,70 @@ ); } +static void test_u_set_char_raw(void) +{ + unsigned char buf[16]; + size_t i; + MEMZERO(&buf); + + i = 0; + u_set_char_raw(buf, &i, 'a'); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], 'a'); + + i = 0; + u_set_char_raw(buf, &i, '\0'); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], '\0'); + + i = 0; + u_set_char_raw(buf, &i, 0x1F); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], 0x1F); + + i = 0; + u_set_char_raw(buf, &i, 0x7F); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], 0x7F); + + i = 0; + u_set_char_raw(buf, &i, 0x7FF); + EXPECT_EQ(i, 2); + EXPECT_EQ(buf[0], 0xDF); + EXPECT_EQ(buf[1], 0xBF); + + i = 0; + u_set_char_raw(buf, &i, 0xFF45); + EXPECT_EQ(i, 3); + EXPECT_EQ(buf[0], 0xEF); + EXPECT_EQ(buf[1], 0xBD); + EXPECT_EQ(buf[2], 0x85); + + i = 0; + u_set_char_raw(buf, &i, 0x1F311); + EXPECT_EQ(i, 4); + EXPECT_EQ(buf[0], 0xF0); + EXPECT_EQ(buf[1], 0x9F); + EXPECT_EQ(buf[2], 0x8C); + EXPECT_EQ(buf[3], 0x91); + + i = 0; + buf[1] = 0x88; + u_set_char_raw(buf, &i, 0x110000); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], 0); + EXPECT_EQ(buf[1], 0x88); + + i = 0; + u_set_char_raw(buf, &i, 0x110042); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], 0x42); + EXPECT_EQ(buf[1], 0x88); +} + static void test_u_set_char(void) { - char buf[16]; + unsigned char buf[16]; size_t i; MEMZERO(&buf); @@ -622,55 +940,78 @@ i = 0; u_set_char(buf, &i, 0x00DF); EXPECT_EQ(i, 2); - EXPECT_EQ(memcmp(buf, "\xC3\x9F", 2), 0); + EXPECT_EQ(buf[0], 0xC3); + EXPECT_EQ(buf[1], 0x9F); i = 0; u_set_char(buf, &i, 0x0E01); EXPECT_EQ(i, 3); - EXPECT_EQ(memcmp(buf, "\xE0\xB8\x81", 3), 0); + EXPECT_EQ(buf[0], 0xE0); + EXPECT_EQ(buf[1], 0xB8); + EXPECT_EQ(buf[2], 0x81); i = 0; u_set_char(buf, &i, 0x1F914); EXPECT_EQ(i, 4); - EXPECT_EQ(memcmp(buf, "\xF0\x9F\xA4\x94", 4), 0); + EXPECT_EQ(buf[0], 0xF0); + EXPECT_EQ(buf[1], 0x9F); + EXPECT_EQ(buf[2], 0xA4); + EXPECT_EQ(buf[3], 0x94); i = 0; u_set_char(buf, &i, 0x10FFFF); EXPECT_EQ(i, 4); - // Note: string separated to prevent "-Wtrigraphs" warning - EXPECT_EQ(memcmp(buf, "<" "?" "?" ">", 4), 0); -} - -static void test_u_set_ctrl(void) -{ - char buf[16]; - size_t i; - MEMZERO(&buf); + EXPECT_EQ(buf[0], '<'); + EXPECT_EQ(buf[1], '?'); + EXPECT_EQ(buf[2], '?'); + EXPECT_EQ(buf[3], '>'); i = 0; - u_set_ctrl(buf, &i, '\0'); + u_set_char(buf, &i, '\0'); EXPECT_EQ(i, 2); - EXPECT_EQ(memcmp(buf, "^@", 3), 0); + EXPECT_EQ(buf[0], '^'); + EXPECT_EQ(buf[1], '@'); i = 0; - u_set_ctrl(buf, &i, '\t'); + u_set_char(buf, &i, '\t'); EXPECT_EQ(i, 2); - EXPECT_EQ(memcmp(buf, "^I", 3), 0); + EXPECT_EQ(buf[0], '^'); + EXPECT_EQ(buf[1], 'I'); i = 0; - u_set_ctrl(buf, &i, 0x1F); + u_set_char(buf, &i, 0x1F); EXPECT_EQ(i, 2); - EXPECT_EQ(memcmp(buf, "^_", 3), 0); + EXPECT_EQ(buf[0], '^'); + EXPECT_EQ(buf[1], '_'); i = 0; - u_set_ctrl(buf, &i, 0x7F); + u_set_char(buf, &i, 0x7F); EXPECT_EQ(i, 2); - EXPECT_EQ(memcmp(buf, "^?", 3), 0); + EXPECT_EQ(buf[0], '^'); + EXPECT_EQ(buf[1], '?'); + + i = 0; + u_set_char(buf, &i, 0x80); + EXPECT_EQ(i, 4); + EXPECT_EQ(buf[0], '<'); + EXPECT_EQ(buf[1], '?'); + EXPECT_EQ(buf[2], '?'); + EXPECT_EQ(buf[3], '>'); + + i = 0; + u_set_char(buf, &i, 0x7E); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], '~'); + + i = 0; + u_set_char(buf, &i, 0x20); + EXPECT_EQ(i, 1); + EXPECT_EQ(buf[0], ' '); } static void test_u_prev_char(void) { - const unsigned char *buf = "深圳市"; + const unsigned char *buf = "\xE6\xB7\xB1\xE5\x9C\xB3\xE5\xB8\x82"; // 深圳市 size_t idx = 9; CodePoint c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x5E02); @@ -696,7 +1037,7 @@ EXPECT_EQ(c, 'e'); EXPECT_EQ(idx, 6); - buf = "🥣🥤"; + buf = "\xF0\x9F\xA5\xA3\xF0\x9F\xA5\xA4"; // 🥣🥤 idx = 8; c = u_prev_char(buf, &idx); EXPECT_EQ(c, 0x1F964); @@ -727,13 +1068,13 @@ static void test_ptr_array(void) { PointerArray a = PTR_ARRAY_INIT; - ptr_array_add(&a, NULL); - ptr_array_add(&a, NULL); - ptr_array_add(&a, xstrdup("foo")); - ptr_array_add(&a, NULL); - ptr_array_add(&a, xstrdup("bar")); - ptr_array_add(&a, NULL); - ptr_array_add(&a, NULL); + ptr_array_append(&a, NULL); + ptr_array_append(&a, NULL); + ptr_array_append(&a, xstrdup("foo")); + ptr_array_append(&a, NULL); + ptr_array_append(&a, xstrdup("bar")); + ptr_array_append(&a, NULL); + ptr_array_append(&a, NULL); EXPECT_EQ(a.count, 7); ptr_array_trim_nulls(&a); @@ -751,6 +1092,215 @@ EXPECT_EQ(a.count, 0); } +static void test_ptr_array_move(void) +{ + PointerArray a = PTR_ARRAY_INIT; + ptr_array_append(&a, xstrdup("A")); + ptr_array_append(&a, xstrdup("B")); + ptr_array_append(&a, xstrdup("C")); + ptr_array_append(&a, xstrdup("D")); + ptr_array_append(&a, xstrdup("E")); + ptr_array_append(&a, xstrdup("F")); + EXPECT_EQ(a.count, 6); + + ptr_array_move(&a, 0, 1); + EXPECT_STREQ(a.ptrs[0], "B"); + EXPECT_STREQ(a.ptrs[1], "A"); + + ptr_array_move(&a, 1, 0); + EXPECT_STREQ(a.ptrs[0], "A"); + EXPECT_STREQ(a.ptrs[1], "B"); + + ptr_array_move(&a, 1, 5); + EXPECT_STREQ(a.ptrs[0], "A"); + EXPECT_STREQ(a.ptrs[1], "C"); + EXPECT_STREQ(a.ptrs[2], "D"); + EXPECT_STREQ(a.ptrs[3], "E"); + EXPECT_STREQ(a.ptrs[4], "F"); + EXPECT_STREQ(a.ptrs[5], "B"); + + ptr_array_move(&a, 5, 1); + EXPECT_STREQ(a.ptrs[0], "A"); + EXPECT_STREQ(a.ptrs[1], "B"); + EXPECT_STREQ(a.ptrs[2], "C"); + EXPECT_STREQ(a.ptrs[3], "D"); + EXPECT_STREQ(a.ptrs[4], "E"); + EXPECT_STREQ(a.ptrs[5], "F"); + + ptr_array_move(&a, 0, 5); + EXPECT_STREQ(a.ptrs[0], "B"); + EXPECT_STREQ(a.ptrs[1], "C"); + EXPECT_STREQ(a.ptrs[2], "D"); + EXPECT_STREQ(a.ptrs[3], "E"); + EXPECT_STREQ(a.ptrs[4], "F"); + EXPECT_STREQ(a.ptrs[5], "A"); + + ptr_array_move(&a, 5, 0); + EXPECT_STREQ(a.ptrs[0], "A"); + EXPECT_STREQ(a.ptrs[1], "B"); + EXPECT_STREQ(a.ptrs[2], "C"); + EXPECT_STREQ(a.ptrs[3], "D"); + EXPECT_STREQ(a.ptrs[4], "E"); + EXPECT_STREQ(a.ptrs[5], "F"); + + ptr_array_move(&a, 5, 0); + EXPECT_STREQ(a.ptrs[0], "F"); + EXPECT_STREQ(a.ptrs[1], "A"); + EXPECT_STREQ(a.ptrs[2], "B"); + EXPECT_STREQ(a.ptrs[3], "C"); + EXPECT_STREQ(a.ptrs[4], "D"); + EXPECT_STREQ(a.ptrs[5], "E"); + + ptr_array_move(&a, 4, 5); + EXPECT_STREQ(a.ptrs[4], "E"); + EXPECT_STREQ(a.ptrs[5], "D"); + + ptr_array_move(&a, 1, 3); + EXPECT_STREQ(a.ptrs[1], "B"); + EXPECT_STREQ(a.ptrs[2], "C"); + EXPECT_STREQ(a.ptrs[3], "A"); + + ptr_array_free(&a); +} + +static void test_hashmap(void) +{ + static const char strings[][8] = { + "foo", "bar", "quux", "etc", "", + "A", "B", "C", "D", "E", "F", "G", + "a", "b", "c", "d", "e", "f", "g", + "test", "1234567", "..", "...", + "\x01\x02\x03 \t\xfe\xff", + }; + + HashMap map; + hashmap_init(&map, ARRAY_COUNT(strings)); + ASSERT_NONNULL(map.entries); + EXPECT_EQ(map.mask, 31); + EXPECT_EQ(map.count, 0); + EXPECT_NULL(hashmap_find(&map, "foo")); + + static const char value[] = "VALUE"; + FOR_EACH_I(i, strings) { + const char *key = strings[i]; + ASSERT_EQ(key[sizeof(strings[0]) - 1], '\0'); + hashmap_insert(&map, xstrdup(key), (void*)value); + HashMapEntry *e = hashmap_find(&map, key); + ASSERT_NONNULL(e); + EXPECT_STREQ(e->key, key); + EXPECT_PTREQ(e->value, value); + } + + EXPECT_EQ(map.count, 24); + EXPECT_EQ(map.mask, 31); + + HashMapIter it = hashmap_iter(&map); + EXPECT_PTREQ(it.map, &map); + EXPECT_NULL(it.entry); + EXPECT_EQ(it.idx, 0); + + while (hashmap_next(&it)) { + ASSERT_NONNULL(it.entry); + ASSERT_NONNULL(it.entry->key); + EXPECT_PTREQ(it.entry->value, value); + } + + FOR_EACH_I(i, strings) { + const char *key = strings[i]; + HashMapEntry *e = hashmap_find(&map, key); + ASSERT_NONNULL(e); + EXPECT_STREQ(e->key, key); + EXPECT_PTREQ(e->value, value); + + EXPECT_PTREQ(hashmap_remove(&map, key), value); + EXPECT_STREQ(e->key, "TOMBSTONE"); + EXPECT_NULL(hashmap_find(&map, key)); + } + + EXPECT_EQ(map.count, 0); + it = hashmap_iter(&map); + EXPECT_FALSE(hashmap_next(&it)); + + hashmap_insert(&map, xstrdup("new"), (void*)value); + ASSERT_NONNULL(hashmap_find(&map, "new")); + EXPECT_STREQ(hashmap_find(&map, "new")->key, "new"); + EXPECT_EQ(map.count, 1); + + FOR_EACH_I(i, strings) { + const char *key = strings[i]; + hashmap_insert(&map, xstrdup(key), (void*)value); + HashMapEntry *e = hashmap_find(&map, key); + ASSERT_NONNULL(e); + EXPECT_STREQ(e->key, key); + EXPECT_PTREQ(e->value, value); + } + + EXPECT_EQ(map.count, 25); + + FOR_EACH_I(i, strings) { + const char *key = strings[i]; + HashMapEntry *e = hashmap_find(&map, key); + ASSERT_NONNULL(e); + EXPECT_STREQ(e->key, key); + EXPECT_PTREQ(hashmap_remove(&map, key), value); + EXPECT_STREQ(e->key, "TOMBSTONE"); + EXPECT_NULL(hashmap_find(&map, key)); + } + + EXPECT_EQ(map.count, 1); + EXPECT_NULL(hashmap_remove(&map, "non-existent-key")); + EXPECT_EQ(map.count, 1); + EXPECT_PTREQ(hashmap_remove(&map, "new"), value); + EXPECT_EQ(map.count, 0); + + it = hashmap_iter(&map); + EXPECT_FALSE(hashmap_next(&it)); + + hashmap_free(&map, NULL); + EXPECT_NULL(map.entries); + EXPECT_EQ(map.count, 0); + EXPECT_EQ(map.mask, 0); + + hashmap_init(&map, 0); + ASSERT_NONNULL(map.entries); + EXPECT_EQ(map.mask, 7); + EXPECT_EQ(map.count, 0); + hashmap_free(&map, NULL); + EXPECT_NULL(map.entries); + + hashmap_init(&map, 13); + ASSERT_NONNULL(map.entries); + EXPECT_EQ(map.mask, 31); + EXPECT_EQ(map.count, 0); + + for (size_t i = 1; i <= 380; i++) { + char key[4]; + EXPECT_EQ(xsnprintf(key, sizeof key, "%zu", i), size_str_width(i)); + hashmap_insert(&map, xstrdup(key), (void*)value); + HashMapEntry *e = hashmap_find(&map, key); + ASSERT_NONNULL(e); + EXPECT_STREQ(e->key, key); + EXPECT_PTREQ(e->value, value); + } + + EXPECT_EQ(map.count, 380); + EXPECT_EQ(map.mask, 511); + hashmap_free(&map, NULL); + + const char *val = "VAL"; + char *key = xstrdup("KEY"); + EXPECT_NULL(hashmap_insert_or_replace(&map, key, (char*)val)); + EXPECT_EQ(map.count, 1); + EXPECT_STREQ(hashmap_get(&map, "KEY"), val); + + const char *new_val = "NEW"; + char *duplicate_key = xstrdup(key); + EXPECT_PTREQ(val, hashmap_insert_or_replace(&map, duplicate_key, (char*)new_val)); + EXPECT_EQ(map.count, 1); + EXPECT_STREQ(hashmap_get(&map, "KEY"), new_val); + hashmap_free(&map, NULL); +} + static void test_hashset(void) { static const char *const strings[] = { @@ -765,9 +1315,13 @@ HashSet set; hashset_init(&set, ARRAY_COUNT(strings), false); + EXPECT_EQ(set.nr_entries, 0); EXPECT_NULL(hashset_get(&set, "foo", 3)); + FOR_EACH_I(i, strings) { + hashset_add(&set, strings[i], strlen(strings[i])); + } - hashset_add_many(&set, (char**)strings, ARRAY_COUNT(strings)); + EXPECT_EQ(set.nr_entries, ARRAY_COUNT(strings)); EXPECT_NONNULL(hashset_get(&set, "\t\xff\x80\b", 4)); EXPECT_NONNULL(hashset_get(&set, "foo", 3)); EXPECT_NONNULL(hashset_get(&set, "Foo", 3)); @@ -791,11 +1345,14 @@ hashset_free(&set); MEMZERO(&set); - hashset_init(&set, ARRAY_COUNT(strings), true); - hashset_add_many(&set, (char**)strings, ARRAY_COUNT(strings)); - EXPECT_NONNULL(hashset_get(&set, "foo", 3)); - EXPECT_NONNULL(hashset_get(&set, "FOO", 3)); - EXPECT_NONNULL(hashset_get(&set, "fOO", 3)); + hashset_init(&set, 0, true); + EXPECT_EQ(set.nr_entries, 0); + hashset_add(&set, STRN("foo")); + hashset_add(&set, STRN("Foo")); + EXPECT_EQ(set.nr_entries, 1); + EXPECT_NONNULL(hashset_get(&set, STRN("foo"))); + EXPECT_NONNULL(hashset_get(&set, STRN("FOO"))); + EXPECT_NONNULL(hashset_get(&set, STRN("fOO"))); hashset_free(&set); MEMZERO(&set); @@ -803,35 +1360,53 @@ // inserting duplicates hashset_init(&set, 0, false); EXPECT_EQ(set.nr_entries, 0); - HashSetEntry *e1 = hashset_add(&set, "foo", 3); + HashSetEntry *e1 = hashset_add(&set, STRN("foo")); EXPECT_EQ(e1->str_len, 3); EXPECT_STREQ(e1->str, "foo"); EXPECT_EQ(set.nr_entries, 1); - HashSetEntry *e2 = hashset_add(&set, "foo", 3); + HashSetEntry *e2 = hashset_add(&set, STRN("foo")); EXPECT_PTREQ(e1, e2); EXPECT_EQ(set.nr_entries, 1); hashset_free(&set); + + hashset_init(&set, 0, false); + // Initial table size should be 16 (minimum + load factor + rounding) + EXPECT_EQ(set.table_size, 16); + for (size_t i = 1; i <= 80; i++) { + char buf[4]; + size_t n = xsnprintf(buf, sizeof buf, "%zu", i); + hashset_add(&set, buf, n); + } + EXPECT_EQ(set.nr_entries, 80); + EXPECT_NONNULL(hashset_get(&set, STRN("1"))); + EXPECT_NONNULL(hashset_get(&set, STRN("80"))); + EXPECT_NULL(hashset_get(&set, STRN("0"))); + EXPECT_NULL(hashset_get(&set, STRN("81"))); + // Table size should be the first power of 2 larger than the number + // of entries (including load factor adjustment) + EXPECT_EQ(set.table_size, 128); + hashset_free(&set); } -static void test_round_up(void) +static void test_round_size_to_next_multiple(void) { - EXPECT_EQ(ROUND_UP(3, 8), 8); - EXPECT_EQ(ROUND_UP(8, 8), 8); - EXPECT_EQ(ROUND_UP(9, 8), 16); - EXPECT_EQ(ROUND_UP(0, 8), 0); - EXPECT_EQ(ROUND_UP(0, 16), 0); - EXPECT_EQ(ROUND_UP(1, 16), 16); - EXPECT_EQ(ROUND_UP(123, 16), 128); - EXPECT_EQ(ROUND_UP(4, 64), 64); - EXPECT_EQ(ROUND_UP(80, 64), 128); - EXPECT_EQ(ROUND_UP(256, 256), 256); - EXPECT_EQ(ROUND_UP(257, 256), 512); - EXPECT_EQ(ROUND_UP(8000, 256), 8192); + EXPECT_EQ(round_size_to_next_multiple(3, 8), 8); + EXPECT_EQ(round_size_to_next_multiple(8, 8), 8); + EXPECT_EQ(round_size_to_next_multiple(9, 8), 16); + EXPECT_EQ(round_size_to_next_multiple(0, 8), 0); + EXPECT_EQ(round_size_to_next_multiple(0, 16), 0); + EXPECT_EQ(round_size_to_next_multiple(1, 16), 16); + EXPECT_EQ(round_size_to_next_multiple(123, 16), 128); + EXPECT_EQ(round_size_to_next_multiple(4, 64), 64); + EXPECT_EQ(round_size_to_next_multiple(80, 64), 128); + EXPECT_EQ(round_size_to_next_multiple(256, 256), 256); + EXPECT_EQ(round_size_to_next_multiple(257, 256), 512); + EXPECT_EQ(round_size_to_next_multiple(8000, 256), 8192); } static void test_round_size_to_next_power_of_2(void) { - EXPECT_UINT_EQ(round_size_to_next_power_of_2(0), 0); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(0), 1); EXPECT_UINT_EQ(round_size_to_next_power_of_2(1), 1); EXPECT_UINT_EQ(round_size_to_next_power_of_2(2), 2); EXPECT_UINT_EQ(round_size_to_next_power_of_2(3), 4); @@ -842,77 +1417,19 @@ EXPECT_UINT_EQ(round_size_to_next_power_of_2(17), 32); EXPECT_UINT_EQ(round_size_to_next_power_of_2(61), 64); EXPECT_UINT_EQ(round_size_to_next_power_of_2(64), 64); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(65), 128); EXPECT_UINT_EQ(round_size_to_next_power_of_2(200), 256); EXPECT_UINT_EQ(round_size_to_next_power_of_2(1000), 1024); EXPECT_UINT_EQ(round_size_to_next_power_of_2(5500), 8192); -} - -static void test_bitop(void) -{ - EXPECT_EQ(bit_popcount_u32(0), 0); - EXPECT_EQ(bit_popcount_u32(1), 1); - EXPECT_EQ(bit_popcount_u32(11), 3); - EXPECT_EQ(bit_popcount_u32(128), 1); - EXPECT_EQ(bit_popcount_u32(255), 8); - EXPECT_EQ(bit_popcount_u32(UINT32_MAX), 32); - EXPECT_EQ(bit_popcount_u32(UINT32_MAX - 1), 31); - - EXPECT_EQ(bit_popcount_u64(0), 0); - EXPECT_EQ(bit_popcount_u64(1), 1); - EXPECT_EQ(bit_popcount_u64(255), 8); - EXPECT_EQ(bit_popcount_u64(UINT64_MAX), 64); - EXPECT_EQ(bit_popcount_u64(UINT64_MAX - 1), 63); - EXPECT_EQ(bit_popcount_u64(U64(0xFFFFFFFFFF)), 40); - EXPECT_EQ(bit_popcount_u64(U64(0x10000000000)), 1); - - EXPECT_EQ(bit_count_leading_zeros_u64(1), 63); - EXPECT_EQ(bit_count_leading_zeros_u64(4), 61); - EXPECT_EQ(bit_count_leading_zeros_u64(127), 57); - EXPECT_EQ(bit_count_leading_zeros_u64(128), 56); - EXPECT_EQ(bit_count_leading_zeros_u64(UINT64_MAX), 0); - EXPECT_EQ(bit_count_leading_zeros_u64(UINT64_MAX - 1), 0); - - EXPECT_EQ(bit_count_leading_zeros_u32(1), 31); - EXPECT_EQ(bit_count_leading_zeros_u32(4), 29); - EXPECT_EQ(bit_count_leading_zeros_u32(127), 25); - EXPECT_EQ(bit_count_leading_zeros_u32(128), 24); - EXPECT_EQ(bit_count_leading_zeros_u32(UINT32_MAX), 0); - EXPECT_EQ(bit_count_leading_zeros_u32(UINT32_MAX - 1), 0); - - EXPECT_EQ(bit_count_trailing_zeros_u32(1), 0); - EXPECT_EQ(bit_count_trailing_zeros_u32(2), 1); - EXPECT_EQ(bit_count_trailing_zeros_u32(3), 0); - EXPECT_EQ(bit_count_trailing_zeros_u32(4), 2); - EXPECT_EQ(bit_count_trailing_zeros_u32(8), 3); - EXPECT_EQ(bit_count_trailing_zeros_u32(13), 0); - EXPECT_EQ(bit_count_trailing_zeros_u32(16), 4); - EXPECT_EQ(bit_count_trailing_zeros_u32(U32(0xFFFFFFFE)), 1); - EXPECT_EQ(bit_count_trailing_zeros_u32(U32(0x10000000)), 28); - EXPECT_EQ(bit_count_trailing_zeros_u32(UINT32_MAX), 0); - EXPECT_EQ(bit_count_trailing_zeros_u32(UINT32_MAX - 0xFF), 8); - - EXPECT_EQ(bit_count_trailing_zeros_u64(1), 0); - EXPECT_EQ(bit_count_trailing_zeros_u64(2), 1); - EXPECT_EQ(bit_count_trailing_zeros_u64(3), 0); - EXPECT_EQ(bit_count_trailing_zeros_u64(16), 4); - EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0xFFFFFFFE)), 1); - EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0x10000000)), 28); - EXPECT_EQ(bit_count_trailing_zeros_u64(U64(0x100000000000)), 44); - EXPECT_EQ(bit_count_trailing_zeros_u64(UINT64_MAX), 0); - - EXPECT_EQ(bit_find_first_set_u32(0), 0); - EXPECT_EQ(bit_find_first_set_u32(1), 1); - EXPECT_EQ(bit_find_first_set_u32(2), 2); - EXPECT_EQ(bit_find_first_set_u32(3), 1); - EXPECT_EQ(bit_find_first_set_u32(64), 7); - EXPECT_EQ(bit_find_first_set_u32(U32(1) << 31), 32); - - EXPECT_EQ(bit_find_first_set_u64(0), 0); - EXPECT_EQ(bit_find_first_set_u64(1), 1); - EXPECT_EQ(bit_find_first_set_u64(2), 2); - EXPECT_EQ(bit_find_first_set_u64(3), 1); - EXPECT_EQ(bit_find_first_set_u64(64), 7); - EXPECT_EQ(bit_find_first_set_u64(U64(1) << 63), 64); + const size_t size_max = (size_t)-1; + const size_t pow2_max = size_max & ~(size_max >> 1); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(size_max >> 1), pow2_max); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(pow2_max), pow2_max); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(pow2_max - 1), pow2_max); + // Note: returns 0 on overflow + EXPECT_UINT_EQ(round_size_to_next_power_of_2(pow2_max + 1), 0); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(size_max), 0); + EXPECT_UINT_EQ(round_size_to_next_power_of_2(size_max - 1), 0); } static void test_path_dirname_and_path_basename(void) @@ -942,11 +1459,50 @@ } } +static void test_relative_filename(void) +{ + static const struct rel_test { + const char *cwd; + const char *path; + const char *result; + } tests[] = { // NOTE: at most 2 ".." components allowed in relative name + { "/", "/", "/" }, + { "/", "/file", "file" }, + { "/a/b/c/d", "/a/b/file", "../../file" }, + { "/a/b/c/d/e", "/a/b/file", "/a/b/file" }, + { "/a/foobar", "/a/foo/file", "../foo/file" }, + }; + FOR_EACH_I(i, tests) { + char *result = relative_filename(tests[i].path, tests[i].cwd); + IEXPECT_STREQ(tests[i].result, result); + free(result); + } +} + static void test_path_absolute(void) { char *path = path_absolute("///dev///"); - ASSERT_NONNULL(path); - EXPECT_STREQ(path, "/dev/"); + EXPECT_STREQ(path, "/dev"); + free(path); + + path = path_absolute("///dev///..///dev//null"); + EXPECT_STREQ(path, "/dev/null"); + free(path); + + path = path_absolute("///dev//n0nexist3nt-file"); + EXPECT_STREQ(path, "/dev/n0nexist3nt-file"); + free(path); + + path = path_absolute("///../..//./"); + EXPECT_STREQ(path, "/"); + free(path); + + path = path_absolute("/"); + EXPECT_STREQ(path, "/"); + free(path); + + path = path_absolute(""); + EXPECT_STREQ(path, NULL); free(path); const char *linkpath = "./build/../build/test/test-symlink"; @@ -954,20 +1510,47 @@ TEST_FAIL("symlink() failed: %s", strerror(errno)); return; } + passed++; path = path_absolute(linkpath); EXPECT_EQ(unlink(linkpath), 0); ASSERT_NONNULL(path); EXPECT_STREQ(path_basename(path), "README.md"); free(path); +} - char buf[8192 + 1]; - memset(buf, 'a', sizeof(buf)); - buf[0] = '/'; - buf[8192] = '\0'; - errno = 0; - EXPECT_NULL(path_absolute(buf)); - EXPECT_EQ(errno, ENAMETOOLONG); +static void test_path_join(void) +{ + char *p = path_join("/", "file"); + EXPECT_STREQ(p, "/file"); + free(p); + p = path_join("foo", "bar"); + EXPECT_STREQ(p, "foo/bar"); + free(p); + p = path_join("foo/", "bar"); + EXPECT_STREQ(p, "foo/bar"); + free(p); + p = path_join("", "bar"); + EXPECT_STREQ(p, "bar"); + free(p); + p = path_join("foo", ""); + EXPECT_STREQ(p, "foo"); + free(p); + p = path_join("", ""); + EXPECT_STREQ(p, ""); + free(p); + p = path_join("/", ""); + EXPECT_STREQ(p, "/"); + free(p); + p = path_join("/home/user", ".dte"); + EXPECT_STREQ(p, "/home/user/.dte"); + free(p); + p = path_join("/home/user/", ".dte"); + EXPECT_STREQ(p, "/home/user/.dte"); + free(p); + p = path_join("/home/user//", ".dte"); + EXPECT_STREQ(p, "/home/user//.dte"); + free(p); } static void test_path_parent(void) @@ -1065,7 +1648,7 @@ static void test_read_file(void) { char *buf = NULL; - ssize_t size = read_file("/dev", &buf); + ssize_t size = read_file("test", &buf); EXPECT_EQ(size, -1); EXPECT_EQ(errno, EISDIR); EXPECT_NULL(buf); @@ -1088,39 +1671,64 @@ free(buf); } -DISABLE_WARNING("-Wmissing-prototypes") - -void test_util(void) +static void test_xfopen(void) { - test_macros(); - test_ascii(); - test_string(); - test_string_view(); - test_number_width(); - test_buf_parse_ulong(); - test_str_to_int(); - test_str_to_size(); - test_u_char_width(); - test_u_to_lower(); - test_u_is_upper(); - test_u_is_cntrl(); - test_u_is_zero_width(); - test_u_is_special_whitespace(); - test_u_is_unprintable(); - test_u_str_width(); - test_u_set_char(); - test_u_set_ctrl(); - test_u_prev_char(); - test_ptr_array(); - test_hashset(); - test_round_up(); - test_round_size_to_next_power_of_2(); - test_bitop(); - test_path_dirname_and_path_basename(); - test_path_absolute(); - test_path_parent(); - test_size_multiply_overflows(); - test_size_add_overflows(); - test_mem_intern(); - test_read_file(); + static const char modes[][4] = {"a", "a+", "r", "r+", "w", "w+"}; + FOR_EACH_I(i, modes) { + FILE *f = xfopen("/dev/null", modes[i], O_CLOEXEC, 0666); + EXPECT_NONNULL(f); + if (likely(f)) { + EXPECT_EQ(fclose(f), 0); + } + } } + +static const TestEntry tests[] = { + TEST(test_util_macros), + TEST(test_IS_POWER_OF_2), + TEST(test_xstreq), + TEST(test_hex_decode), + TEST(test_ascii), + TEST(test_base64), + TEST(test_string), + TEST(test_string_view), + TEST(test_get_delim), + TEST(test_size_str_width), + TEST(test_buf_parse_ulong), + TEST(test_str_to_int), + TEST(test_str_to_size), + TEST(test_umax_to_str), + TEST(test_uint_to_str), + TEST(test_buf_uint_to_str), + TEST(test_u_char_width), + TEST(test_u_to_lower), + TEST(test_u_to_upper), + TEST(test_u_is_lower), + TEST(test_u_is_upper), + TEST(test_u_is_cntrl), + TEST(test_u_is_zero_width), + TEST(test_u_is_special_whitespace), + TEST(test_u_is_unprintable), + TEST(test_u_str_width), + TEST(test_u_set_char_raw), + TEST(test_u_set_char), + TEST(test_u_prev_char), + TEST(test_ptr_array), + TEST(test_ptr_array_move), + TEST(test_hashmap), + TEST(test_hashset), + TEST(test_round_size_to_next_multiple), + TEST(test_round_size_to_next_power_of_2), + TEST(test_path_dirname_and_path_basename), + TEST(test_relative_filename), + TEST(test_path_absolute), + TEST(test_path_join), + TEST(test_path_parent), + TEST(test_size_multiply_overflows), + TEST(test_size_add_overflows), + TEST(test_mem_intern), + TEST(test_read_file), + TEST(test_xfopen), +}; + +const TestGroup util_tests = TEST_GROUP(tests);