RegionsHighlighter::create_region() validates the highlighter type argument
but RegionsHighlighter::create_default_region() assumes it is correct,
segfaulting from dereferencing a null pointer if the given type isn't in
the highlighter registry HashMap.
@PJungkamp reported this in https://github.com/mawww/kakoune/issues/4959
with a simple recipe to reproduce:
:add-highlighter shared/test regions
:add-highlighter shared/test/ default-region invalid highlighter
:add-highlighter window/test ref test
Validate the type argument in RegionsHighlighter::create_default_region()
in the same way as RegionsHighlighter::create_region().
The current exponential behaviour does not seem that useful, it seems
more predictible that pressing `+` twice would end up with 3 copies
of the original selections instead of 4.
Fixes#4533
Commits e49c0fb04 (unmap: fail if the mapping is currently executing,
2023-05-14) 42be0057a (map: fail if key is currently executing,
2023-06-24) fixed potential use-after-free issues. By doing so,
it broke configurations that in practice have not triggered any
crashes [1] [2].
For example with,
set -remove global autocomplete insert
hook global InsertCompletionShow .* %{
map window insert <esc> <c-o>
}
hook global InsertCompletionHide .* %{
unmap window insert <esc> <c-o>
}
The execution of the <esc> mapping triggers InsertCompletionHide fails
at unmapping. This seems legit and I don't see an obvious alternative
way to write it (InsertIdle would not be correct though it would work
in practice).
Fix the regression by allowing map and unmap again while keeping the
mappings alive until they have finished executing.
Applying map/unmap immediately seems like the most obvious semantics.
Alternatively, we could apply them in between key presses.
[1]: <https://github.com/kak-lsp/kak-lsp/issues/689>
[2]: <https://github.com/alexherbo2/auto-pairs.kak/issues/60>
If during execution of a mapping, that same mapping is replaced,
there is undefined behavior because we destroy a mapping that we are
still iterating over.
I have been using this mapping inside my kakrc to re-source the kakrc.
map global user s %{:source "%val{config}/kakrc"<ret>} -docstring 'source "%val{config}/kakrc"'
Now <space>s happens to not trigger undefined behavior because the
mapping stays the same.
However it triggers an assertion added by Commit e49c0fb04 (unmap:
fail if the mapping is currently executing, 2023-05-14), specifically
the destructor of ScopedSetBool that guards mapping execution.
Fix these by banning map of a key that is executing, just like we
did for unmap.
Alternative solution: we could allow mapping (and even unmapping)
keys at any time and keep them alive by moving them into a trash can,
like we do for clients and others.
Normally page-down is sent as \033[6~ but some terminals
send a CSI u encoding for the page-down on the numpad. See
https://codeberg.org/dnkl/foot#keypad for example.
Treat them as the underlying key; we could add a modifier if anyone
cares about the distinction.
When a line only contains non-range atoms we can end-up accessing
past the end atom.
Add a test that shows the issue when run with valgrind, it is
unfortunately quite hard to trigger a crash because the invalidly
accessed byte usually leads to the correct code path being taken
(when != DisplayAtom::Range) so we have only 1 in 255 chance of
triggerring a crash.
Fixes#4927
In some cases such as with folding we can end-up with regions
not having any atoms to highlight which can trigger a crash as
we assume display buffers not to be empty
Fixes#4926
My terminal allows to map <c-[> and <esc> independently. I like
to use <c-[> as escape key so I have this mapping:
map global prompt <c-[> <esc>
Unfortunately, this is not equivalent to <esc>. Since mappings are
run with history disabled, <c-[> will not add the command to the
prompt history.
So disabling command history inside mappings is wrong in case the
command prompt was created before mapping execution. The behavior
should be: "a prompt that is both created and closed inside a
noninteractive context does not add to prompt history", where
"noninteractive" means inside a mapping, hook, command, execute-keys
or evaluate-commands.
Implement this behavior, it should better meet user expectations.
Scripts can always use "set-register" to add to history.
Here are my test cases:
1. Basic regression test (needs above mapping):
:nop should be added to history<c-[>
---
2. Create the prompt in a noninteractive context:
:exec %{:}
now we're back in the interactive context, so we can type:
nop should be added to history<ret>
---
3. To check if it works for nested prompts, first set up this mapping.
map global prompt <c-j> '<a-semicolon>:nop should NOT be added to history<ret>'
map global prompt <c-h> '<a-semicolon>:nop should be added to history first'
Then type
:nop should be added to history second<c-j><c-h><ret><ret>
the inner command run by <c-j> should not be added to history because
it only existed in a noninteractive context.
---
See also the discussion https://github.com/mawww/kakoune/pull/4692
We could automate the tests if we had a test setup that allowed
feeding interactive key input into Kakoune instead of using
"execute-commands". Some projects use tmux, or maybe we can mock
the terminal.
The commit after next will fix a bug where we wrongly disable prompt
history in some scenarios. The root cause is that life span of
"disable_history" does not model when we actually want to disable
history.
Let's rename the state variable to "noninteractive". It's set whenever
we are executing a hook, mapping or command.
Note that it's also active inside ":prompt"'s callback, which doesn't
play well with the new name :(
This switch makes show-matching fallback to the character preceeding
the cursor if the character under the cursor is not a matching
character, which should make show-matching more useful in insert mode.
Cache get fully invalidated whenever the regions change, so there
should be no risk of referencing a removed region, and this removes
one hash map lookup for every region in the displayed buffer range.
Range atoms should always appear in order, so we can iterate a single
time through the display lines and display atoms while applying
hightlighters to regions
When unmapping a key sequence that is currently executing, we continue
executing freed memory which can have weird effects. Let's instead
throw an error if that happens. In future we can support unmap in
this scenario.
Closes#4896
The current implementation only does this during regex operations,
but should be extensible to other operations that might take a long
time by regularly calling EventManager::handle_urgent_events().
Although Kakoune responds to modified mouse events, they show up in the
debug buffer corrupted. to_string() tests for equality on the mouse event
modifiers rather than testing just the relevant bits, so the modified
mouse events incorrectly fall through to the normal key handling.
Fix this and restructure to allow mouse events to be modifier-prefixed.
Signed-off-by: Chris Webb <chris@arachsys.com>
Change the initial <c-h>/<c-k> bindings to the recently freed-up
<a-u></a-U>.
Pros:
- easier to remember
- the redo binding is logical.
- works on legacy terminals, unlike <c-h>
Cons:
- It's less convenient to toggle between selection undo and redo
keys. I think this is okay since this scenario does not happen that
often in practice.
%exp{...} just expands its content the same way double quoted strings
do, but using a named expansion type makes it possible to use the
more quoting mechanism to avoid quoting hell.
When Kakoune's terminal is shown on my laptop monitor and I plug
in my external monitor, the terminal's workspace will move to that
external monitor. When this happens, Kakoune may segfault.
There are multiple resize events (SIGWINCH) in quick succession;
it crashes because we handle SIGWINCH during rendering.
The problem happens during execution of "TerminalUI::Screen::output"
(frame #18). When we receive SIGWINCH while writing to stdout, write(2)
fails with EAGAIN, prompting us to handle pending events (See ae001a1f9
(Run EventManager whenever writing to a file descriptor would block,
2022-05-10)). We update the screen size in check_resize() here:
#4 Kakoune::TerminalUI::check_resize (force=<optimized out>) at terminal_ui.cc:683
#5 Kakoune::TerminalUI::get_next_key () at terminal_ui.cc:719
#6 operator() (__closure=0x555555984198) at terminal_ui.cc:484
#7 std::__invoke_impl<void, Kakoune::TerminalUI::TerminalUI()::<lambda(Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode)>&, Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode> (__f=...) at /usr/include/c++/12.2.1/bits/invoke.h:61
#8 std::__invoke_r<void, Kakoune::TerminalUI::TerminalUI()::<lambda(Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode)>&, Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode> (__fn=...) at /usr/include/c++/12.2.1/bits/invoke.h:111
#9 std::_Function_handler<void(Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode), Kakoune::TerminalUI::TerminalUI()::<lambda(Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode)> >::_M_invoke(const std::_Any_data &, Kakoune::FDWatcher &, Kakoune::FdEvents &&, Kakoune::EventMode &&) (__functor=..., __args#0=..., __args#1=<optimized out>, __args#2=<optimized out>) at /usr/include/c++/12.2.1/bits/std_function.h:290
#10 std::function<void (Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode)>::operator()(Kakoune::FDWatcher&, Kakoune::FdEvents, Kakoune::EventMode) const (__args#2=<optimized out>, __args#1=<optimized out>, __args#0=...) at /usr/include/c++/12.2.1/bits/std_function.h:591
#11 Kakoune::FDWatcher::run (mode=Kakoune::EventMode::Urgent, events=<optimized out>) at event_manager.cc:28
#12 Kakoune::EventManager::handle_next_events (mode=mode@entry=Kakoune::EventMode::Urgent, sigmask=sigmask@entry=0x0, block=<optimized out>, block@entry=false) at event_manager.cc:143
#13 Kakoune::write (fd=1, data=...) at file.cc:273
#14 Kakoune::BufferedWriter<4096>::flush () at string.hh:236
#15 Kakoune::BufferedWriter<4096>::write (data="t file.hh:145
#16 Kakoune::TerminalUI::Screen::set_face (face=..., writer=...) at terminal_ui.cc:255
#17 operator() (line=..., __closure=<synthetic pointer>) at terminal_ui.cc:326
#18 Kakoune::TerminalUI::Screen::output (force=force@entry=true, synchronized=<optimized out>, writer=...) at terminal_ui.cc:402
#19 Kakoune::TerminalUI::redraw (force=force@entry=true) at terminal_ui.cc:571
#20 Kakoune::TerminalUI::refresh (force=<optimized out>) at terminal_ui.cc:592
#21 Kakoune::Client::redraw_ifn () at client.cc:282
#22 Kakoune::ClientManager::redraw_clients () at client_manager.cc:232
#23 Kakoune::run_server (session=..., server_init=..., client_init=..., init_buffer="fish-rust/src/ast.rs", init_coord=..., flags=Kakoune::ServerFlags::None, ui_type=Kakoune::UIType::Terminal,
debug_flags=<optimized out>, files=ArrayView<Kakoune::StringView> = {...}) at main.cc:893
#24 main (argc=<optimized out>, argv=<optimized out>) at main.cc:1243
Thereafter, "TerminalUI::Screen::output" resumes and crashes due to
a buffer overflow in "lines" which has been resized.
input_handler.cc:1476:16: error: alias template 'ConstArrayView' requires template arguments; argument deduction only allowed for class templates
insert(ConstArrayView{content});
^
input_handler.cc:1522:16: error: alias template 'ConstArrayView' requires template arguments; argument deduction only allowed for class templates
insert(ConstArrayView{str});
^
Whenever a new history node is committed after some undo steps, instead
of creating a new branch in the undo graph, we first append the inverse
modifications starting from the end of the undo list up to the current
position before adding the new node.
For example let's assume that the undo history is A-B-C, that a single undo
has been done (bringing us to state B) and that a new change D is committed.
Instead of creating a new branch starting at B, we add the inverse of C
(noted ^C) at the end, and D afterwards. This results in the undo history
A-B-C-^C-D. Since C-^C collapses to a null change, this is equivalent to
A-B-D but without having lost the C branch of the history.
If a new change is committed while no undo has been done, the new history
node is simply appended to the list, as was the case previously.
This results in a simplification of the user interaction, as two bindings
are now sufficient to walk the entire undo history, as opposed to needing
extra bindings to switch branches whenever they occur.
The <a-u> and <a-U> bindings are now free.
It also simplifies the implementation, as the graph traversal and
branching code are not needed anymore. The parent and child of a node are
now respectively the previous and the next elements in the list, so there
is no need to store their ID as part of the node.
Only the committing of an undo group is slightly more complex, as inverse
history nodes need to be added depending on the current position in the
undo list.
The following article was the initial motivation for this change:
https://github.com/zaboople/klonk/blob/master/TheGURQ.md
The previous code was assuming it was fine to push_next without
growing, which used to be the case with the previous implementation
because we always have poped the current thread that we try to push.
However now that we use a ring-buffer, m_next_begin == m_next_end can
either mean full, or empty. We solve this by assuming it means empty
and never allowing the buffer to become full, which means we need
to grow after pushing to next if we get full.
Fixes#4859
Handle begin/end paste directly in paste csi, manage paste buffer
out of get_char, filter Key::Invalid earlier.
get_next_key returning Key::Invalid means there was some input but
it could not be represented as a Key. An empty optional means there
was no input at all.
Text pasted into Kakoune's normal mode is interpreted as command
sequence, which is probably never what the user wants. Text
pasted during insert mode will be inserted fine but may trigger
auto-indentation hooks which is likely not what users want.
Bracketed paste is pair of escape codes sent by terminals that allow
applications to distinguish between pasted text and typed text.
Let's use this feature to always insert pasted text verbatim, skipping
keymap lookup and the InsertChar hook. In future, we could add a
dedicated Paste hook.
We need to make a decision on whether to paste before or after the
selection. I chose "before" because that's what I'm used to.
TerminalUI::set_on_key has
EventManager::instance().force_signal(0);
I'm not sure if we want the same for TerminalUI::set_on_paste?
I assume it doesn't matter because they are always called in tandem.
Closes#2465
If, for example, the buffer path now is a directory, MappedFile will
throw on construction. Using a try block to explicitely allow errors
fixes the issue.
Instead of potentially decoding for each thread, always decode as
its only slightly slower than finding next codepoint (which will
be necessary anyway) and pass the codepoint to each thread.
The command line "hook -group xyz " should get scope completions but
it actually gets hook completions because "xyz" is wrongly interpreted
as positional argument.
Fix this by using the parameters parser to compute positional
arguments.
Fixes#4840
Some plugins (*cough* kak-lsp) and help texts tend to have immensely long content
in a single line. This generates info boxes that span the entire terminal width.
This is made especially worse on widescreen monitors or at small text size.
This grants user control over how wide these boxes are.
I deliberately avoid pushing this change to `kak-lsp` because it's not the only
plugin that this could help--see the `hook` help text for an example of this
problem in vanilla Kakoune. I would also suggest that since this is a rendering
concern, it be handled by the terminal rendering logic.
We only grow when the ring buffer is full, which allows for a nice
simplification of the code.
Tell grow_ifn if we pushed in current or next so that we can
distinguish between filled by next or filled by current when
m_current == m_next_begin
Instead of two stacks growing from the two ends of a buffer, use
a ring buffer growing from the same mid spot.
This avoids the costly memory copy every step when we set next
threads as the current ones.
Erasing fully trimmed display atoms one by one means we have to
shift all the remaining ones every time. This is wasteful and we
can just erase all the fully trimmed atom in one go.
Fixes#4797
Previously it would result in a stray single-character selection at the
beginning of the input text.
For example:
[abcabc] -> split on 'a' -> [a][bc]a[bc]
or
[foobarfoobar] -> split on 'foo' -> [f]oo[bar]foo[bar]
Note that this behavior was not occuring if the input text was at the
beginning of the buffer
Commit 933e4a599 (Load buffer in command line order, 2022-12-06)
introduced a regression: the command
$ kak /boot/grub/grub.cfg
Fatal error: no such buffer '/boot/grub/grub.cfg'
quits with no indication of the underlying error.
Prior to 933e4a599, it would open the *scratch* buffer instead,
and show an error in the status line, pointing to the debug buffer,
which would contain:
error while opening file '/boot/grub/grub.cfg':
/boot/grub/grub.cfg: Permission denied
Let's fix this scenario by matching the old behavior.
Recent changes for selection-undo added an assertion that triggers
when a mouse-drag overlaps with an insert mode, because both events
record selection history. However this is actually fine. The one
that finishes last concludes the selection edition, while the other
one will be a nop.
The test could be simpler (i.e. not require sleeps) but I figured it
doesn't hurt add this since we don't have any comparable tests.
After buffer modification - in particular after deletion - adjacent
selection history entries may correspond to the same effective
selection when applied to the current buffer. This means that we
sometimes need to press <c-h> multiple times to make one visible
change. This is not what the user expects, so let's keep walking the
selection history until we hit an actual change.
Alternatively, we could minimize the selection history after buffer
changes but I think that would make the it worse after content
undo+redo.
Each selection undo operation is surrounded by pair of
begin_edition()/end_edition() calls.
The original reason for adding these was that in one of my preliminary
versions, a WinDisplay hook could break an undo chain, even if the
hook did not affect selections at all. This has since been fixed.
By surrounding the undo with begin_edition()/end_edition(), try to
ensure that any selection modification that happens in a WinDisplay
hook would not break the undo chain. Essentially this means that,
after using <c-h> to undo a buffer change, this was meant to
make sure that <c-k> could redo that buffer change.
However, it turns out this actually doesn't work. The attached test
case triggers an assertion. As described in the first paragraph,
the only real-world motivation for this is gone, so let's simplify
the behavior.
The assertion fix means that we can test the next commit better.
It turns out that neither <a-f> or <a-t> make sense when run at the
beginning of the buffer. When I first created the check, I thought
that <a-f> made sense if the character under the cursor was the
character being searched for. I was wrong, <a-f> should always go
at least one character backwards.
This commit fixes a bug in Buffer::advance where it would first access
m_lines[-1], and then check whether or not that access would have
segfaulted. This commit moves the check to before the segfault would
occur.
is run as
> ./kak -f '2oK.k<c-n><ret><c-n>' /dev/null
The crash occurs because when <c-n> is pressed for the second time,
it attempts to use m_completions from the first press of <c-n>.
This only happens when kakoune is run with -f, because when this is done
interactively, there is a client, which means that m_completions gets
reset. This removes the check that causes that difference.
I am not *completely* sure that this is the best way to solve the
problem, since I am not completely sure why that check was put there
in the first place.
Using BufferIterator adds overhead, but we know that DisplayAtoms
cannot span multiple buffer lines and hence we can directly iterate
using char pointers.
Each draft context gets its own private copy of the selections.
Any selection changes will be thrown away when the draft context
is disposed. Since selection-undo is only supported as top-level
command, it can never be used inside a draft context, so let's stop
recording it.
No functional change.
Calculating the length of an atom means we need to decode every
codepoint and compute its column width. This can prove quite expensive
in trim_from as we can have full buffer lines, so on buffer with long
lines we might have to go through megabytes of undisplayed data.
Pass the first buffer on the the command line explicitely to client
creation. This ensure the buffer list matches the command line, which
makes buffer-next/buffer-previous a bit more useful.
Fixes#2705
With overlapping selections, pasting after breaks assumption of
SelectionList::for_each as our changes are no longer happening in
increasing locations.
We hence cannot rely on the ForwardChangeTracker in that case and
have to rely on the more general (and more costly) ranges update logic.
This interacts poorly with paste linewise pastes and we try to preserve
the current behaviour by tracking the last paste position.
Overall, this change really begs for overlapping selections to be
removed, but we will fix them like that for now.
Fixes#4779
Comparing iterators between buffers should never happen, and the
only place we did was with default constructed BufferIterator which
we replace by casting the iterator to bool.
This should improve performance on iterator heavy code.
When the file system runs out of space, "write -force" will fail but
doesn't print "No space left on device".
Let's fix this by including such an underlying error. Untested.
Backstory: I alias "w" to a command that runs "write -force %arg{@}".
so I can overwrite files that already exist outside the editor (I
should probably get used to the new behavior).
Commit 69053d962 (Use menu behavior when completing change-directory,
2022-07-19) made ":cd dir/" actually run ":cd dir/first-subdir",
which can be surprising.
This is usually irrelevant because you rarely type the trailing slash.
However it does happen after correcting an error with `<backspace>`
and friends. For for example,
:cd d<tab>/f<backspace>
results in
:cd dir/
We should probably fix user expectations here. Do this by adding "dir/"
as valid completion. This requires us to allow empty candidates in
"RankedMatch" but there's no harm in that. This means we need to
filter out empty completions from shell-script-candidates elsewhere.
Alternative fix: we could revert 69053d962. This would remove the
convenient menu behavior but that wouldn't be a huge deal.
Fixes#4775
Mapping upper case keys is legitimate, for exampled so that they behave
the same as a lower case mapping. The current rejection of those mappings
is a misguided attempt to prevent mapping *to* upper case keys as those
will never get triggered.
Closes#4769
After a failed
:write file-that-already-exists
a user might want to type ":<up> -f<ret>" to force-overwrite.
This doesn't work because :write's switches must precede the filename.
It's dual :edit does not have this restriction.
Some commands require switches to precede positional arguments for a
good reason; for example because positional arguments might start with
"-" (like ":echo 1 - 1").
There seems to be no reason for the :write restriction, so remove
it. Same for :enter-user-mode.
Thanks to alexherbo2 for reporting.
Running %sYeti<ret>casdf on file
[example.journal.txt](https://github.com/mawww/kakoune/issues/4685#issuecomment-1193243588)
can cause noticeable lag. This is because we insert text at 6000
selections, which means we need to update highlighters in those lines.
The runtime for updating range highlighters is quadratic in the
number of selections: for each selection, we call on_new_range(),
which calls add_matches(), which calls std::rotate(), which needs
needs linear time.
Fix the quadratic runtime by calling std::inplace_merge() once instead
of repeatedly calling std::rotate(). This is works because ranges
are already sorted.
I used this script to benchmark the improvements.
(In hindsight I could have just used "-ui json" instead of tmux).
#!/bin/sh
set -ex
N=${1:-100}
kak=${2:-./kak.opt}
for i in $(seq "$N")
do
echo -n "\
2022-02-06 * Earth
expense:electronics:audio 116.7 USD
liability:card -116.7 USD
2022-02-06 * Blue Yeti USB Microphone
expense:electronics:audio 116.7 USD
liability:card -116.7 USD
"
done > big-journal.ledger
echo > .empty-tmux.conf 'set -sg escape-time 5'
test_tmux() {
tmux -S .tmux-socket -f .empty-tmux.conf "$@"
}
test_tmux new-session -d "$kak" big-journal.ledger
test_tmux send-keys '%sYeti' Enter c 1234567890
sleep .2
test_tmux send-keys Escape
while ! test_tmux capture-pane -p | grep 123
do
sleep .1
done
test_tmux send-keys ':wq' Enter
while test_tmux ls
do
sleep .1
done
rm -f .tmux-socket .empty-tmux.conf
This script's runtime used to grow super-linearly but now it grows
linearly:
kak.old kak.new
N=10000 1.142 0.897
N=20000 2.879 1.400
Detailed results:
$ hyperfine -w 1 './bench.sh 10000 ./kak.opt.'{old,new}
Benchmark 1: ./bench.sh 10000 ./kak.opt.old
Time (mean ± σ): 1.142 s ± 0.072 s [User: 0.252 s, System: 0.059 s]
Range (min … max): 1.060 s … 1.242 s 10 runs
Benchmark 2: ./bench.sh 10000 ./kak.opt.new
Time (mean ± σ): 897.2 ms ± 19.3 ms [User: 241.6 ms, System: 57.4 ms]
Range (min … max): 853.9 ms … 923.6 ms 10 runs
Summary
'./bench.sh 10000 ./kak.opt.new' ran
1.27 ± 0.09 times faster than './bench.sh 10000 ./kak.opt.old'
$ hyperfine -w 1 './bench.sh 20000 ./kak.opt.'{old,new}
Benchmark 1: ./bench.sh 20000 ./kak.opt.old
Time (mean ± σ): 2.879 s ± 0.065 s [User: 0.553 s, System: 0.126 s]
Range (min … max): 2.768 s … 2.963 s 10 runs
Benchmark 2: ./bench.sh 20000 ./kak.opt.new
Time (mean ± σ): 1.400 s ± 0.018 s [User: 0.428 s, System: 0.083 s]
Range (min … max): 1.374 s … 1.429 s 10 runs
Summary
'./bench.sh 20000 ./kak.opt.new' ran
2.06 ± 0.05 times faster than '../repro.sh 20000 ./kak.opt.old'
LineRangeSet::add_range() calls Vector::erase() in a loop over the
same vector. This could cause performance problems when there are many
selections. Fix this by only calling Vector::erase() once. I didn't
measure anything because my benchmark is dominated by another issue
(see next commit).
LineRangeSet::remove_range() also has a suspicious call to erase()
but that one is only used in test code, so it doesn't matter.
From the issue:
> It often happens to me that I carefully craft a selection with multiple
> cursors, ready to make changes elegantly, only to completely mess it
> up by pressing a wrong key (by merging the cursors for example). Being
> able to undo the last selection change (even if only until the previous
> buffer change) would make this much less painful.
Fix this by recording selection changes and allowing simple linear
undo/redo of selection changes.
The preliminary key bindings are <c-h> and <c-k>.
Here are some other vacant normal mode keys I considered
X Y
<backspace> <minus>
# ^ =
<plus> '
unfortunately none of them is super convenient to type. Maybe we
can kick out some other normal mode command?
---
This feature has some overlap with the jump list (<c-o>/<c-i>) and
with undo (u) but each of the three features have their moment.
Currently there's no special integration with either peer feature;
the three histories are completely independent. In future we might
want to synchronize them so we can implement Sublime Text's "Soft
undo" feature.
Note that it is possible to restore selections that predate a buffer
modification. Depending on the buffer modification, the selections
might look different of course. (When trying to apply an old buffer's
selection to the new buffer, Kakoune computes a diff of the buffers
and updates the selection accordingly. This works quite well for
many practical examples.)
This makes us record the full history of all selections for each
client. This seems wasteful, we could set a limit. I don't expect
excessive memory usage in practice (we also keep the full history of
buffer changes) but I could be wrong.
Closes#898
To be able to undo selection changes, we want to record selections
from all commands that modify selections. Each such command will get
its own private copy of the selections object.
This copy will live until the command is finished executing.
All child commands that are run while the command is executing,
will also use the same copy, because to the user it's all just one
selection change anyway.
Add an RAII object in all places where we might modify selections.
The next commit will use this to create the private selections copy
in the constructor (if there is none) and remove redundant history
items in the destructor.
We could avoid the RAII object in some places but that seems worse.
For lifetimes that don't correspond to a lexical scope, we use a
std::unique_ptr. For lambdas that require conversion to std::function,
we use std::shared_ptr because we need something that's copyable.
The next commit changes the selections to a history of
selections. Today we directly access the selections data member. Let's
instead use an accessor method, to reduce the number of changes in
the next commit.
clang/clangd complain about the new HashSet type:
hash_map.cc:98:20: warning: braces around scalar initializer [-Wbraced-scalar-init]
set.insert({10});
^~~~
The argument to HashSet<int>::insert is just an int, so we don't
need braces. Only an actual HashMap would need braces to construct
a HashItem object.
When passing a filename parameter to "write", the -force parameter
allows overwriting an existing file.
The "write!" variant (which allows writing files where the current
user does not have write permissions) already implies -force.
All other variants (like write-quit or write-all) do not take a
file parameter.
Hence -force is relevant only for "write". Let's hide it from the
autoinfo of the other commands.
It's difficult to avoid duplication when constructing the constexpr
SwitchMap because String is not constexpr-enabled. Today, all our
SwitchMap objects are known at compile time, so we could make SwitchMap
use StringView to work around this. In future we might want to allow
adding switches at runtime, which would need String again to avoid
lifetime issues.
Instead of storing regexes in each regions, move them to the core
highlighter in a hash map so that shared regexes between different
regions are only applied once per update instead of once per region
Also change iteration logic to apply all regex together to each
changed lines to improve memory locality on big buffers.
For the big_markdown.md file described in #4685 this reduces
initial display time from 3.55s to 2.41s on my machine.
When I wrote this line I wanted to avoid adding the array size but
I didn't know about make_array().
I had unsuccessfully tried some alternatives, for example
Array{"a", "b", "c"}
which doesn't work because we need StringView (c.f. git blame on
this line)
also
Array<StringView>{"a", "b", "c"}
doesn't work because it's missing a template argument.
This makes the function easier to find for newcomers because
to_string() is the obvious name. It enables format() to do the
conversion automatically which seems like good idea (since there is
no other obvious representation).
Of course this change makes it a bit harder to grep but that's not
a problem with clang tooling.
We need to cast the function in one place when calling transform()
but that's acceptable.
Commit d470bd2cc (Make numeric registers setable, 2017-02-14) removed
the user-provided StaticRegister::operator= in favor of a set()
member function, so this comment is no longer valid.
Recently, switch completion were given the menu behavior.
Unfortunately this breaks cases like
:echo -- -mark<ret>
where the hypothetical user wanted to actually display "-mark", not
"-markup".
Simply bail if there is a double-dash. This is not fully correct,
for example it wrongly disables switch completion on
echo -to-file -- -
but that's minor, we can fix it later.
In future, we should reuse the ParametersParser when computing completions,
which will obsolete this workaround.
Commit 217dd6a1d (Disable history when executing maps, 2015-11-10)
made it so with
map global normal X %{:echo 123<ret>}
X does not add to prompt history (%reg{:}).
Unfortunately this behavior was not extended to mappings in the "user"
keymap, nor to mappings in custom user modes.
In my experience, not adding to history is almost always the expected
behavior for mappings. Most users achieve this by adding a leading space:
map global user X %{: echo 123<ret>}
but that's awkward. We should have good defaults (no nnoremap)
and map should work the same way across all modes.
Fix this by also disabling history when executing user mappings. This
is a breaking change but I think it only breaks hypothetical scenarios.
I found some uses where user mappings add to history but none of them
looks intentional.
f702a641d1/.config/kak/kakrc (L169)604ef1c1c2/kakrc (L96)d22e7d6f68/kak/kakrc (L71)https://grep.app/search?q=map%20%28global%7Cbuffer%7Cwindow%29%20user%20.%2A%5B%21%3A/%5D%5B%5E%20%5D.%2A%3Cret%3E®exp=true
This allows to select completions without pressing Tab.
There are two different obvious ways to add the menu bit.
1. When creating the "Completions" object, pass the
Completions::Flags::Menu parameter.
2. If there is a completer function like "complete_scope", wrap
it, e.g. "menu(complete_scope)".
I have settled on always using 2 if there is a completer function
and 1 otherwise.
The advantage of 2 over 1 is that it allows to use the completer
function in a context where we don't want the menu behavior
(e.g. "complete-command").
---
Now the only* completion type where we usually don't use menu behavior
is file completion. Unfortunately, menu behavior has poor interaction
with directories' trailing slashes. Consider this (contrived) example:
define-command ls -docstring "list directory contents" -params .. %{
echo -- %sh{ls "$@"}
}
complete-command -menu ls file
Run ":ls kakoun<ret>". The prompt expands to ":ls kakoune/"
before executing. Next, run ":<c-p>". This recalls ":ls kakoune/"
and immediately selects the first completion, so on validation,
the command will be ":ls kakoune/colors/", which is weird.
[*] Also, expansions like %val{bufname} also don't use menu
behavior. It wouldn't add value since validation doesn't add a
closing delimiter. I have an experimental patch that adds closing
delimiters automatically but I'm not sure if that's the right
direction.
This makes "cd<space><ret>" change to the first completion,
not to $HOME. This might be surprising but it should make sense.
I don't have a concrete motivation but this should save a Tab press
in some scenarios.
We allow to abbreviate scopes ("set g" means the same thing as "set
global") but that feature is a bit obscure. Users might figure out the
menu completion behavior faster, so let's maybe use it here as well?
Not really attached to this but it enables the next commit to use
menu() for completing scopes.
This refactoring is possible because we always have
params[token_to_complete].length() == pos_in_token
---
Instead of three separate functions, I originally tried to add
template arguments to complete_scope(). That worked fine with g++
12.1 but clang 14.0 complained when wrapping a menu() around a
complete_scope() that relied on defaulted template arguments:
commands.cc:1087:20: error: no matching function for call to 'menu'
make_completer(menu(complete_scope), menu(complete_hooks), complete_nothing,
^~~~
commands.cc:116:6: note: candidate template ignored: couldn't infer template argument 'Completer'
auto menu(Completer completer)
^
On a command prompt like
"set-option -remove buffer aligntab "
we fail to show the aligntab-specific info . Fix this by skipping a
leading -remove, just like we skip -add.
Add an explicit specialization of contains() because otherwise I'd
need to write something like
contains(Array{"-add", "remove"}, param)
This gives the "prompt" command the same "-menu" switch as
"complete-command" and "define-command". I don't think anyone has
asked for it but it seems like a good idea?
Both "define-command" and "prompt" use the same logic, so share
it. This will make it easy to implement "prompt -menu".
This reveals a problem with PromptCompleterAdapter: when converting
it to std::function and then to bool, it always evaluates to true
because it has an operator(). However, it should evaluate to false
if the adaptee holds no valid function (e.g. is a default-constructed
std::function). Otherwise we try to call a non-existant function. Tweak
PromptCompleterAdapter to work for empty inputs.
If I type
:echo -mx
I get no completions, even when I move the cursor on the x.
If I replace the x with a k, I get a completion "-markup".
The odd thing is that if I accept the completion while the cursor is
on the k, then the commandline will be
:echo markupk
Evidently, the characters under cursor (x/k) influence the completion
(actually all letters right of the cursor do), but they are not
incorporated in the final result, which is weird.
I think there are two consistent behaviors:
1. Compute completions only from characters left of the cursor. We already
do this in insert mode completion, and when completing the command name
in a prompt.
2. Compute completions from the whole token, and when accepting a completion,
replace the whole token.
Most readline-style completion systems out there implement 1. A
notable exception is fish's tab-completion. I think we should stick
to 1 because it's more predictable and familiar. Do that.
This allows us to get rid of a special case for completing command
names, because the new behavior subsumes it.
In fact, I think this would allow us to get rid of most "pos_in_token"
or "cursor_pos" completer parameters. I believe the only place where we
it's actually different from the end of the query is in "shell-script"
completers, where we expose "$kak_pos_in_token". I think we can still
remove that parameter and just cut the commandline at the cursor
position before passing it to a "shell-script" completer. Then we
also don't need "$kak_token_to_complete" (but we can still keep
expose them for backwards compatibility).
Just like in the parent commit, this requires us to use a non-owning
type. Technically, these strings all benefit from SSO, so there is
no lifetime issue, but we can't deduce that from the types.
I guess we could use InplaceString just as well.
This means that typing
:add-highlighter g c 80
results in
:add-highlighter global/ column 80
Paths for add-highlighter do not get the menu behavior because we
want to be able to type "global/foo" even if "global/foobar" exists.