summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile10
-rw-r--r--doc/pages/changelog.asciidoc2
-rw-r--r--doc/pages/commands.asciidoc4
-rw-r--r--doc/pages/options.asciidoc6
-rw-r--r--rc/filetype/julia.kak29
-rw-r--r--rc/tools/make.kak10
-rw-r--r--rc/windowing/detection.kak2
-rw-r--r--rc/windowing/hyprland.kak45
-rw-r--r--src/buffer.hh4
-rw-r--r--src/buffer_utils.cc3
-rw-r--r--src/client.cc3
-rw-r--r--src/client_manager.cc10
-rw-r--r--src/hash_map.hh7
-rw-r--r--src/highlighter.hh12
-rw-r--r--src/highlighters.cc235
-rw-r--r--src/main.cc121
-rw-r--r--src/normal.cc96
-rw-r--r--src/ranges.hh62
-rw-r--r--src/regex_impl.cc45
-rw-r--r--src/regex_impl.hh107
-rw-r--r--src/string.cc15
-rw-r--r--src/string.hh31
-rw-r--r--src/terminal_ui.cc7
-rw-r--r--src/terminal_ui.hh1
-rw-r--r--src/utils.hh3
-rw-r--r--src/window.cc60
-rw-r--r--test/highlight/replace-multiline-range-pulls-new-lines/rc1
-rw-r--r--test/highlight/replace-multiline-range-pulls-new-lines/script8
-rw-r--r--test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd1
-rw-r--r--test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in1
-rw-r--r--test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script2
-rw-r--r--test/highlight/wrap/basic/cmd1
-rw-r--r--test/highlight/wrap/basic/in2
-rw-r--r--test/highlight/wrap/basic/rc1
-rw-r--r--test/highlight/wrap/basic/script4
-rw-r--r--test/highlight/wrap/interact-with-number-lines/cmd1
-rw-r--r--test/highlight/wrap/interact-with-number-lines/in2
-rw-r--r--test/highlight/wrap/interact-with-number-lines/rc2
-rw-r--r--test/highlight/wrap/interact-with-number-lines/script3
-rw-r--r--test/highlight/wrap/interact-with-replace-ranges/cmd1
-rw-r--r--test/highlight/wrap/interact-with-replace-ranges/in3
-rw-r--r--test/highlight/wrap/interact-with-replace-ranges/rc4
-rw-r--r--test/highlight/wrap/interact-with-replace-ranges/script3
-rw-r--r--test/highlight/wrap/interact-with-show-whitespaces/cmd1
-rw-r--r--test/highlight/wrap/interact-with-show-whitespaces/in1
-rw-r--r--test/highlight/wrap/interact-with-show-whitespaces/rc2
-rw-r--r--test/highlight/wrap/interact-with-show-whitespaces/script4
-rw-r--r--test/highlight/wrap/interact-with-tabulation/cmd1
-rw-r--r--test/highlight/wrap/interact-with-tabulation/in1
-rw-r--r--test/highlight/wrap/interact-with-tabulation/rc2
-rw-r--r--test/highlight/wrap/interact-with-tabulation/script4
-rw-r--r--test/highlight/wrap/marker-and-indent/cmd1
-rw-r--r--test/highlight/wrap/marker-and-indent/in4
-rw-r--r--test/highlight/wrap/marker-and-indent/rc1
-rw-r--r--test/highlight/wrap/marker-and-indent/script4
-rw-r--r--test/highlight/wrap/word/cmd1
-rw-r--r--test/highlight/wrap/word/in3
-rw-r--r--test/highlight/wrap/word/rc1
-rw-r--r--test/highlight/wrap/word/script4
-rw-r--r--test/normal/pipe-replaces-all-lines/cmd1
-rw-r--r--test/normal/pipe-replaces-all-lines/in2
-rw-r--r--test/normal/pipe-replaces-all-lines/out2
-rw-r--r--test/regression/0-slow-BufCloseFifo/cmd1
-rw-r--r--test/regression/0-slow-BufCloseFifo/in1
-rw-r--r--test/regression/0-slow-BufCloseFifo/rc16
-rw-r--r--test/regression/0-slow-BufCloseFifo/script2
-rw-r--r--test/regression/2999-buggy-wrapping/cmd2
-rw-r--r--test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd1
-rw-r--r--test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in1
-rw-r--r--test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc1
-rwxr-xr-xtest/run34
-rw-r--r--test/tools/git/blame-in-diff/rc15
-rw-r--r--test/tools/git/blame-in-diff/script11
74 files changed, 604 insertions, 495 deletions
diff --git a/.gitignore b/.gitignore
index 26769f06..69712a85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,9 +6,9 @@
*.tar*
src/kak
src/kak.debug
-src/kak.debug.san_*
+src/kak.debug.*
src/kak.opt
-src/kak.opt.san_*
+src/kak.opt.*
src/.version*
src/.*.json
doc/kak.1.gz
diff --git a/Makefile b/Makefile
index b1f70826..8858b01c 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,9 @@ gzip_man = yes
# to get format compatible with GitHub archive use "gzip -S .gz" here
compress_bin = bzip2
+tag-static-no=
+tag-static-yes=.static
+
compress-suffix-bzip2 = bz2
compress-suffix-zstd = zst
@@ -96,6 +99,7 @@ KAK_LIBS = \
$(LIBS)
tag = $(tag-debug-$(debug))$(tag-sanitize-$(sanitize))
+tagbin = $(tag)$(tag-static-$(static))
.SUFFIXES: $(tag).o .cc
.PHONY: src/kak
@@ -106,10 +110,10 @@ objects = $(sources:.cc=$(tag).o)
all: src/kak
-src/kak: src/kak$(tag)
- ln -sf kak$(tag) $@
+src/kak: src/kak$(tagbin)
+ ln -sf kak$(tagbin) $@
-src/kak$(tag): src/.version.o $(objects)
+src/kak$(tagbin): src/.version.o $(objects)
$(CXX) $(KAK_LDFLAGS) $(KAK_CXXFLAGS) $(objects) src/.version.o $(KAK_LIBS) -o $@
deps = $(shell touch src/.version$(tag).d && find src -type f -name '.*$(tag).d') # Ensure we find one deps for FreeBSD make
diff --git a/doc/pages/changelog.asciidoc b/doc/pages/changelog.asciidoc
index 66733443..af3c711a 100644
--- a/doc/pages/changelog.asciidoc
+++ b/doc/pages/changelog.asciidoc
@@ -1,5 +1,7 @@
= Changelog
+To control the startup info message, see <<options#startup-info,`:doc options startup-info`>>
+
This changelog contains major and/or breaking changes to Kakoune between
released versions.
diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc
index e386ed61..3a6bef65 100644
--- a/doc/pages/commands.asciidoc
+++ b/doc/pages/commands.asciidoc
@@ -293,7 +293,9 @@ of the file onto the filesystem
(See <<faces#,`:doc faces`>> and <<scopes#,`:doc scopes`>>)
*colorscheme* <name>::
- load named colorscheme
+ load named colorscheme +
+ Colorschemes are just `.kak` scripts that set faces. Colorschemes are searched for in `%val{config}/colors` and `%val{runtime}/colors`. +
+ (See <<faces#, `:doc faces`>> and <<expansions#, `:doc expansions`>>)
*add-highlighter* [<switches>] <highlighter_path> <highlighter_parameters> ...::
*alias* addhl +
diff --git a/doc/pages/options.asciidoc b/doc/pages/options.asciidoc
index e5cb4c38..4a2120ea 100644
--- a/doc/pages/options.asciidoc
+++ b/doc/pages/options.asciidoc
@@ -342,7 +342,7 @@ are exclusively available to built-in options.
*ui_options* `str-to-str-map`::
a list of `key=value` pairs that are forwarded to the user
- interface implementation. The NCurses UI supports the following options:
+ interface implementation. The Terminal UI supports the following options:
*terminal_set_title*:::
if *yes* or *true*, the terminal emulator title will
@@ -385,6 +385,10 @@ are exclusively available to built-in options.
set the maximum allowable width of an info box. set to zero for
no limit.
+ *terminal_cursor_native*:::
+ if *yes* or *true*, use native terminal cursor visibility control
+ instead of Kakoune's cursor management (defaults to *false*)
+
[[startup-info]]
*startup_info_version* `int`::
_default_ 0 +
diff --git a/rc/filetype/julia.kak b/rc/filetype/julia.kak
index 5f6bbaa6..6dc2af13 100644
--- a/rc/filetype/julia.kak
+++ b/rc/filetype/julia.kak
@@ -13,6 +13,9 @@ hook global BufCreate .*\.(jl) %{
hook global WinSetOption filetype=julia %{
require-module julia
+ hook window InsertChar \n -group julia-indent julia-indent-on-new-line
+ hook window InsertChar d -group julia-insert julia-insert-on-new-line
+ hook -once -always window WinSetOption filetype=.* %{ remove-hooks window julia-.+ }
}
hook -group julia-highlight global WinSetOption filetype=julia %{
@@ -21,7 +24,7 @@ hook -group julia-highlight global WinSetOption filetype=julia %{
}
-provide-module julia %{
+provide-module julia %§
# Highlighters
# ‾‾‾‾‾‾‾‾‾‾‾‾
@@ -39,4 +42,26 @@ add-highlighter shared/julia/code/ regex \b(Number|Real|BigInt|Integer|UInt|UInt
add-highlighter shared/julia/code/ regex \w+!*(?=\() 0:function
add-highlighter shared/julia/code/ regex @\w+!*\b 0:meta
add-highlighter shared/julia/code/ regex '(?:\?|=|:=|\+=|-=|\*=|/=|//=|\.//=|\.\*=|\./=|\\=|\.\\=|\^=|\.\^=|÷=|\.÷=|%=|\.%=|\|=|&=|\$=|=>|<<=|>>=|>>>=|~|\.\+=|\.-=|--|-->|←|→|↔|↚|↛|↠|↣|↦|↮|⇎|⇏|⇒|⇔|⇴|⇶|⇷|⇸|⇹|⇺|⇻|⇼|⇽|⇾|⇿|⟵|⟶|⟷|⟷|⟹|⟺|⟻|⟼|⟽|⟾|⟿|⤀|⤁|⤂|⤃|⤄|⤅|⤆|⤇|⤌|⤍|⤎|⤏|⤐|⤑|⤔|⤕|⤖|⤗|⤘|⤝|⤞|⤟|⤠|⥄|⥅|⥆|⥇|⥈|⥊|⥋|⥎|⥐|⥒|⥓|⥖|⥗|⥚|⥛|⥞|⥟|⥢|⥤|⥦|⥧|⥨|⥩|⥪|⥫|⥬|⥭|⥰|⧴|⬱|⬰|⬲|⬳|⬴|⬵|⬶|⬷|⬸|⬹|⬺|⬻|⬼|⬽|⬾|⬿|⭀|⭁|⭂|⭃|⭄|⭇|⭈|⭉|⭊|⭋|⭌|←|→|&&|\|\||>|<|>=|≥|<=|≤|==|===|≡|!=|≠|!==|≢|\.>|\.<|\.>=|\.≥|\.<=|\.≤|\.==|\.!=|\.≠|\.=|\.!|<:|>:|∈|∉|∋|∌|⊆|⊈|⊂|⊄|⊊|∝|∊|∍|∥|∦|∷|∺|∻|∽|∾|≁|≃|≄|≅|≆|≇|≈|≉|≊|≋|≌|≍|≎|≐|≑|≒|≓|≔|≕|≖|≗|≘|≙|≚|≛|≜|≝|≞|≟|≣|≦|≧|≨|≩|≪|≫|≬|≭|≮|≯|≰|≱|≲|≳|≴|≵|≶|≷|≸|≹|≺|≻|≼|≽|≾|≿|⊀|⊁|⊃|⊅|⊇|⊉|⊋|⊏|⊐|⊑|⊒|⊜|⊩|⊬|⊮|⊰|⊱|⊲|⊳|⊴|⊵|⊶|⊷|⋍|⋐|⋑|⋕|⋖|⋗|⋘|⋙|⋚|⋛|⋜|⋝|⋞|⋟|⋠|⋡|⋢|⋣|⋤|⋥|⋦|⋧|⋨|⋩|⋪|⋫|⋬|⋭|⋲|⋳|⋴|⋵|⋶|⋷|⋸|⋹|⋺|⋻|⋼|⋽|⋾|⋿|⟈|⟉|⟒|⦷|⧀|⧁|⧡|⧣|⧤|⧥|⩦|⩧|⩪|⩫|⩬|⩭|⩮|⩯|⩰|⩱|⩲|⩳|⩴|⩵|⩶|⩷|⩸|⩹|⩺|⩻|⩼|⩽|⩾|⩿|⪀|⪁|⪂|⪃|⪄|⪅|⪆|⪇|⪈|⪉|⪊|⪋|⪌|⪍|⪎|⪏|⪐|⪑|⪒|⪓|⪔|⪕|⪖|⪗|⪘|⪙|⪚|⪛|⪜|⪝|⪞|⪟|⪠|⪡|⪢|⪣|⪤|⪥|⪦|⪧|⪨|⪩|⪪|⪫|⪬|⪭|⪮|⪯|⪰|⪱|⪲|⪳|⪴|⪵|⪶|⪷|⪸|⪹|⪺|⪻|⪼|⪽|⪾|⪿|⫀|⫁|⫂|⫃|⫄|⫅|⫆|⫇|⫈|⫉|⫊|⫋|⫌|⫍|⫎|⫏|⫐|⫑|⫒|⫓|⫔|⫕|⫖|⫗|⫘|⫙|⫷|⫸|⫹|⫺|⊢|⊣|\|>|<\||:|\.\.|\+|-|⊕|⊖|⊞|⊟|\.\+|\.-|\+\+|\||∪|∨|\$|⊔|±|∓|∔|∸|≂|≏|⊎|⊻|⊽|⋎|⋓|⧺|⧻|⨈|⨢|⨣|⨤|⨥|⨦|⨧|⨨|⨩|⨪|⨫|⨬|⨭|⨮|⨹|⨺|⩁|⩂|⩅|⩊|⩌|⩏|⩐|⩒|⩔|⩖|⩗|⩛|⩝|⩡|⩢|⩣|<<|>>|>>>|\.<<|\.>>|\.>>>|\*|/|\./|÷|\.÷|%|⋅|∘|×|\.%|\.\*|\\|\.\\|&|∩|∧|⊗|⊘|⊙|⊚|⊛|⊠|⊡|⊓|∗|∙|∤|⅋|≀|⊼|⋄|⋆|⋇|⋉|⋊|⋋|⋌|⋏|⋒|⟑|⦸|⦼|⦾|⦿|⧶|⧷|⨇|⨰|⨱|⨲|⨳|⨴|⨵|⨶|⨷|⨸|⨻|⨼|⨽|⩀|⩃|⩄|⩋|⩍|⩎|⩑|⩓|⩕|⩘|⩚|⩜|⩞|⩟|⩠|⫛|⊍|▷|⨝|⟕|⟖|⟗|//|\.//|\^|\.\^|↑|↓|⇵|⟰|⟱|⤈|⤉|⤊|⤋|⤒|⤓|⥉|⥌|⥍|⥏|⥑|⥔|⥕|⥘|⥙|⥜|⥝|⥠|⥡|⥣|⥥|⥮|⥯|↑|↓|::|\.)' 0:operator
-}
+
+define-command -hidden julia-indent-on-new-line %<
+ evaluate-commands -draft -itersel %<
+ # preserve previous line indent
+ try %{ execute-keys -draft <semicolon> K <a-&> }
+ # preserve previous line comment
+ try %{ execute-keys -draft k x <a-k> ^\h*# <ret> j i <#> <space> }
+ # cleanup trailing whitespaces from previous line
+ try %{ execute-keys -draft k x s \h+$ <ret> d }
+ # indent after block start
+ try %{ execute-keys -draft , k x <a-k> ^\h*(struct|macro|function|begin|if|try|for|while|let|quote|do) <ret> <a-K> end$ <ret> j <a-gt> }
+ # deindent closing brace/bracket when after cursor (for arrays and dictionaries)
+ try %< execute-keys -draft x <a-k> ^\h*[}\]] <ret> gh / [}\]] <ret> m <a-S> 1<a-&> >
+ >
+>
+
+define-command -hidden julia-insert-on-new-line %<
+ evaluate-commands -draft -itersel %<
+ # deindent on end
+ try %{ execute-keys -draft x <a-k> ^\h*end <ret> <lt> }
+ >
+>
diff --git a/rc/tools/make.kak b/rc/tools/make.kak
index 7de2051d..9bc71c2e 100644
--- a/rc/tools/make.kak
+++ b/rc/tools/make.kak
@@ -62,8 +62,9 @@ define-command -hidden make-jump %{
set-option buffer jump_current_line %val{cursor_line}
set-register a "%reg{1}/%reg{2}" "%reg{3}" "%reg{4}" "%reg{5}"
} catch %{
- set-register / %opt{make_error_pattern}
- execute-keys <a-h><a-l> s<ret>l
+ # check if error pattern matches exactly at the start of the current line, possibly spanning more lines
+ set-register / "\A%opt{make_error_pattern}"
+ execute-keys ghGe s<ret>l
set-option buffer jump_current_line %val{cursor_line}
set-register a "%reg{1}" "%reg{2}" "%reg{3}" "%reg{4}"
}
@@ -73,11 +74,12 @@ define-command -hidden make-jump %{
}
define-command -hidden make-select-next %{
set-register / %opt{make_error_pattern}
- execute-keys "%opt{jump_current_line}ggl" "/<ret>"
+ # go to the current line end, search and go to the start of selection
+ execute-keys "%opt{jump_current_line}ggl" "/<ret><a-;>;"
}
define-command -hidden make-select-previous %{
set-register / %opt{make_error_pattern}
- execute-keys "%opt{jump_current_line}g" "<a-/><ret>"
+ execute-keys "%opt{jump_current_line}g" "<a-/><ret><a-;>;"
}
define-command make-next-error -docstring %{alias for "jump-next *make*"} %{
diff --git a/rc/windowing/detection.kak b/rc/windowing/detection.kak
index 2fde81cd..36b0398f 100644
--- a/rc/windowing/detection.kak
+++ b/rc/windowing/detection.kak
@@ -23,7 +23,7 @@ declare-option -docstring \
"Ordered list of windowing modules to try and load. An empty list disables
both automatic module loading and environment detection, enabling complete
manual control of the module loading." \
-str-list windowing_modules 'tmux' 'screen' 'zellij' 'kitty' 'iterm' 'appleterminal' 'sway' 'wayland' 'x11' 'wezterm'
+str-list windowing_modules 'tmux' 'screen' 'zellij' 'kitty' 'iterm' 'appleterminal' 'sway' 'hyprland' 'wayland' 'x11' 'wezterm'
declare-option -docstring %{
windowing module to use in the 'terminal' command
diff --git a/rc/windowing/hyprland.kak b/rc/windowing/hyprland.kak
new file mode 100644
index 00000000..27ad16c2
--- /dev/null
+++ b/rc/windowing/hyprland.kak
@@ -0,0 +1,45 @@
+# https://hypr.land
+provide-module hyprland %{
+
+# Ensure we're actually in Hyprland
+evaluate-commands %sh{
+ [ -z "${kak_opt_windowing_modules}" ] ||
+ [ "$XDG_CURRENT_DESKTOP" == "Hyprland" ] &&
+ [ -n "$HYPRLAND_INSTANCE_SIGNATURE" ] ||
+ echo 'fail hyprland not detected'
+}
+
+require-module 'wayland'
+
+alias global hyprland-terminal-window wayland-terminal-window
+
+define-command hyprland-focus-pid -hidden %{
+ evaluate-commands %sh{
+ pid=$kak_client_pid
+ while [ "$(hyprctl dispatch focuswindow pid:$pid)" != "ok" ]; do
+ pid=$(ps -p $pid -o ppid= | tr -d " ")
+ if [ -z $pid ] || [ $pid -le 1 ]; then
+ echo "fail Can't find PID for Sway window to focus"
+ break
+ fi
+ done
+ }
+}
+
+define-command hyprland-focus -params ..1 -docstring '
+hyprland-focus [<kakoune_client>]: focus a given client''s window.
+If no client is passed, then the current client is used' \
+%{
+ evaluate-commands %sh{
+ if [ $# -eq 1 ]; then
+ printf "evaluate-commands -client '%s' hyprland-focus-pid" "$1"
+ else
+ echo hyprland-focus-pid
+ fi
+ }
+}
+complete-command -menu hyprland-focus client
+
+alias global focus hyprland-focus
+
+}
diff --git a/src/buffer.hh b/src/buffer.hh
index 2bcd8cc3..b256b8ef 100644
--- a/src/buffer.hh
+++ b/src/buffer.hh
@@ -224,6 +224,10 @@ public:
Type type;
BufferCoord begin;
BufferCoord end;
+
+ #ifdef KAK_DEBUG
+ bool operator==(const Change&) const = default;
+ #endif
};
ConstArrayView<Change> changes_since(size_t timestamp) const;
diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc
index 994dbd23..dca1a372 100644
--- a/src/buffer_utils.cc
+++ b/src/buffer_utils.cc
@@ -290,7 +290,8 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, AutoScroll
kak_assert(m_buffer.flags() & Buffer::Flags::Fifo);
close_fd();
m_buffer.run_hook_in_own_context(Hook::BufCloseFifo, "");
- m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo);
+ if (not m_buffer.values().contains(fifo_watcher_id))
+ m_buffer.flags() &= ~(Buffer::Flags::Fifo | Buffer::Flags::NoUndo);
}
void read_fifo()
diff --git a/src/client.cc b/src/client.cc
index 2cb1c973..14b7b7bc 100644
--- a/src/client.cc
+++ b/src/client.cc
@@ -194,6 +194,9 @@ void Client::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_sel
if (m_buffer_reload_dialog_opened)
close_buffer_reload_dialog();
+ if (context().buffer().flags() & Buffer::Flags::Locked)
+ throw runtime_error("Changing buffer is not allowed while current buffer is locked");
+
buffer.flags() |= Buffer::Flags::Locked;
OnScopeEnd unlock{[&] { buffer.flags() &= ~Buffer::Flags::Locked; }};
diff --git a/src/client_manager.cc b/src/client_manager.cc
index 2e7d76e7..8d4f2de0 100644
--- a/src/client_manager.cc
+++ b/src/client_manager.cc
@@ -67,17 +67,18 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui, int pi
m_clients.emplace_back(client);
auto& context = client->context();
- if (buffer->name() == "*scratch*")
+ if (context.buffer().name() == "*scratch*")
context.print_status({"This *scratch* buffer won't be automatically saved",
context.faces()["Information"]});
if (init_coord)
{
- auto& selections = context.selections_write_only();
- selections = SelectionList(*buffer, buffer->clamp(*init_coord));
+ context.selections_write_only() = SelectionList(*buffer, context.buffer().clamp(*init_coord));
context.window().center_line(init_coord->line);
}
+ unlock.trigger();
+
try
{
context.hooks().run_hook(Hook::ClientCreate, context.name(), context);
@@ -86,8 +87,7 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui, int pi
catch (Kakoune::runtime_error& error)
{
context.print_status({error.what().str(), context.faces()["Error"]});
- context.hooks().run_hook(Hook::RuntimeError, error.what(),
- context);
+ context.hooks().run_hook(Hook::RuntimeError, error.what(), context);
}
// Do not return the client if it already got moved to the trash
diff --git a/src/hash_map.hh b/src/hash_map.hh
index eef082dd..60ebc6ab 100644
--- a/src/hash_map.hh
+++ b/src/hash_map.hh
@@ -274,6 +274,7 @@ struct HashMap
int index = find_index(key, hash);
if (index >= 0)
{
+ [[maybe_unused]] Item keepalive = std::move(m_items[index]);
m_items.erase(m_items.begin() + index);
m_index.remove(hash, index);
m_index.ordered_fix_entries(index);
@@ -287,7 +288,8 @@ struct HashMap
int index = find_index(key, hash);
if (index >= 0)
{
- constexpr_swap(m_items[index], m_items.back());
+ [[maybe_unused]] Item keepalive = std::move(m_items[index]);
+ m_items[index] = std::move(m_items.back());
m_items.pop_back();
m_index.remove(hash, index);
if (index != m_items.size())
@@ -296,7 +298,7 @@ struct HashMap
}
template<typename KeyType> requires IsHashCompatible<Key, KeyType>
- constexpr void erase(const KeyType& key) { unordered_remove(key); }
+ constexpr void erase(const KeyType& key) { return unordered_remove(key); }
template<typename KeyType> requires IsHashCompatible<Key, KeyType>
constexpr void remove_all(const KeyType& key)
@@ -305,6 +307,7 @@ struct HashMap
for (int index = find_index(key, hash); index >= 0;
index = find_index(key, hash))
{
+ [[maybe_unused]] Item keepalive = std::move(m_items[index]);
m_items.erase(m_items.begin() + index);
m_index.remove(hash, index);
m_index.ordered_fix_entries(index);
diff --git a/src/highlighter.hh b/src/highlighter.hh
index 73520c53..fbeef9e4 100644
--- a/src/highlighter.hh
+++ b/src/highlighter.hh
@@ -22,11 +22,12 @@ using BufferRange = Range<BufferCoord>;
enum class HighlightPass
{
- Wrap = 1 << 0,
- Move = 1 << 1,
- Colorize = 1 << 2,
+ Replace = 1 << 0,
+ Wrap = 1 << 1,
+ Move = 1 << 2,
+ Colorize = 1 << 3,
- All = Wrap | Move | Colorize,
+ All = Replace | Wrap | Move | Colorize,
};
constexpr bool with_bit_ops(Meta::Type<HighlightPass>) { return true; }
@@ -43,11 +44,8 @@ struct DisplaySetup
LineCount line_count;
ColumnCount first_column;
ColumnCount widget_columns;
- // Position of the cursor in the window
- DisplayCoord cursor_pos;
// Offset of line and columns that must remain visible around cursor
DisplayCoord scroll_offset;
- bool ensure_cursor_visible;
};
using HighlighterIdList = ConstArrayView<StringView>;
diff --git a/src/highlighters.cc b/src/highlighters.cc
index 25f93245..ff46081a 100644
--- a/src/highlighters.cc
+++ b/src/highlighters.cc
@@ -601,7 +601,7 @@ struct WrapHighlighter : Highlighter
static constexpr StringView ms_id = "wrap";
- struct SplitPos{ ByteCount byte; ColumnCount column; };
+ struct SplitPos{ DisplayLine::iterator atom_it; ByteCount byte; ColumnCount column; };
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override
{
@@ -613,42 +613,27 @@ struct WrapHighlighter : Highlighter
return;
const Buffer& buffer = context.context.buffer();
- const auto& cursor = context.context.selections().main().cursor();
const int tabstop = context.context.options()["tabstop"].get<int>();
- const LineCount win_height = context.context.window().dimensions().line;
const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column);
const Face face_marker = context.context.faces()["WrapMarker"];
for (auto it = display_buffer.lines().begin();
it != display_buffer.lines().end(); ++it)
{
- const LineCount buf_line = it->range().begin.line;
- const ByteCount line_length = buffer[buf_line].length();
const ColumnCount indent = m_preserve_indent ?
- zero_if_greater(line_indent(buffer, tabstop, buf_line), wrap_column) : 0_col;
+ zero_if_greater(line_indent(buffer, tabstop, it->range().begin.line), wrap_column) : 0_col;
const ColumnCount prefix_len = std::max(marker_len, indent);
- auto pos = next_split_pos(buffer, wrap_column, prefix_len, tabstop, buf_line, {0, 0});
- if (pos.byte == line_length)
- continue;
-
- for (auto atom_it = it->begin();
- pos.byte != line_length and atom_it != it->end(); )
+ SplitPos pos{it->begin(), 0, 0}; ;
+ while (next_split_pos(pos, it->end(), wrap_column, prefix_len))
{
- const BufferCoord coord{buf_line, pos.byte};
- if (!atom_it->has_buffer_range() or
- coord < atom_it->begin() or coord >= atom_it->end())
- {
- ++atom_it;
- continue;
- }
-
auto& line = *it;
- if (coord > atom_it->begin())
- atom_it = ++line.split(atom_it, coord);
+ if (pos.byte > 0 and pos.atom_it->type() == DisplayAtom::Range)
+ pos.atom_it = ++line.split(pos.atom_it, pos.atom_it->begin() + BufferCoord{0, pos.byte});
- DisplayLine new_line{ AtomList{ atom_it, line.end() } };
- line.erase(atom_it, line.end());
+ auto coord = pos.atom_it->begin();
+ DisplayLine new_line{ AtomList{ pos.atom_it, line.end() } };
+ line.erase(pos.atom_it, line.end());
if (marker_len != 0)
new_line.insert(new_line.begin(), {m_marker, face_marker});
@@ -658,23 +643,13 @@ struct WrapHighlighter : Highlighter
it->replace(String{' ', indent - marker_len});
}
- if (it+1 - display_buffer.lines().begin() == win_height)
+ it = display_buffer.lines().insert(it+1, new_line);
+ pos = SplitPos{it->begin(), 0, 0};
+ if (pos.atom_it->type() != DisplayAtom::Range) // avoid infinite loop trying to split too long non-buffer ranges
{
- if (cursor >= new_line.range().begin) // strip first lines if cursor is not visible
- {
- display_buffer.lines().erase(display_buffer.lines().begin(), display_buffer.lines().begin()+1);
- --it;
- }
- else
- {
- display_buffer.lines().erase(it+1, display_buffer.lines().end());
- return;
- }
+ pos.column += pos.atom_it->content().column_length();
+ ++pos.atom_it;
}
- it = display_buffer.lines().insert(it+1, new_line);
-
- pos = next_split_pos(buffer, wrap_column - prefix_len, prefix_len, tabstop, buf_line, pos);
- atom_it = it->begin();
}
}
}
@@ -688,80 +663,9 @@ struct WrapHighlighter : Highlighter
if (wrap_column <= 0)
return;
- const Buffer& buffer = context.context.buffer();
- const auto& cursor = context.context.selections().main().cursor();
- const int tabstop = context.context.options()["tabstop"].get<int>();
-
- auto line_wrap_count = [&](LineCount line, ColumnCount prefix_len) {
- LineCount count = 0;
- const ByteCount line_length = buffer[line].length();
- SplitPos pos{0, 0};
- while (true)
- {
- pos = next_split_pos(buffer, wrap_column - (pos.byte == 0 ? 0_col : prefix_len),
- prefix_len, tabstop, line, pos);
- if (pos.byte == line_length)
- break;
- ++count;
- }
- return count;
- };
-
- const auto win_height = context.context.window().dimensions().line;
-
// Disable horizontal scrolling when using a WrapHighlighter
setup.first_column = 0;
- setup.line_count = 0;
setup.scroll_offset.column = 0;
-
- const ColumnCount marker_len = zero_if_greater(m_marker.column_length(), wrap_column);
-
- for (auto buf_line = setup.first_line, win_line = 0_line;
- win_line < win_height or (setup.ensure_cursor_visible and buf_line <= cursor.line);
- ++buf_line, ++setup.line_count)
- {
- if (buf_line >= buffer.line_count())
- break;
-
- const ColumnCount indent = m_preserve_indent ?
- zero_if_greater(line_indent(buffer, tabstop, buf_line), wrap_column) : 0_col;
- const ColumnCount prefix_len = std::max(marker_len, indent);
-
- if (buf_line == cursor.line)
- {
- SplitPos pos{0, 0};
- for (LineCount count = 0; true; ++count)
- {
- auto next_pos = next_split_pos(buffer, wrap_column - (pos.byte != 0 ? prefix_len : 0_col),
- prefix_len, tabstop, buf_line, pos);
- if (next_pos.byte > cursor.column)
- {
- setup.cursor_pos = DisplayCoord{
- win_line + count,
- get_column(buffer, tabstop, cursor) -
- pos.column + (pos.byte != 0 ? indent : 0_col)
- };
- break;
- }
- pos = next_pos;
- }
- }
- const auto wrap_count = line_wrap_count(buf_line, prefix_len);
- win_line += wrap_count + 1;
-
- // scroll window to keep cursor visible, and update range as lines gets removed
- while (setup.ensure_cursor_visible and
- buf_line >= cursor.line and setup.first_line < cursor.line and
- setup.cursor_pos.line + setup.scroll_offset.line >= win_height)
- {
- auto remove_count = 1 + line_wrap_count(setup.first_line, indent);
- ++setup.first_line;
- --setup.line_count;
- setup.cursor_pos.line -= std::min(win_height, remove_count);
- win_line -= remove_count;
- kak_assert(setup.cursor_pos.line >= 0);
- }
- }
}
void fill_unique_ids(Vector<StringView>& unique_ids) const override
@@ -769,79 +673,78 @@ struct WrapHighlighter : Highlighter
unique_ids.push_back(ms_id);
}
- SplitPos next_split_pos(const Buffer& buffer, ColumnCount wrap_column, ColumnCount prefix_len,
- int tabstop, LineCount line, SplitPos current) const
+ bool next_split_pos(SplitPos& pos, DisplayLine::iterator line_end,
+ ColumnCount wrap_column, ColumnCount prefix_len) const
{
- const ColumnCount target_column = current.column + wrap_column;
- StringView content = buffer[line];
-
- SplitPos pos = current;
- SplitPos last_word_boundary = {0, 0};
- SplitPos last_WORD_boundary = {0, 0};
+ SplitPos last_word_boundary{};
+ SplitPos last_WORD_boundary{};
- auto update_boundaries = [&](Codepoint cp) {
- if (not m_word_wrap)
- return;
- if (!is_word<Word>(cp))
+ auto update_word_boundaries = [&](Codepoint cp) {
+ if (m_word_wrap and not is_word<Word>(cp))
last_word_boundary = pos;
- if (!is_word<WORD>(cp))
+ if (m_word_wrap and not is_word<WORD>(cp))
last_WORD_boundary = pos;
};
- while (pos.byte < content.length() and pos.column < target_column)
+ while (pos.atom_it != line_end and pos.column < wrap_column)
{
- if (content[pos.byte] == '\t')
+ auto content = pos.atom_it->content();
+ const char* it = &content[pos.byte];
+ const Codepoint cp = utf8::read_codepoint(it, content.end());
+ const ColumnCount width = codepoint_width(cp);
+ if (pos.column + width > wrap_column) // the target column was in the char
{
- const ColumnCount next_column = (pos.column / tabstop + 1) * tabstop;
- if (next_column > target_column and pos.byte != current.byte) // the target column was in the tab
- break;
- pos.column = next_column;
- ++pos.byte;
- last_word_boundary = last_WORD_boundary = pos;
+ update_word_boundaries(cp);
+ break;
}
- else
+ pos.column += width;
+ pos.byte = (int)(it - content.begin());
+ update_word_boundaries(cp);
+ if (it == content.end())
{
- const char* it = &content[pos.byte];
- const Codepoint cp = utf8::read_codepoint(it, content.end());
- const ColumnCount width = codepoint_width(cp);
- if (pos.column + width > target_column and pos.byte != current.byte) // the target column was in the char
- {
- update_boundaries(cp);
- break;
- }
- pos.column += width;
- pos.byte = (int)(it - content.begin());
- update_boundaries(cp);
+ ++pos.atom_it;
+ pos.byte = 0;
+ // Thanks to the pass ordering, atom boundary should always be reasonable word split points
+ last_word_boundary = pos;
+ last_WORD_boundary = pos;
}
}
+ if (pos.atom_it == line_end)
+ return false;
+ auto content = pos.atom_it->content();
if (m_word_wrap and pos.byte < content.length())
{
- auto find_split_pos = [&](SplitPos start_pos, auto is_word) -> Optional<SplitPos> {
- if (start_pos.byte == 0)
- return {};
+ auto find_split_pos = [&](SplitPos start_pos, auto is_word) {
+ if (start_pos.column == 0)
+ return false;
const char* it = &content[pos.byte];
// split at current position if is a word boundary
if (not is_word(utf8::codepoint(it, content.end()), {'_'}))
- return pos;
+ return true;
// split at last word boundary if the word is shorter than our wrapping width
ColumnCount word_length = pos.column - start_pos.column;
- while (it != content.end() and word_length <= (wrap_column - prefix_len))
+ ColumnCount max_word_length = wrap_column - prefix_len;
+ while (it != content.end() and word_length <= max_word_length)
{
const Codepoint cp = utf8::read_codepoint(it, content.end());
if (not is_word(cp, {'_'}))
- return start_pos;
+ break;
word_length += codepoint_width(cp);
}
- return {};
+ if (word_length <= max_word_length)
+ {
+ pos = start_pos;
+ return true;
+ }
+ return false;
};
- if (auto split = find_split_pos(last_WORD_boundary, is_word<WORD>))
- return *split;
- if (auto split = find_split_pos(last_word_boundary, is_word<Word>))
- return *split;
+ if (find_split_pos(last_WORD_boundary, is_word<WORD>) or
+ find_split_pos(last_word_boundary, is_word<Word>))
+ return true;
}
- return pos;
+ return true;
}
static ColumnCount line_indent(const Buffer& buffer, int tabstop, LineCount line)
@@ -876,7 +779,7 @@ constexpr StringView WrapHighlighter::ms_id;
struct TabulationHighlighter : Highlighter
{
- TabulationHighlighter() : Highlighter{HighlightPass::Move} {}
+ TabulationHighlighter() : Highlighter{HighlightPass::Replace} {}
void do_highlight(HighlightContext context, DisplayBuffer& display_buffer, BufferRange) override
{
@@ -947,7 +850,6 @@ struct TabulationHighlighter : Highlighter
const ColumnCount offset = std::max(column + width - win_end, 0_col);
setup.first_column += offset;
- setup.cursor_pos.column -= offset;
}
};
@@ -968,7 +870,7 @@ const HighlighterDesc show_whitespace_desc = {
struct ShowWhitespacesHighlighter : Highlighter
{
ShowWhitespacesHighlighter(String tab, String tabpad, String spc, String lf, String nbsp, String indent, bool only_trailing)
- : Highlighter{HighlightPass::Move}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)},
+ : Highlighter{HighlightPass::Replace}, m_tab{std::move(tab)}, m_tabpad{std::move(tabpad)},
m_spc{std::move(spc)}, m_lf{std::move(lf)}, m_nbsp{std::move(nbsp)}, m_indent{std::move(indent)}, m_only_trailing{std::move(only_trailing)}
{}
@@ -1641,7 +1543,7 @@ const HighlighterDesc replace_ranges_desc = {
"each spec is interpreted as a display line to display in place of the range",
{}
};
-struct ReplaceRangesHighlighter : OptionBasedHighlighter<RangeAndStringList, ReplaceRangesHighlighter, HighlightPass::Move>
+struct ReplaceRangesHighlighter : OptionBasedHighlighter<RangeAndStringList, ReplaceRangesHighlighter, HighlightPass::Replace>
{
using ReplaceRangesHighlighter::OptionBasedHighlighter::OptionBasedHighlighter;
private:
@@ -1694,7 +1596,6 @@ private:
auto& buffer = context.context.buffer();
auto& sels = context.context.selections();
auto& range_and_faces = get_option(context);
- const int tabstop = context.context.options()["tabstop"].get<int>();
update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);
range_and_faces.prefix = buffer.timestamp();
@@ -1707,19 +1608,7 @@ private:
if (range.first.line < setup.first_line and last.line >= setup.first_line)
setup.first_line = range.first.line;
- const auto& cursor = context.context.selections().main().cursor();
- if (cursor.line == last.line and cursor.column >= last.column)
- {
- auto first_column = get_column(buffer, tabstop, range.first);
- auto last_column = get_column(buffer, tabstop, last);
- auto replacement = parse_display_line(spec, context.context.faces());
- auto cursor_move = replacement.length() - ((range.first.line == last.line) ? last_column - first_column : last_column);
- setup.cursor_pos.line -= last.line - range.first.line;
- setup.cursor_pos.column += cursor_move;
- }
-
- if (setup.ensure_cursor_visible and
- last.line >= setup.first_line and
+ if (last.line >= setup.first_line and
range.first.line <= setup.first_line + setup.line_count and
range.first.line != last.line)
{
diff --git a/src/main.cc b/src/main.cc
index fb78669d..60c0669e 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -66,110 +66,15 @@ struct {
"» custom completions are no longer sorted if the typed text is empty\n"
"» {+u}terminal{} now selects implementation based on windowing options\n"
"» {+u}local{} scopes\n"
- }, {
- 20230805,
- "» Fix FreeBSD/MacOS clang compilation\n"
- }, {
- 20230729,
- "» {+b}<a-u>{} and {+b}<a-U>{} now undo/redo selection changes; "
- "the previous meaning of moving in history tree has been moved to "
- "{+b}<c-j>{} and {+b}<c-k>{}\n"
- "» {+u}%exp\\{...}{} expansions provide flexible quoting for expanded "
- "strings (as double quoted strings)\n"
- "» {+u}show-matching -previous{} switch\n"
- "» {+b}<c-g>{} to cancel current operation\n"
- }, {
- 20221031,
- "» {+b}<esc>{} does not end macro recording anymore, use {+b}Q{}\n"
- "» pipe commands do not append final end-of-lines anymore\n"
- "» {+u}complete-command{} to configure command completion\n"
- "» {+b}p{}, {+b}P{}, {+b}!{} and {+b}<a-!>{} now select the inserted text\n"
- "» {+b}x{} now uses {+b}<a-x>{} behaviour\n"
- "» {+b}<space>{} and {+b},{} have been swapped\n"
- }, {
- 20211107,
- "» colored and curly underlines support (undocumented in 20210828)\n"
- }, {
- 20211028,
- "» {+b}g{} and {+b}v{} do not auto-convert to lowercase anymore\n"
- }, {
- 20210828,
- "» {+u}write <filename>{} will refuse to overwrite without {+u}-force{}\n"
- "» {+u}$kak_command_fifo{} support\n"
- "» {+u}set-option -remove{} support\n"
- "» prompts auto select {+i}menu{} completions on space\n"
- "» explicit completion support ({+b}<c-x>...{}) in prompts\n"
- "» {+u}write -atomic{} was replaced with {+u}write -method <method>{}\n"
- }, {
- 20200901,
- "» daemon mode does not fork anymore\n"
- }, {
- 20200804,
- "» {+u}User{} hook support\n"
- "» Removed {+i}bold{} and {+i}italic{} faces from colorschemes\n"
- "» Read from stdin support for clients\n"
- "» {+u}rgba:RRGGBBAA{} faces and alpha blending\n"
- }, {
- 20200116,
- "» {+u}InsertCompletionHide{} parameter is now the list of inserted ranges\n"
- }, {
- 20191210,
- "» {+u}ModeChange{} parameter has changed to contain push/pop\n"
- "» {+ui}Mode{+u}Begin{}/{+ui}Mode{+u}End{} hooks were removed\n"
- }, {
- 20190701,
- "» {+u}%file\\{<filename>}{} expansions to read files\n"
- "» {+u}echo -to-file <filename>{} to write to file\n"
- "» completions option have an on select command instead of "
- "a docstring\n"
- "» Function key syntax do not accept lower case f anymore\n"
- "» shell quoting of list options is now opt-in with "
- "{+u}$kak_quoted_...{}\n"
- }, {
- 20190120,
- "» named capture groups in regex\n"
- "» auto_complete option renamed to autocomplete\n"
- }, {
- 20181027,
- "» {+u}define-commands{} {+i}-shell-completion{} and {+i}-shell-candidates{} "
- "has been renamed\n"
- "» exclusive face attributes is replaced with final "
- "(fg/bg/attr)\n"
- "» {+b}<a-M>{} (merge consecutive) moved to {+b}<a-_>{} to make {+b}<a-M>{} "
- "backward {+b}<a-m>{}\n"
- "» {+u}remove-hooks{} now takes a regex parameter\n"
- }, {
- 20180904,
- "» Big breaking refactoring of various Kakoune features, "
- "configuration might need to be updated see `:doc changelog` "
- "for details\n"
- "» {+u}define-command{} {+i}-allow-override{} switch has been renamed "
- "{+i}-override{}\n"
- }, {
- 20180413,
- "» {+u}ModeChange{} hook has been introduced and is expected "
- "to replace the various {+ui}Mode{+u}Begin{}/{+ui}Mode{+u}End{} hooks, "
- "consider those deprecated.\n"
- "» {+b}*{} Does not strip whitespaces anymore, use built-in "
- "{+b}_{} to strip them\n"
- "» {+b}l{} on eol will go to next line, {+b}h{} on first char will "
- "go to previous\n"
- "» selections merging behaviour is now a bit more complex "
- "again\n"
- "» {+b}x{} will only jump to next line if full line is already "
- "selected\n"
- "» {+i}WORD{} text object moved to {+b}<a-w>{} instead of {+b}W{} for "
- "consistency\n"
- "» rotate main selection moved to {+b}){}, rotate content to {+b}<a-)>{}, "
- "{+b}({} for backward\n"
- "» faces are now scoped, {+u}set-face{} command takes an additional "
- "scope parameter\n"
- "» {+b}<backtab>{} key is gone, use {+b}<s-tab>{} instead\n"
-} };
+ }
+};
+
+static_assert(sizeof(version_notes) / sizeof(version_notes[0]) <= 4 - (version_notes[0].version != 0 ? 1 : 0),
+ "Maximum 3 versions should be displayed in version notes, not including the development version");
void show_startup_info(Client* local_client, int last_version)
{
- const Face version_face{Color::Default, Color::Default, Attribute::Bold};
+ const Face version_face{.attributes=Attribute::Bold};
DisplayLineList info;
for (auto [version, notes] : version_notes)
{
@@ -183,20 +88,17 @@ void show_startup_info(Client* local_client, int last_version)
const auto year = version / 10000;
const auto month = (version / 100) % 100;
const auto day = version % 100;
- info.push_back({format("• Kakoune v{}.{}{}.{}{}",
- year, month < 10 ? "0" : "", month, day < 10 ? "0" : "", day),
- version_face});
+ info.push_back({format("• Kakoune v{}.{:02}.{:02}", year, month, day), version_face});
}
for (auto&& line : notes | split<StringView>('\n'))
info.push_back(parse_display_line(line, GlobalScope::instance().faces()));
}
if (not info.empty())
- {
- info.push_back({"See the `:doc options startup-info` to control this message",
- Face{Color::Default, Color::Default, Attribute::Italic}});
- local_client->info_show({format("Kakoune {}", version), version_face}, info, {}, InfoStyle::Prompt);
- }
+ local_client->info_show({{{format("Kakoune {}", version), version_face},
+ {", more info at ", {}},
+ {":doc changelog", {.attributes=Attribute::Underline}}}},
+ std::move(info), {}, InfoStyle::Prompt);
}
inline void write_stdout(StringView str) { try { write(STDOUT_FILENO, str); } catch (runtime_error&) {} }
@@ -611,6 +513,7 @@ void register_options()
" terminal_shift_function_key int\n"
" terminal_padding_char codepoint\n"
" terminal_padding_fill bool\n"
+ " terminal_cursor_native bool\n"
" terminal_info_max_width int\n",
UserInterface::Options{});
reg.declare_option("modelinefmt", "format string used to generate the modeline",
diff --git a/src/normal.cc b/src/normal.cc
index a2a2504b..37bbbb2a 100644
--- a/src/normal.cc
+++ b/src/normal.cc
@@ -1,11 +1,14 @@
#include "normal.hh"
+#include "array_view.hh"
+#include "assert.hh"
#include "buffer.hh"
#include "buffer_manager.hh"
#include "buffer_utils.hh"
#include "changes.hh"
#include "command_manager.hh"
#include "context.hh"
+#include "coord.hh"
#include "diff.hh"
#include "enum.hh"
#include "face_registry.hh"
@@ -19,6 +22,7 @@
#include "selectors.hh"
#include "shell_manager.hh"
#include "string.hh"
+#include "units.hh"
#include "user_interface.hh"
#include "unit_tests.hh"
#include "window.hh"
@@ -537,8 +541,9 @@ void command(Context& context, NormalParams params)
command(context, std::move(env_vars), params.reg);
}
-BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after)
+BufferRange apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> lines_before, StringView after)
{
+ BufferCoord first = pos;
const auto lines_after = after | split_after<StringView>('\n') | gather<Vector<StringView>>();
auto byte_count = [](auto&& lines, int first, int count) {
@@ -546,30 +551,108 @@ BufferCoord apply_diff(Buffer& buffer, BufferCoord pos, ArrayView<StringView> li
[](ByteCount l, StringView s) { return l + s.length(); });
};
+ bool tried_to_erase_final_newline = false;
for_each_diff(lines_before.begin(), (int)lines_before.size(),
lines_after.begin(), (int)lines_after.size(),
[&, posA = 0, posB = 0](DiffOp op, int len) mutable {
switch (op)
{
case DiffOp::Keep:
+ kak_assert(not tried_to_erase_final_newline);
pos = buffer.advance(pos, byte_count(lines_before, posA, len));
posA += len;
posB += len;
break;
case DiffOp::Add:
+ if (buffer.is_end(pos))
+ tried_to_erase_final_newline = false;
pos = buffer.insert(pos, {lines_after[posB].begin(),
lines_after[posB + len - 1].end()}).end;
posB += len;
break;
case DiffOp::Remove:
- pos = buffer.erase(pos, buffer.advance(pos, byte_count(lines_before, posA, len)));
+ {
+ kak_assert(not tried_to_erase_final_newline);
+ BufferCoord end = buffer.advance(pos, byte_count(lines_before, posA, len));
+ tried_to_erase_final_newline |= buffer.is_end(end);
+ pos = buffer.erase(pos, end);
posA += len;
break;
}
+ }
});
- return pos;
+ if (tried_to_erase_final_newline)
+ {
+ first = std::min(first, buffer.back_coord());
+ pos = buffer.erase(buffer.back_coord(), buffer.end_coord());
+ }
+ return {first, pos};
}
+UnitTest test_apply_diff{[] {
+ using Change = Buffer::Change;
+ auto validate = [&](LineCount line,
+ StringView new_text,
+ BufferRange expected_new_range,
+ ConstArrayView<Change> expected_buffer_changes)
+ {
+ Buffer buffer{"", Buffer::Flags::None, {
+ StringData::create("line1\n"),
+ StringData::create("line2\n"),
+ StringData::create("line3\n"),
+ }};
+ const size_t timestamp = buffer.timestamp();
+ Vector<StringView> old_text;
+ for (auto i = line; i < buffer.line_count(); i++)
+ old_text.push_back(buffer[i]);
+ BufferRange new_text_range = apply_diff(buffer, BufferCoord{line, 0}, old_text, new_text);
+ kak_assert(new_text_range == expected_new_range);
+ kak_assert(buffer.changes_since(timestamp) == expected_buffer_changes);
+ };
+ // When appending at end, we add any missing newline
+ validate(
+ /*line=*/3,
+ "added-line3-missing-eol",
+ BufferRange{{3, 0}, {4, 0}},
+ {
+ Change{Change::Insert, {3, 0}, {4, 0}},
+ }
+ );
+ // Special case: erasing until buffer end also erases the final newline.
+ validate(
+ /*line=*/2,
+ "",
+ BufferRange{{1, 5}, {1, 5}},
+ {
+ Change{Change::Erase, {2, 0}, {3, 0}},
+ }
+ );
+ // Erasing and appending at end still produces forward-only changes.
+ validate(
+ /*line=*/2,
+ "changed-line3\n"
+ "added-line4\n"
+ "added-line5\n",
+ BufferRange{{2, 0}, {5, 0}},
+ {
+ Change{Change::Erase, {2, 0}, {3, 0}},
+ Change{Change::Insert, {2, 0}, {5, 0}},
+ }
+ );
+ // Same result when append is missing newline.
+ validate(
+ /*line=*/2,
+ "changed-line3\n"
+ "added-line4\n"
+ "added-line5-missing-eol",
+ BufferRange{{2, 0}, {5, 0}},
+ {
+ Change{Change::Erase, {2, 0}, {3, 0}},
+ Change{Change::Insert, {2, 0}, {5, 0}},
+ }
+ );
+}};
+
template<bool replace>
void pipe(Context& context, NormalParams params)
{
@@ -628,12 +711,12 @@ void pipe(Context& context, NormalParams params)
if (in_lines.back().back() != '\n' and not out.empty() and out.back() == '\n')
out.resize(out.length()-1, 0);
- auto new_end = apply_diff(buffer, first, in_lines, out);
- if (new_end != first)
+ auto [new_first, new_end] = apply_diff(buffer, first, in_lines, out);
+ if (new_first != new_end)
{
auto& min = sel.min();
auto& max = sel.max();
- min = first;
+ min = new_first;
max = buffer.char_prev(new_end);
}
else
@@ -1612,6 +1695,7 @@ void replay_macro(Context& context, NormalParams params)
auto keys = parse_keys(reg_val[0]);
ScopedEdition edition(context);
ScopedSelectionEdition selection_edition{context};
+ ScopedSetBool disable_keymaps(context.keymaps_disabled());
do
{
for (auto& key : keys)
diff --git a/src/ranges.hh b/src/ranges.hh
index af13d6f8..f1eaffa3 100644
--- a/src/ranges.hh
+++ b/src/ranges.hh
@@ -546,10 +546,10 @@ auto find(Range&& range, const T& value)
}
template<typename Range, typename T>
-auto find_if(Range&& range, T op)
+auto find_if(Range&& range, T&& op)
{
using std::begin; using std::end;
- return std::find_if(begin(range), end(range), op);
+ return std::find_if(begin(range), end(range), std::forward<T>(op));
}
template<typename Range, typename T>
@@ -560,24 +560,24 @@ bool contains(Range&& range, const T& value)
}
template<typename Range, typename T>
-bool all_of(Range&& range, T op)
+bool all_of(Range&& range, T&& op)
{
using std::begin; using std::end;
- return std::all_of(begin(range), end(range), op);
+ return std::all_of(begin(range), end(range), std::forward<T>(op));
}
template<typename Range, typename T>
-bool any_of(Range&& range, T op)
+bool any_of(Range&& range, T&& op)
{
using std::begin; using std::end;
- return std::any_of(begin(range), end(range), op);
+ return std::any_of(begin(range), end(range), std::forward<T>(op));
}
template<typename Range, typename T>
-auto remove_if(Range&& range, T op)
+auto remove_if(Range&& range, T&& op)
{
using std::begin; using std::end;
- return std::remove_if(begin(range), end(range), op);
+ return std::remove_if(begin(range), end(range), std::forward<T>(op));
}
template<typename Range, typename U>
@@ -596,7 +596,7 @@ template<typename Range, typename Init, typename BinOp>
Init accumulate(Range&& c, Init&& init, BinOp&& op)
{
using std::begin; using std::end;
- return std::accumulate(begin(c), end(c), init, op);
+ return std::accumulate(begin(c), end(c), init, std::forward<BinOp>(op));
}
template<typename Range, typename Compare, typename Func>
@@ -632,36 +632,26 @@ auto gather()
}};
}
-template<typename ExceptionType, bool exact_size, size_t... Indexes>
-auto elements()
-{
- return ViewFactory{[=] (auto&& range) {
- using std::begin; using std::end;
- auto it = begin(range), end_it = end(range);
- size_t i = 0;
- auto elem = [&](size_t index) {
- for (; i < index; ++i)
- if (++it == end_it) throw ExceptionType{i};
- return *it;
- };
- // Note that initializer lists elements are guaranteed to be sequenced
- Array<std::remove_cvref_t<decltype(*begin(range))>, sizeof...(Indexes)> res{{elem(Indexes)...}};
- if (exact_size and ++it != end_it)
- throw ExceptionType{++i};
- return res;
- }};
-}
-
-template<typename ExceptionType, bool exact_size, size_t... Indexes>
-auto static_gather_impl(std::index_sequence<Indexes...>)
-{
- return elements<ExceptionType, exact_size, Indexes...>();
-}
-
template<typename ExceptionType, size_t size, bool exact_size = true>
auto static_gather()
{
- return static_gather_impl<ExceptionType, exact_size>(std::make_index_sequence<size>());
+ return []<size_t... Indexes>(std::index_sequence<Indexes...>) {
+ return ViewFactory{[] (auto&& range) {
+ using std::begin; using std::end;
+ auto it = begin(range), end_it = end(range);
+ size_t i = 0;
+ auto elem = [&](size_t index) {
+ for (; i < index; ++i)
+ if (++it == end_it) throw ExceptionType{i};
+ return *it;
+ };
+ // Note that initializer lists elements are guaranteed to be sequenced
+ Array<std::remove_cvref_t<decltype(*begin(range))>, sizeof...(Indexes)> res{{elem(Indexes)...}};
+ if (exact_size and ++it != end_it)
+ throw ExceptionType{++i};
+ return res;
+ }};
+ }(std::make_index_sequence<size>());
}
}
diff --git a/src/regex_impl.cc b/src/regex_impl.cc
index dd900125..8a46d833 100644
--- a/src/regex_impl.cc
+++ b/src/regex_impl.cc
@@ -505,6 +505,18 @@ private:
parse_error("unclosed character class");
++m_pos;
+ if (not character_class.ignore_case)
+ {
+ bool could_ignore_case = true;
+ for (const auto& [min, max] : character_class.ranges)
+ {
+ if (not contains(character_class.ranges, CharacterClass::Range{to_lower(min), to_lower(max)}) or
+ not contains(character_class.ranges, CharacterClass::Range{to_upper(min), to_upper(max)}))
+ could_ignore_case = false;
+ }
+ character_class.ignore_case = could_ignore_case;
+ }
+
if (character_class.ignore_case)
{
for (auto& range : character_class.ranges)
@@ -521,7 +533,7 @@ private:
if (character_class.ctypes == CharacterType::None and not character_class.negative and
character_class.ranges.size() == 1 and
character_class.ranges.front().min == character_class.ranges.front().max)
- return add_node(ParsedRegex::Literal, character_class.ranges.front().min);
+ return add_node(ParsedRegex::Literal, character_class.ranges.front().min, {1,1}, character_class.ignore_case);
if (character_class.ctypes != CharacterType::None and not character_class.negative and
character_class.ranges.empty())
@@ -585,14 +597,14 @@ private:
}
}
- NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1})
+ NodeIndex add_node(ParsedRegex::Op op, Codepoint value = -1, ParsedRegex::Quantifier quantifier = {1, 1}, bool ignore_case = false)
{
constexpr auto max_nodes = std::numeric_limits<int16_t>::max();
const NodeIndex res = m_parsed_regex.nodes.size();
if (res == max_nodes)
parse_error(format("regex parsed to more than {} ast nodes", max_nodes));
const NodeIndex next = res+1;
- m_parsed_regex.nodes.push_back({op, m_flags & Flags::IgnoreCase, next, value, quantifier});
+ m_parsed_regex.nodes.push_back({op, ignore_case or (m_flags & Flags::IgnoreCase), next, value, quantifier});
return res;
}
@@ -703,6 +715,7 @@ struct RegexCompiler
private:
template<RegexMode direction>
+ [[gnu::noinline]]
OpIndex compile_node_inner(ParsedRegex::NodeIndex index)
{
auto& node = get_node(index);
@@ -729,7 +742,15 @@ private:
push_inst(CompiledRegex::AnyCharExceptNewLine);
break;
case ParsedRegex::CharClass:
- push_inst(CompiledRegex::CharClass, {.character_class_index=int16_t(node.value)});
+ if (auto& char_class = m_parsed_regex.character_classes[node.value];
+ char_class.ranges.size() == 1 and char_class.ctypes == CharacterType::None and
+ char_class.ranges[0].max <= std::numeric_limits<uint8_t>::max())
+ push_inst(CompiledRegex::CharRange, {.range={.min=uint8_t(char_class.ranges[0].min),
+ .max=uint8_t(char_class.ranges[0].max),
+ .ignore_case=char_class.ignore_case,
+ .negative=char_class.negative}});
+ else
+ push_inst(CompiledRegex::CharClass, {.character_class_index=int16_t(node.value)});
break;
case ParsedRegex::CharType:
push_inst(CompiledRegex::CharType, {.character_type=CharacterType{(unsigned char)node.value}});
@@ -1107,12 +1128,18 @@ String dump_regex(const CompiledRegex& program)
case CompiledRegex::AnyCharExceptNewLine:
res += "anything but newline\n";
break;
- case CompiledRegex::CharClass:
- res += format("character class {}\n", inst.param.character_class_index);
+ case CompiledRegex::CharRange:
+ res += format("character range {}[{}{}-{}]\n",
+ inst.param.range.ignore_case ? "(ignore case) " : "",
+ inst.param.range.negative ? "^" : "",
+ inst.param.range.min, inst.param.range.max);
break;
case CompiledRegex::CharType:
res += format("character type {}\n", to_underlying(inst.param.character_type));
break;
+ case CompiledRegex::CharClass:
+ res += format("character class {}\n", inst.param.character_class_index);
+ break;
case CompiledRegex::Jump:
res += format("jump {} ({:03})\n", inst.param.jump_offset, index + inst.param.jump_offset);
break;
@@ -1259,6 +1286,12 @@ auto test_regex = UnitTest{[]{
}
{
+ TestVM<> vm{R"([aA])"};
+ kak_assert(vm.exec("a"));
+ kak_assert(vm.exec("A"));
+ }
+
+ {
TestVM<> vm{R"(a{3,5}b)"};
kak_assert(not vm.exec("aab"));
kak_assert(vm.exec("aaab"));
diff --git a/src/regex_impl.hh b/src/regex_impl.hh
index b04d99e7..8853ced5 100644
--- a/src/regex_impl.hh
+++ b/src/regex_impl.hh
@@ -76,8 +76,9 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex>
Literal,
AnyChar,
AnyCharExceptNewLine,
- CharClass,
+ CharRange,
CharType,
+ CharClass,
Jump,
Split,
Save,
@@ -105,8 +106,15 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex>
uint32_t codepoint : 24;
bool ignore_case : 1;
} literal;
- int16_t character_class_index;
+ struct CharRange
+ {
+ uint8_t min;
+ uint8_t max;
+ bool ignore_case : 1;
+ bool negative;
+ } range;
CharacterType character_type;
+ int16_t character_class_index;
int16_t jump_offset;
int16_t save_index;
struct Split
@@ -361,23 +369,20 @@ private:
void step_current_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, const ExecConfig& config)
{
Thread thread = m_threads.pop_current();
- auto failed = [this, &thread]() {
- release_saves(thread.saves);
- };
- auto consumed = [this, &thread]() {
- m_threads.push_next(thread);
- };
+ auto failed = [&] { release_saves(thread.saves); };
+ auto consumed = [&] { m_threads.push_next(thread); };
while (true)
{
- auto& inst = *thread.inst++;
+ auto* inst = thread.inst++;
+ auto [op, last_step, param] = *inst;
// if this instruction was already executed for this step in another thread,
// then this thread is redundant and can be dropped
- if (inst.last_step == current_step)
+ if (last_step == current_step)
return failed();
- inst.last_step = current_step;
+ inst->last_step = current_step;
- switch (inst.op)
+ switch (op)
{
case CompiledRegex::Match:
if ((pos != config.end and not (mode & RegexMode::Search)) or
@@ -394,7 +399,7 @@ private:
return;
case CompiledRegex::Literal:
if (pos != config.end and
- inst.param.literal.codepoint == (inst.param.literal.ignore_case ? to_lower(cp) : cp))
+ param.literal.codepoint == (param.literal.ignore_case ? to_lower(cp) : cp))
return consumed();
return failed();
case CompiledRegex::AnyChar:
@@ -403,25 +408,31 @@ private:
if (pos != config.end and cp != '\n')
return consumed();
return failed();
- case CompiledRegex::CharClass:
- if (pos != config.end and
- m_program.character_classes[inst.param.character_class_index].matches(cp))
+ case CompiledRegex::CharRange:
+ if (auto actual_cp = (param.range.ignore_case ? to_lower(cp) : cp);
+ pos != config.end and
+ (actual_cp >= param.range.min and actual_cp <= param.range.max) != param.range.negative)
return consumed();
return failed();
case CompiledRegex::CharType:
- if (pos != config.end and is_ctype(inst.param.character_type, cp))
+ if (pos != config.end and is_ctype(param.character_type, cp))
+ return consumed();
+ return failed();
+ case CompiledRegex::CharClass:
+ if (pos != config.end and
+ m_program.character_classes[param.character_class_index].matches(cp))
return consumed();
return failed();
case CompiledRegex::Jump:
- thread.inst = &inst + inst.param.jump_offset;
+ thread.inst = inst + param.jump_offset;
break;
case CompiledRegex::Split:
- if (auto* target = &inst + inst.param.split.offset;
+ if (auto* target = inst + param.split.offset;
target->last_step != current_step)
{
if (thread.saves >= 0)
++m_saves[thread.saves].refcount;
- if (not inst.param.split.prioritize_parent)
+ if (not param.split.prioritize_parent)
std::swap(thread.inst, target);
m_threads.push_current({target, thread.saves});
}
@@ -436,23 +447,23 @@ private:
--saves.refcount;
thread.saves = new_saves<true>(saves.pos, saves.valid_mask);
}
- m_saves[thread.saves].pos[inst.param.save_index] = pos;
- m_saves[thread.saves].valid_mask |= (1 << inst.param.save_index);
+ m_saves[thread.saves].pos[param.save_index] = pos;
+ m_saves[thread.saves].valid_mask |= (1 << param.save_index);
break;
case CompiledRegex::LineAssertion:
- if (not (inst.param.line_start ? is_line_start(pos, config) : is_line_end(pos, config)))
+ if (not (param.line_start ? is_line_start(pos, config) : is_line_end(pos, config)))
return failed();
break;
case CompiledRegex::SubjectAssertion:
- if (pos != (inst.param.subject_begin ? config.subject_begin : config.subject_end))
+ if (pos != (param.subject_begin ? config.subject_begin : config.subject_end))
return failed();
break;
case CompiledRegex::WordBoundary:
- if (is_word_boundary(pos, config) != inst.param.word_boundary_positive)
+ if (is_word_boundary(pos, config) != param.word_boundary_positive)
return failed();
break;
case CompiledRegex::LookAround:
- if (lookaround(inst.param.lookaround, pos, config) != inst.param.lookaround.positive)
+ if (lookaround(param.lookaround, pos, config) != param.lookaround.positive)
return failed();
break;
}
@@ -691,13 +702,13 @@ private:
void ensure_initial_capacity()
{
- if (m_capacity != 0)
+ if (m_capacity_mask != 0)
return;
- constexpr int32_t initial_capacity = 64 / sizeof(Thread);
- static_assert(initial_capacity >= 4);
+ constexpr uint32_t initial_capacity = 64 / sizeof(Thread);
+ static_assert(initial_capacity >= 4 and std::has_single_bit(initial_capacity));
m_data.reset(new Thread[initial_capacity]);
- m_capacity = initial_capacity;
+ m_capacity_mask = initial_capacity-1;
}
[[gnu::always_inline]]
@@ -710,41 +721,41 @@ private:
[[gnu::noinline]]
void grow(bool pushed_current)
{
- const auto new_capacity = m_capacity * 2;
+ auto capacity = m_capacity_mask + 1;
+ const auto new_capacity = capacity * 2;
Thread* new_data = new Thread[new_capacity];
Thread* old_data = m_data.get();
- std::rotate_copy(old_data, old_data + m_current, old_data + m_capacity, new_data);
- m_next_begin -= m_current;
- if ((pushed_current and m_next_begin == 0) or m_next_begin < 0)
- m_next_begin += m_capacity;
- m_next_end = m_capacity;
+ std::rotate_copy(old_data, old_data + m_current, old_data + capacity, new_data);
+ m_next_begin = (m_next_begin - m_current) & m_capacity_mask;
+ if (pushed_current and m_next_begin == 0)
+ m_next_begin = capacity;
+ m_next_end = capacity;
m_current = 0;
m_data.reset(new_data);
- m_capacity = new_capacity;
+ kak_assert(std::has_single_bit(new_capacity));
+ m_capacity_mask = new_capacity-1;
}
private:
- int32_t decrement(int32_t& index)
+ uint32_t decrement(uint32_t& index)
{
- if (index == 0)
- index = m_capacity;
- return --index;
+ index = (index - 1) & m_capacity_mask;
+ return index;
}
- int32_t post_increment(int32_t& index)
+ uint32_t post_increment(uint32_t& index)
{
auto res = index;
- if (++index == m_capacity)
- index = 0;
+ index = (index + 1) & m_capacity_mask;
return res;
}
std::unique_ptr<Thread[]> m_data;
- int32_t m_capacity = 0; // Maximum capacity should be 2*instruction count, so 65536
- int32_t m_current = 0;
- int32_t m_next_begin = 0;
- int32_t m_next_end = 0;
+ uint32_t m_capacity_mask = 0; // Maximum capacity should be 2*instruction count, so 65536
+ uint32_t m_current = 0;
+ uint32_t m_next_begin = 0;
+ uint32_t m_next_end = 0;
};
static constexpr bool forward = mode & RegexMode::Forward;
diff --git a/src/string.cc b/src/string.cc
index 353a9f4e..551add2d 100644
--- a/src/string.cc
+++ b/src/string.cc
@@ -118,6 +118,21 @@ void String::Data::clear()
set_empty();
}
+String::String(Codepoint cp, CharCount count)
+{
+ reserve(utf8::codepoint_size(cp) * (int)count);
+ while (count-- > 0)
+ utf8::dump(std::back_inserter(*this), cp);
+}
+
+String::String(Codepoint cp, ColumnCount count)
+{
+ int cp_count = (int)(count / max(codepoint_width(cp), 1_col));
+ reserve(utf8::codepoint_size(cp) * cp_count);
+ while (cp_count-- > 0)
+ utf8::dump(std::back_inserter(*this), cp);
+}
+
void String::resize(ByteCount size, char c)
{
const size_t target_size = (size_t)size;
diff --git a/src/string.hh b/src/string.hh
index 3b8c9013..4d7b60f9 100644
--- a/src/string.hh
+++ b/src/string.hh
@@ -1,14 +1,14 @@
#ifndef string_hh_INCLUDED
#define string_hh_INCLUDED
-#include <climits>
-#include <cstddef>
#include "memory.hh"
#include "hash.hh"
#include "units.hh"
#include "utf8.hh"
-#include <algorithm>
+#include <climits>
+#include <cstddef>
+#include <cstring>
namespace Kakoune
{
@@ -109,21 +109,9 @@ public:
String() {}
String(const char* content) : m_data(content, (size_t)strlen(content)) {}
String(const char* content, ByteCount len) : m_data(content, (size_t)len) {}
- explicit String(Codepoint cp, CharCount count = 1)
- {
- reserve(utf8::codepoint_size(cp) * (int)count);
- while (count-- > 0)
- utf8::dump(std::back_inserter(*this), cp);
- }
- explicit String(Codepoint cp, ColumnCount count)
- {
- int cp_count = (int)(count / std::max(codepoint_width(cp), 1_col));
- reserve(utf8::codepoint_size(cp) * cp_count);
- while (cp_count-- > 0)
- utf8::dump(std::back_inserter(*this), cp);
- }
String(const char* begin, const char* end) : m_data(begin, end-begin) {}
-
+ explicit String(Codepoint cp, CharCount count = 1);
+ explicit String(Codepoint cp, ColumnCount count);
explicit String(StringView str);
struct NoCopy{};
@@ -298,9 +286,10 @@ inline String String::no_copy(StringView str) { return {NoCopy{}, str}; }
template<typename Type, typename CharType>
inline StringView StringOps<Type, CharType>::substr(ByteCount from, ByteCount length) const
{
- const auto str_len = type().length();
- kak_assert(from >= 0 and from <= str_len);
- return StringView{type().data() + (int)from, std::min(str_len - from, length >= 0 ? length : str_len)};
+ const auto str_length = type().length();
+ const auto max_length = str_length - from;
+ kak_assert(from >= 0 and max_length >= 0);
+ return StringView{type().data() + (int)from, length >= 0 and length < max_length ? length : max_length};
}
template<typename Type, typename CharType>
@@ -352,7 +341,7 @@ inline String operator+(StringView lhs, StringView rhs)
inline bool operator==(const StringView& lhs, const StringView& rhs)
{
return lhs.length() == rhs.length() and
- std::equal(lhs.begin(), lhs.end(), rhs.begin());
+ (lhs.empty() or std::memcmp(lhs.begin(), rhs.begin(), (size_t)lhs.length()) == 0);
}
inline auto operator<=>(const StringView& lhs, const StringView& rhs)
diff --git a/src/terminal_ui.cc b/src/terminal_ui.cc
index 672626e4..c55d0763 100644
--- a/src/terminal_ui.cc
+++ b/src/terminal_ui.cc
@@ -1574,6 +1574,13 @@ void TerminalUI::set_ui_options(const Options& options)
m_padding_char = find("terminal_padding_char").map([](StringView s) { return s.column_length() < 1 ? ' ' : s[0_char]; }).value_or(Codepoint{'~'});
m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false);
+
+ bool new_cursor_native = find("terminal_cursor_native").map(to_bool).value_or(false);
+ if (new_cursor_native != m_cursor_native)
+ {
+ m_cursor_native = new_cursor_native;
+ write(STDOUT_FILENO, m_cursor_native ? "\033[?25h" : "\033[?25l");
+ }
m_info_max_width = find("terminal_info_max_width").map(str_to_int_ifp).value_or(0);
}
diff --git a/src/terminal_ui.hh b/src/terminal_ui.hh
index 02085532..064edafe 100644
--- a/src/terminal_ui.hh
+++ b/src/terminal_ui.hh
@@ -167,6 +167,7 @@ private:
Codepoint m_padding_char = '~';
bool m_padding_fill = false;
+ bool m_cursor_native = false;
bool m_dirty = false;
diff --git a/src/utils.hh b/src/utils.hh
index 4b84ce75..b517735f 100644
--- a/src/utils.hh
+++ b/src/utils.hh
@@ -73,6 +73,9 @@ public:
{ other.m_valid = false; }
[[gnu::always_inline]]
+ void trigger() { if (m_valid) m_func(); m_valid = false; }
+
+ [[gnu::always_inline]]
~OnScopeEnd() noexcept(noexcept(m_func())) { if (m_valid) m_func(); }
private:
diff --git a/src/window.cc b/src/window.cc
index 520d77e1..c65bebe8 100644
--- a/src/window.cc
+++ b/src/window.cc
@@ -132,7 +132,7 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context)
return m_display_buffer;
kak_assert(&buffer() == &context.buffer());
- const DisplaySetup setup = compute_display_setup(context);
+ DisplaySetup setup = compute_display_setup(context);
for (LineCount line = 0; line < setup.line_count; ++line)
{
@@ -144,8 +144,26 @@ const DisplayBuffer& Window::update_display_buffer(const Context& context)
m_display_buffer.compute_range();
const BufferRange range{{0,0}, buffer().end_coord()};
- m_builtin_highlighters.highlight({context, setup, HighlightPass::Wrap, {}}, m_display_buffer, range);
- m_builtin_highlighters.highlight({context, setup, HighlightPass::Move, {}}, m_display_buffer, range);
+ for (auto pass : {HighlightPass::Replace, HighlightPass::Wrap, HighlightPass::Move})
+ m_builtin_highlighters.highlight({context, setup, pass, {}}, m_display_buffer, range);
+
+ if (context.ensure_cursor_visible)
+ {
+ auto cursor_pos = display_coord(context.selections().main().cursor());
+ kak_assert(cursor_pos);
+
+ if (auto line_overflow = cursor_pos->line - m_dimensions.line + setup.scroll_offset.line + 1; line_overflow > 0)
+ {
+ lines.erase(lines.begin(), lines.begin() + (size_t)line_overflow);
+ setup.first_line = lines.begin()->range().begin.line;
+ }
+
+ auto max_first_column = cursor_pos->column - (setup.widget_columns + setup.scroll_offset.column);
+ setup.first_column = std::min(setup.first_column, max_first_column);
+
+ auto min_first_column = cursor_pos->column - (m_dimensions.column - setup.scroll_offset.column) + 1;
+ setup.first_column = std::max(setup.first_column, min_first_column);
+ }
for (auto& line : m_display_buffer.lines())
line.trim_from(setup.widget_columns, setup.first_column, m_dimensions.column);
@@ -203,11 +221,9 @@ DisplaySetup Window::compute_display_setup(const Context& context) const
offset.line = std::min(offset.line, (m_dimensions.line + 1) / 2);
offset.column = std::min(offset.column, (m_dimensions.column + 1) / 2);
- const int tabstop = context.options()["tabstop"].get<int>();
const auto& cursor = context.selections().main().cursor();
- bool ensure_cursor_visible = context.ensure_cursor_visible;
- if (ensure_cursor_visible)
+ if (context.ensure_cursor_visible)
{
if (cursor.line - offset.line < win_pos.line)
win_pos.line = std::max(0_line, cursor.line - offset.line);
@@ -216,39 +232,11 @@ DisplaySetup Window::compute_display_setup(const Context& context) const
}
win_pos.line = std::min(win_pos.line, buffer().line_count()-1);
- DisplaySetup setup{
- win_pos.line,
- m_dimensions.line,
- win_pos.column,
- 0_col,
- {cursor.line - win_pos.line,
- get_column(buffer(), tabstop, cursor) - win_pos.column},
- offset,
- ensure_cursor_visible
- };
- for (auto pass : { HighlightPass::Move, HighlightPass::Wrap })
+ DisplaySetup setup{win_pos.line, m_dimensions.line, win_pos.column, 0_col, offset};
+ for (auto pass : {HighlightPass::Move, HighlightPass::Wrap, HighlightPass::Replace})
m_builtin_highlighters.compute_display_setup({context, setup, pass, {}}, setup);
check_display_setup(setup, *this);
- // now ensure the cursor column is visible
- if (ensure_cursor_visible)
- {
- auto underflow = std::max(-setup.first_column,
- setup.cursor_pos.column - setup.scroll_offset.column);
- if (underflow < 0)
- {
- setup.first_column += underflow;
- setup.cursor_pos.column -= underflow;
- }
- auto overflow = setup.cursor_pos.column + setup.scroll_offset.column - (m_dimensions.column - setup.widget_columns) + 1;
- if (overflow > 0)
- {
- setup.first_column += overflow;
- setup.cursor_pos.column -= overflow;
- }
- check_display_setup(setup, *this);
- }
-
return setup;
}
diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/rc b/test/highlight/replace-multiline-range-pulls-new-lines/rc
index fd2a7b6e..33eb085c 100644
--- a/test/highlight/replace-multiline-range-pulls-new-lines/rc
+++ b/test/highlight/replace-multiline-range-pulls-new-lines/rc
@@ -1,3 +1,4 @@
declare-option range-specs ranges
+add-highlighter window/ number-lines
add-highlighter window/ replace-ranges ranges
set-option buffer ranges %val{timestamp} 2.1,6.2|..
diff --git a/test/highlight/replace-multiline-range-pulls-new-lines/script b/test/highlight/replace-multiline-range-pulls-new-lines/script
index 0c4161b1..042e897f 100644
--- a/test/highlight/replace-multiline-range-pulls-new-lines/script
+++ b/test/highlight/replace-multiline-range-pulls-new-lines/script
@@ -1,7 +1,3 @@
ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
-ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
-ui_out '{ "jsonrpc": "2.0", "method": "menu_hide", "params": [] }'
-ui_out '{ "jsonrpc": "2.0", "method": "info_hide", "params": [] }'
-ui_out '{ "jsonrpc": "2.0", "method": "draw_status", "params": [[], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "out 1:1 " }, { "face": { "fg": "black", "bg": "yellow", "underline": "default", "attributes": [] }, "contents": "" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1 sel" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " - client0@[kak-tests]" }], { "fg": "cyan", "bg": "default", "underline": "default", "attributes": [] }] }'
-ui_out '{ "jsonrpc": "2.0", "method": "set_cursor", "params": ["buffer", { "line": 0, "column": 0 }] }'
-ui_out '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "0" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": ".." }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 7│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "07\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 8│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "08\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 9│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "09\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "11\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "12\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "13\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "14\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "15\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "16\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "17\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "18\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "19\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "20\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "21\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "22\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "24\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "25\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "26\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "27\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "28\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in
new file mode 100644
index 00000000..e4b48b88
--- /dev/null
+++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/in
@@ -0,0 +1 @@
+xxxxxxxx1 2 3 4 5 6 7 8 9 10%( )
diff --git a/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script
new file mode 100644
index 00000000..73bd69b1
--- /dev/null
+++ b/test/highlight/tabulation/ensure-cursor-on-tab-fully-visible/script
@@ -0,0 +1,2 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "2" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "3" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "4" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "5" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "6" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "7" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "8" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "9" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "10" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": " " }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
diff --git a/test/highlight/wrap/basic/cmd b/test/highlight/wrap/basic/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/basic/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/basic/in b/test/highlight/wrap/basic/in
new file mode 100644
index 00000000..fb85aeed
--- /dev/null
+++ b/test/highlight/wrap/basic/in
@@ -0,0 +1,2 @@
+--------------------------------------------------------------------------------wrap
+--------------------------------------------------------------------------------wrap----------------------------------------------------------------------------wrap
diff --git a/test/highlight/wrap/basic/rc b/test/highlight/wrap/basic/rc
new file mode 100644
index 00000000..2cd258c4
--- /dev/null
+++ b/test/highlight/wrap/basic/rc
@@ -0,0 +1 @@
+add-highlighter window/ wrap
diff --git a/test/highlight/wrap/basic/script b/test/highlight/wrap/basic/script
new file mode 100644
index 00000000..7151f6a6
--- /dev/null
+++ b/test/highlight/wrap/basic/script
@@ -0,0 +1,4 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "--------------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap----------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+
diff --git a/test/highlight/wrap/interact-with-number-lines/cmd b/test/highlight/wrap/interact-with-number-lines/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/interact-with-number-lines/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/interact-with-number-lines/in b/test/highlight/wrap/interact-with-number-lines/in
new file mode 100644
index 00000000..7bba8c8e
--- /dev/null
+++ b/test/highlight/wrap/interact-with-number-lines/in
@@ -0,0 +1,2 @@
+line 1
+line 2
diff --git a/test/highlight/wrap/interact-with-number-lines/rc b/test/highlight/wrap/interact-with-number-lines/rc
new file mode 100644
index 00000000..8f447491
--- /dev/null
+++ b/test/highlight/wrap/interact-with-number-lines/rc
@@ -0,0 +1,2 @@
+add-highlighter window/ wrap -word -width 4
+add-highlighter window/ number-lines
diff --git a/test/highlight/wrap/interact-with-number-lines/script b/test/highlight/wrap/interact-with-number-lines/script
new file mode 100644
index 00000000..67e34fea
--- /dev/null
+++ b/test/highlight/wrap/interact-with-number-lines/script
@@ -0,0 +1,3 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "l" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "ine" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "line" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 2" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 2\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
diff --git a/test/highlight/wrap/interact-with-replace-ranges/cmd b/test/highlight/wrap/interact-with-replace-ranges/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/interact-with-replace-ranges/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/interact-with-replace-ranges/in b/test/highlight/wrap/interact-with-replace-ranges/in
new file mode 100644
index 00000000..1c17e1bf
--- /dev/null
+++ b/test/highlight/wrap/interact-with-replace-ranges/in
@@ -0,0 +1,3 @@
+------------------------------------------------------------------------- wrap
+-------------------------------------------------------------------------
+prefix replaced wrapped
diff --git a/test/highlight/wrap/interact-with-replace-ranges/rc b/test/highlight/wrap/interact-with-replace-ranges/rc
new file mode 100644
index 00000000..563fc934
--- /dev/null
+++ b/test/highlight/wrap/interact-with-replace-ranges/rc
@@ -0,0 +1,4 @@
+declare-option range-specs ranges %val{timestamp} '1.1+0|HINT:' '2.74+0|WRAPPED HINT' '3.8+8|OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
+
+add-highlighter window/ wrap -word
+add-highlighter window/ replace-ranges ranges
diff --git a/test/highlight/wrap/interact-with-replace-ranges/script b/test/highlight/wrap/interact-with-replace-ranges/script
new file mode 100644
index 00000000..6511f11c
--- /dev/null
+++ b/test/highlight/wrap/interact-with-replace-ranges/script
@@ -0,0 +1,3 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "HINT:" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "------------------------------------------------------------------------ " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "WRAPPED HINT" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "prefix " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "OVERFLOWING HINT XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " wrapped\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
diff --git a/test/highlight/wrap/interact-with-show-whitespaces/cmd b/test/highlight/wrap/interact-with-show-whitespaces/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/interact-with-show-whitespaces/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/interact-with-show-whitespaces/in b/test/highlight/wrap/interact-with-show-whitespaces/in
new file mode 100644
index 00000000..d675fa44
--- /dev/null
+++ b/test/highlight/wrap/interact-with-show-whitespaces/in
@@ -0,0 +1 @@
+foo bar
diff --git a/test/highlight/wrap/interact-with-show-whitespaces/rc b/test/highlight/wrap/interact-with-show-whitespaces/rc
new file mode 100644
index 00000000..37119e08
--- /dev/null
+++ b/test/highlight/wrap/interact-with-show-whitespaces/rc
@@ -0,0 +1,2 @@
+add-highlighter window/ wrap -word -width 5
+add-highlighter window/ show-whitespaces
diff --git a/test/highlight/wrap/interact-with-show-whitespaces/script b/test/highlight/wrap/interact-with-show-whitespaces/script
new file mode 100644
index 00000000..538ab98a
--- /dev/null
+++ b/test/highlight/wrap/interact-with-show-whitespaces/script
@@ -0,0 +1,4 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "f" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "oo" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["final_fg"] }, "contents": "·" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "bar" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["final_fg"] }, "contents": "¬" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+
diff --git a/test/highlight/wrap/interact-with-tabulation/cmd b/test/highlight/wrap/interact-with-tabulation/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/interact-with-tabulation/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/interact-with-tabulation/in b/test/highlight/wrap/interact-with-tabulation/in
new file mode 100644
index 00000000..9077d5dc
--- /dev/null
+++ b/test/highlight/wrap/interact-with-tabulation/in
@@ -0,0 +1 @@
+----------------------------------------------------------------------------
diff --git a/test/highlight/wrap/interact-with-tabulation/rc b/test/highlight/wrap/interact-with-tabulation/rc
new file mode 100644
index 00000000..784f691c
--- /dev/null
+++ b/test/highlight/wrap/interact-with-tabulation/rc
@@ -0,0 +1,2 @@
+add-highlighter window/ number-lines
+add-highlighter window/ wrap
diff --git a/test/highlight/wrap/interact-with-tabulation/script b/test/highlight/wrap/interact-with-tabulation/script
new file mode 100644
index 00000000..4961bd6c
--- /dev/null
+++ b/test/highlight/wrap/interact-with-tabulation/script
@@ -0,0 +1,4 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " 1│" }, { "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "---------------------------------------------------------------------------" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": ["italic"] }, "contents": " 1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "│" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+
diff --git a/test/highlight/wrap/marker-and-indent/cmd b/test/highlight/wrap/marker-and-indent/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/marker-and-indent/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/marker-and-indent/in b/test/highlight/wrap/marker-and-indent/in
new file mode 100644
index 00000000..37962f9e
--- /dev/null
+++ b/test/highlight/wrap/marker-and-indent/in
@@ -0,0 +1,4 @@
+--------------------------------------------------------------------------------wrap
+--------------------------------------------------------------------------------wrap-------------------------------------------------------------------------wrap
+ ----------------------------------------------------------------------------wrap
+ ----------------------------------------------------------------------------wrap------------------------------------------------------------------------wrap
diff --git a/test/highlight/wrap/marker-and-indent/rc b/test/highlight/wrap/marker-and-indent/rc
new file mode 100644
index 00000000..3ac2bf1e
--- /dev/null
+++ b/test/highlight/wrap/marker-and-indent/rc
@@ -0,0 +1 @@
+add-highlighter window/ wrap -marker '>>>' -indent
diff --git a/test/highlight/wrap/marker-and-indent/script b/test/highlight/wrap/marker-and-indent/script
new file mode 100644
index 00000000..72861f73
--- /dev/null
+++ b/test/highlight/wrap/marker-and-indent/script
@@ -0,0 +1,4 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "-" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "-------------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "--------------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap-------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " ----------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " ----------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap------------------------------------------------------------------------" }], [{ "face": { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }, "contents": ">>>" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": " " }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+
diff --git a/test/highlight/wrap/word/cmd b/test/highlight/wrap/word/cmd
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/highlight/wrap/word/cmd
@@ -0,0 +1 @@
+
diff --git a/test/highlight/wrap/word/in b/test/highlight/wrap/word/in
new file mode 100644
index 00000000..5cdc9a1d
--- /dev/null
+++ b/test/highlight/wrap/word/in
@@ -0,0 +1,3 @@
+123456789 wrap
+123456 wrap
+123456 wrap 1234 wrap 123456789012 wrap
diff --git a/test/highlight/wrap/word/rc b/test/highlight/wrap/word/rc
new file mode 100644
index 00000000..921daf40
--- /dev/null
+++ b/test/highlight/wrap/word/rc
@@ -0,0 +1 @@
+add-highlighter window/ wrap -word -width 10
diff --git a/test/highlight/wrap/word/script b/test/highlight/wrap/word/script
new file mode 100644
index 00000000..eb18dadc
--- /dev/null
+++ b/test/highlight/wrap/word/script
@@ -0,0 +1,4 @@
+ui_out '{ "jsonrpc": "2.0", "method": "set_ui_options", "params": [{}] }'
+ui_out '{ "jsonrpc": "2.0", "method": "draw", "params": [[[{ "face": { "fg": "black", "bg": "white", "underline": "default", "attributes": [] }, "contents": "1" }, { "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "23456789 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "123456 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "123456 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap 1234 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap 12345" }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "6789012 " }], [{ "face": { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, "contents": "wrap\u000a" }]], { "fg": "default", "bg": "default", "underline": "default", "attributes": [] }, { "fg": "blue", "bg": "default", "underline": "default", "attributes": [] }] }'
+ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [true] }'
+
diff --git a/test/normal/pipe-replaces-all-lines/cmd b/test/normal/pipe-replaces-all-lines/cmd
new file mode 100644
index 00000000..fb90dc1a
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/cmd
@@ -0,0 +1 @@
+%|printf '\n\n'<ret>
diff --git a/test/normal/pipe-replaces-all-lines/in b/test/normal/pipe-replaces-all-lines/in
new file mode 100644
index 00000000..422c2b7a
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/in
@@ -0,0 +1,2 @@
+a
+b
diff --git a/test/normal/pipe-replaces-all-lines/out b/test/normal/pipe-replaces-all-lines/out
new file mode 100644
index 00000000..139597f9
--- /dev/null
+++ b/test/normal/pipe-replaces-all-lines/out
@@ -0,0 +1,2 @@
+
+
diff --git a/test/regression/0-slow-BufCloseFifo/cmd b/test/regression/0-slow-BufCloseFifo/cmd
new file mode 100644
index 00000000..2eeea72e
--- /dev/null
+++ b/test/regression/0-slow-BufCloseFifo/cmd
@@ -0,0 +1 @@
+:run<ret>
diff --git a/test/regression/0-slow-BufCloseFifo/in b/test/regression/0-slow-BufCloseFifo/in
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/regression/0-slow-BufCloseFifo/in
@@ -0,0 +1 @@
+
diff --git a/test/regression/0-slow-BufCloseFifo/rc b/test/regression/0-slow-BufCloseFifo/rc
new file mode 100644
index 00000000..8a953e18
--- /dev/null
+++ b/test/regression/0-slow-BufCloseFifo/rc
@@ -0,0 +1,16 @@
+define-command run %{
+ evaluate-commands %sh{
+ mkfifo fifo1 fifo2 2>/dev/null
+ ( : >fifo1 & ) > /dev/null 2>&1 </dev/null
+ }
+ edit! -fifo fifo1 *fifo*
+ add-highlighter global/myhl regex foo 0:green
+ hook -once global BufCloseFifo .* %{
+ evaluate-commands -client client0 %{
+ nop %sh{sleep 2}
+ }
+ hook -once buffer NormalIdle .* %{
+ echo -to-file fifo2 still alive
+ }
+ }
+}
diff --git a/test/regression/0-slow-BufCloseFifo/script b/test/regression/0-slow-BufCloseFifo/script
new file mode 100644
index 00000000..2147a8e8
--- /dev/null
+++ b/test/regression/0-slow-BufCloseFifo/script
@@ -0,0 +1,2 @@
+mkfifo fifo2 2>/dev/null
+assert_eq "$(cat fifo2)" "still alive"
diff --git a/test/regression/2999-buggy-wrapping/cmd b/test/regression/2999-buggy-wrapping/cmd
index 4c559f78..5238dfce 100644
--- a/test/regression/2999-buggy-wrapping/cmd
+++ b/test/regression/2999-buggy-wrapping/cmd
@@ -1 +1 @@
-j
+jvt
diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd
new file mode 100644
index 00000000..a1a5692c
--- /dev/null
+++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/cmd
@@ -0,0 +1 @@
+:e -scratch<ret><c-o>
diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/in
@@ -0,0 +1 @@
+
diff --git a/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc
new file mode 100644
index 00000000..6240a36f
--- /dev/null
+++ b/test/regression/5338-crash-when-changing-buffer-on-WinDiplay/rc
@@ -0,0 +1 @@
+hook global WinDisplay .* %{ edit out }
diff --git a/test/run b/test/run
index a26642da..c85d188a 100755
--- a/test/run
+++ b/test/run
@@ -32,15 +32,13 @@ main() {
number_tests=0
number_failures=0
- for dir in $(find "${@:-.}" -type d | sort); do
+ for dir in $(find "${@:-.}" -type d | sed 's|^\./||' | sort); do
cd $root/$dir;
- indent="$(echo "$dir/" | sed -e 's|[^/]*//*| |g')"
- name=${PWD##*/}
if [ ! -f cmd ]; then
- echo "$indent$name"
+ echo "$dir"
continue
elif [ -x enabled ] && ! ./enabled; then
- printf "${yellow}$indent%s (disabled)${none}\n" "$name"
+ echo "${yellow}$dir (disabled)${none}"
continue
fi
@@ -81,8 +79,8 @@ main() {
if [ ! -e error ]; then # failure not expected
if [ $retval -ne 0 ]; then
- printf "${red}$indent%s${none}\n" "$name"
- echo "$indent Kakoune returned error $retval"
+ printf "${red}%s${none}\n" "$dir"
+ echo " Kakoune returned error $retval"
failed=1
else
for file in out $env_vars; do
@@ -100,19 +98,19 @@ main() {
if [ -f stderr ]; then
sed -i -e 's/^[0-9]*:[0-9]*: //g' stderr
if [ -s error ] && ! cmp -s error stderr; then
- printf "${yellow}$indent%s${none}\n" "$name"
+ printf "${yellow}%s${none}\n" "$dir"
show_diff error stderr
failed=1
fi
elif [ $retval -eq 0 ]; then
- printf "${red}$indent%s${none}\n" "$name"
- echo "$indent Expected failure, but Kakoune returned 0"
+ printf "${red}%s${none}\n" "$dir"
+ echo " Expected failure, but Kakoune returned 0"
failed=1
fi
fi
if [ $failed -eq 0 ]; then
- printf "${green}$indent%s${none}\n" "$name"
+ printf "${green}%s${none}\n" "$dir"
else
number_failures=$(($number_failures + 1))
fi
@@ -131,7 +129,7 @@ main() {
fail_ifn() {
if [ $failed -eq 0 ]; then
- printf "${red}$indent%s${none}\n" "$name"
+ printf "${red}%s${none}\n" "$dir"
failed=1
fi
}
@@ -139,7 +137,13 @@ fail_ifn() {
assert_eq() {
if [ ! "$1" = "$2" ]; then
fail_ifn
- printf "${indent} ${red}- %s\n${indent} ${green}+ %s${none}\n" "$1" "$2"
+ if command -v git > /dev/null; then
+ echo "$1" > expected
+ echo "$2" > actual
+ git --no-pager diff --color-words --no-index expected actual
+ else
+ printf " ${red}- %s\n ${green}+ %s${none}\n" "$1" "$2"
+ fi
fi
}
@@ -198,11 +202,11 @@ ui_out() {
[ "$event" = "$expected" ] && break
done
;;
- -until-eval | -until-grep)
+ -until-grep)
pattern=$1
shift
while read -r event <&4; do
- if printf %s "$event" | "${arg#-until-}" "$pattern" >/dev/null; then
+ if printf %s "$event" | grep "$pattern" >/dev/null; then
if [ $# -ne 0 ]; then
assert_eq "$1" "$event"
shift
diff --git a/test/tools/git/blame-in-diff/rc b/test/tools/git/blame-in-diff/rc
index eba02270..1fc54f3f 100644
--- a/test/tools/git/blame-in-diff/rc
+++ b/test/tools/git/blame-in-diff/rc
@@ -11,4 +11,19 @@ define-command run %{
git commit --all --message 'changed line 2'
# Show the commit, jumping to the new version of line 2.
git blame-jump
+ hook -once buffer BufCloseFifo .* %{
+ evaluate-commands -client client0 %{
+ # We've jumped to the new version of line 2. Move to the old
+ # version so we can annotate the old version of the file.
+ execute-keys k
+ git blame
+ hook -once buffer BufCloseFifo .* %{
+ execute-keys -client client0 x
+ # Wait for a redraw before reporting completion.
+ hook -once buffer NormalIdle .* %{
+ echo -to-file fifo
+ }
+ }
+ }
+ }
}
diff --git a/test/tools/git/blame-in-diff/script b/test/tools/git/blame-in-diff/script
index 0cee0ca7..4c371348 100644
--- a/test/tools/git/blame-in-diff/script
+++ b/test/tools/git/blame-in-diff/script
@@ -1,10 +1,3 @@
-ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [false] }'
-
-# We've jumped to the new version of line 2. Move to the old version so we
-# can annotate the old file.
-ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "k:git blame<ret>" ] }'
-ui_out -until-eval 'grep "draw_status" | grep -v "\[fifo\]"'
-
+mkfifo fifo 2>/dev/null
+cat fifo
# We should have jumped to the old version of line 2, assert on kak_selection.
-ui_in '{ "jsonrpc": "2.0", "method": "keys", "params": [ "x" ] }'
-ui_out -until '{ "jsonrpc": "2.0", "method": "refresh", "params": [false] }'