Diff against known state and insert/erase relevant lines.
Erase everything first to avoid insertion invalidating lines that
get out of the terminal at bottom.
This should reduce flicker, by avoiding transient states where
info/menu windows are not displayed, and paves the way for proper
diffing of the screen.
Log error to debug buffer and Change the 'waiting for shell' face
to 'Error'.
Update the 'waiting for shell' message when the shell has exited
but Kakoune is still waiting on stdin/stdout/stderr to be closed.
Those fifos are accessible during %sh{...} blocks, the command fifo
executes commands written to it once the write end side is closed
(multiple open/write/close sequences are supported), the response
fifo is a simple helper fifo available to write response back to
the shell process
An example use of this feature is to request some list options
content from without being limited by the environment size:
```
%sh{
echo "echo -to-file $kak_response_fifo -quoting shell -- %opt{some_list}" > $kak_command_fifo
eval "set -- $(cat $kak_response_fifo)"
}
```
If a %sh{} script refers to any variables multiple times they are all multiply
included in the environment. Example: if a %sh{} invocation refers to
${kak_buffile} 5 times, the environment will have "kak_buffile=..." repeated 5
times and so on. This repetition happens for each multiply used variable that
is passed into the environment.
The variable should, of course, be only passed into the environment once. This
commit should fix this issue.
The real technical limit is with lines bigger than 2 GiB and buffers
with more than 2 Gi lines, refactor buffer loading to make it possible
to load those files.
Fix an overflow with the hash_data function at the same time
This fixes an issue where completion would still be provided after
the closing character of a token, which could then get frustrating
combined with auto-insertion of completions.
For example, inserting `%{<newline>}` for a command-completed token
(such as the commands for a hook) would still trigger completion right
after the `}` and that completion would get auto-inserted **replacing**
that closing `}`.
Do not use a shared kakoune/ directory for all users to avoid the
complexity of having to set the sticky bit on that dir, resolve the
session directoy only once by using a static variable and an
immediately evaluated lambda.
This fixes an annoyance whenver using `su` and having Kakoune refuse
to start due to XDG_RUNTIME_DIR still being set.
For historical reasons, mouse events represent keyboard modifiers as a bitfield,
but keyboard events represent modifiers as a bitfield-plus-one. For example, a
mouse event with an Alt modifier will use the value 4, but a keyboard event will
use the value 5.
Previously, I refactored the parse_mask() helper to do the subtraction itself,
instead of requiring the caller to do it. This made keyboard-event decoding much
cleaner, but I didn't realise it broke mouse-event decoding. Now the subtraction
is done only for keyboard events.
Fixes#4176.
In some cases, it may be difficult to easily spot the area out of the buffer
(bad color scheme, small font, superimposed windows).
This patch adds two ncurses ui_options to bypass this problem:
- `ncurses_padding_char`, to configure the padding character,
- `ncurses_padding_fill`, to indicate whether to fill the padding line
(or to display a single character).
The default config is the legacy one (a single "~").
- Add the Narrow No-Break SPace (0x202F, NNBSP) to the list of handled
spaces in the show-whitespace highlighter.
- Do not add an aditional option, just handle it like NBSP, with the same highlight character.
Quote by wrapping in quotes if we are replacing the whole token,
using backspaces if the completion only adds to it.
This ensure that the inserted completion will be correctly parsed
once validated.
Fixes#4121
Kakoune now knows about all the keypad keys listed in:
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keyshttps://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-VT220-Style-Function-Keys
The VT220-style encodings are used to for modified numeric keys when NumLock is
off. For example, consider the 8/Up key:
| Modifiers | Sequence | Notes |
|-----------------|-------------|-------------------|
| Unmodified | CSI A | Ordinary up arrow |
| Shift | SS3 2 x | Shift-8 |
| NumLock | 8 | Ordinary 8 |
| Shift + NumLock | CSI 1 ; 2 A | Shift-Up |
Note that even though the terminal distinguishes between keypad and regular keys,
Kakoune maps keypad keys onto regular keys - keypad Enter is still <ret>, it
just supports more modifiers than the regular Enter key.
It's useful for parsing modifier masks in all kinds of sequences, not just CSI
sequences. Also, since the modifier mask always has "1" as "no modifiers",
do the subtraction inside parse_mask() instead of when calling it.
This avoids 100% CPU usage when we have pending fifo input while running
a shell process, as we will not end-up busy looping in pselect but not
reading the available data due to being only processing urgent events.
When a replaced buffer range atom was starting exactly at the
location we wanted to split onto the code would split *after*
that atom instead of before.
Fixes#4052
This avoids an issue when using `su` and running Kakoune which creates
a session directory owned by root and prevents the user from creating
more sessions.
Different terminals send different codes to indicate backspace, usually one of
\x08 or \x7f, so Kakoune blindly treated both as backspace. However, a given
terminal is only likely to use one of those, and mnemonic control codes like
<c-h> are a precious resource so we should endeavour to keep backspace and
<c-h> separate when we can. Luckily, termios tells us what code our terminal is
currently using, and Kakoune already reads the information at startup, so we can
just use that information.
Thanks to @krobelus for figuring out the C++ syntax required.
Fixes#3863.
Add that libexec directory to the PATH instead of the current kak
binary directory to avoid impacting other commands.
The libexec directory currently only contains a symlink back to
the Kakoune binary.
Various paths can run arbitrary commands (callbacks, hooks) which
could lead to the InputMode being popped off the mode stack, but
contrarily to the on_key method, we had no guarantees to be kept
alive.
Add a keep_alive RefPtr to this to ensure the mode survives till
the end.
Fixes#3915
When doing line completion, we previously used to not complete the line
if it had different indent to the potential completion.
This commit changes the behaviour to ignore indentation when completing lines.
Unfortunately this breaks some pretty useful use cases, such as inserting a
command ending with a new-line (as it now leads to an addtional command being
auto-completed on validation)
This reverts commit aab0be529f.
When pasting many words with <a-p> we can end-up with a huge
concatenated word and many selections, the previous code ended
up iterating from each selection cursor to that word start and
end to find the word under the cursor.
This could lead to performance issue as each selection would
trigger iteration on that huge word. This is unnecessary as
word completion has a word length limit, so we now take it into
account to avoid iterating to far from the cursor position.
The previous code was advancing from the general insertion point
for all selection, instead of iterating only once from insertion
point until the end of inserted text.
0 means stdin was closed, this is quite unexpected as we would usually
get a SIGHUP, but it looks like in some rare case this happens and
it leads to an infinite loop trying to handle stdin events (as it
will always be readable from now on).
Fixes#3557
Previously, Kakoune only handled ctrl-codes less than 27, representing them as
lower-case ASCII codes. For regular keys like <c-a>, that worked fine. However,
NUL became the unorthodox <c-`> and other ctrl-symbols (<c-\>, <c-]>, <c-_>)
weren't supported at all.
Now NUL is rendered as the more comfortable <c-space>, and the other ctrl-symbol
codes are properly decoded.
Fixes#2553.
`-atomic` becomes `-method replace` and `-method overwrite` is now
supported explicitely instead of only available through the
writemethod option.
Fixes#3827
Giving an explicit register uses its content for the default value
to use if the user does not enter anything. This enables:
`set-register a %{commands}; execute-keys '"a:<ret>'`
`set-register a %{shell script}; execute-keys '"a|<ret>'`
...
This provides a nice way to avoid the need to escape keys to use
those normal mode commands.
Fixes#3825
This avoids a frustrating behaviour where Kakoune autoinserts the first
command name when hitting <space> after a ; in a command line. It also
fixes the empty prompt case that was auto-completed instead of executing
the default command.
We try to detect when compiling under Cygwin, so we can set the
_XOPEN_SOURCE define which lets us use wcwidth(). We look for the
string "CYGWIN" in the uname, which looks like:
CYGWIN_NT-10.0
MSYS2 is also based on Cygwin, so all of the above should still work.
However, we detect Cygwin by looking for the string "CYGWIN" in the
uname. In MSYS2, the uname looks like:
MSYS_NT-10.0-18362
This patch looks for the string "_NT" instead of the string "CYGWIN"
in the uname, since it's common to both environments. This fixes a
compilation error on MSYS2.
The menu flag signifies that only the completions are valid arguments,
hence it makes sense to auto insert the best one on space.
Because full match is always considered the best match in completion
ranking, this should always have a reasonable behaviour.
This makes it harder to enter a hidden command, but completion can
always be disabled via <c-o> or by quoting in those rare cases.
Because no flags were set for regex matching, the regex engine was
assuming that the subject string past-the-end matched a end-of-line.
As the subject string already ended with a \n character, the regex
engine processing of the "past-the-end" position would match '^$'
as ^ matched past the existing \n and $ matched the assumed
end-of-line.
Fixes#3799
Previously we would just bypass that hook making it impossible to
act on the inserted text when triggering an explicit completion after
inserting text from the previous completer.
This one has been a long time coming, I am still concerned this could
impact performance a lot. This hook does *not* trigger for capture
registers (0-9) or any other dynamic registers (that are not writable).
Fixes#859
ncurses_ui.cc:759:59: error: non-constant-expression cannot be narrowed from type 'unsigned int' to 'Key::MouseButton' in initializer list [-Wc++11-narrowing]
return mouse_button(mod, Key::MouseButton{code}, coord, c == 'm');
^~~~
Only include the value for int/str/bool options, for the rest just
write '<option name>=...'.
This should reduce the cost of some patterns such as repeatedly adding
a value inside a list option.
It seems very unlikely that the actual value would be matched by
a hook regex string for non primitive types.
Similarly to the <semicolon> key, make it easier to write
`:execute-keys` commands by replacing <percent> with `%`.
Highlighters can keep escaping the sign when regular expressions are
not quoted, but built-in scripts that use `%` as an editing primitive
have been modified to use the named key, for clarity.
Ranges specified with a +<length> were inconsistent, with +0 meaning
an empty range, while +1 meant a two character long range (first character
+ the following one). Change that to mean a single character.
Fixes#3479
Fixes#3489
When there are multiple empty lines between a paragraph and the cursor
(C in the example below), <a-[>p skips over one of them. Prevent the
check for the extra newline from going out of bounds.
```
a paragraph
C after <a-[>p, the first two lines will be selected
```
A command line argument like +line[:column] can be used to specify a
target line and column for the first file.
This did not work when connecting to a session, because the client
opens its file parameter with `-e "edit file1; edit file2"` which is
executed after the initial buffer position is set. Work around this by
passing the position to the first file and avoid moving the cursor
in unrelated files.
Reproduce:
kak -s foo
kak -c foo +4:11 README.asciidoc
The Ubuntu Disco distribution comes with `g++` v8 installed by default,
which is not able to deduce the return type of a particular call to
`transform()`.
This commit explicitly declares the return type to mitigate that
problem, and allow the file to compile.
Fixes#3410
Completion candidates are currently escaped with a backslash `\`
character, which leads to ugly interactive commands on the prompt,
especially when they contain space characters.
This commit makes completion candidates be escaped by simple quoting.
Examples:
candidate\ with\ spaces
\%opt{foo}
\"dquote
\'quote
become:
'candidate with spaces'
'%opt{foo}'
'"dquote'
'''quote'
The prompt and autocomplete normally wait for `idle_timeout` before showing
suggestions, however commands like `g`, `v`, or the lead-key show Clippy
instantly.
This fixes the issue by making `on_next_key_with_autoinfo()` wait for
`idle_timeout` before displaying suggestions.
Fixesmawww/kakoune#3365Fixesmawww/kakoune#2066
It seems that when -atomic was implemented for `:write`, the usage
strings were not updated to reflect that a new flag was available.
The `write-all` command didn't benefit from the implementation of
the new flag despite also writing files - this commit fixes that.
The first attempt at a bug fix for @ symbols in selection buffer names
worked, but it was very inefficient. In particular, it allocated three
different vectors, and we really only needed the correct elements.
Manipulating iterators to give us the right slices of the existing
vector is far more efficient.
By reversing the original content and taking the last two, we're able to
get the number of selections and main selection without too much hassle.
The buffer name is everything from the start of the content to the
selection count. This gets us through with only one vector allocation.
Credit to @mawww for the optimization idea and for fixing my types.
The selection descriptions use the format
`<buffer>@<timestamp>@<main_index>`. This fails when file paths have `@`
symbols in them: the parser splits on `@` symbols and finds more values
than it expects.
We here modify the behavior to require *at least* two @ symbols, using
the last two for `<timestamp>` and `<main_index>` and leaving the
remaining text for the <buffer>. This should work for any number of `@`
symbols, since `<timestamp>` and `<main_index>` are numbers and should
never contain `@` symbols.
The description of startup_info_version in the manual says "only messages
relating to a Kakoune version greater than this value will be displayed,"
but showed messages relating to the version equal to that value.
This change aligns the code with the manual and makes a workaround that set
startup_info_version next to the original version (ex. 20200117) unnecessary.
Signed-off-by: Masanori Ogino <masanori.ogino@gmail.com>
When a region calls the regex highlighter, it is incorrect to share
the regex cache as it means we can get matches that span multiple
regions.
Fixes#3041
Do not access Buffer::m_changes to find the inserted range, return
it directly from Buffer::insert and Buffer::replace. This fixes a
wrong behaviour where replacing at eof would lose the selected end
of line (as the implementation does not actually replace that end
of line)
Rxvt emits `\E[23$` and `\E[24$` for `F21` and `F22` (alias `s-F11` and
`s-F12` provided that `ncurses_shift_function_key` is set to `10`),
respectively.
When compiled on Mac with `clang`, the following error occurs at
compile-time:
```
./diff.hh:56:28: error: no member named 'min' in namespace 'std'
const int max_D = std::min((M + N + 1) / 2 + 1, cost_limit);
~~~~~^
```
When compiling the code with `-Wp,-D_GLIBCXX_ASSERTIONS`, the process
gets aborted, likely because iterators to standard containers are
not obtained in a safe way.
Fixes#3226.
This is tricky to fix better than that as tabs make text length
dependent on where it will get displayed and what preceedes it.
Also fix an issue with empty info title
Fixes#2237
Optmize the code to avoid allocating like crazy, unify various
info style rendering, crop content and display markers that there
is more text remaining.
Fixes#2257
We can get this signal while suspending if a parent process (say
git-commit) has already put us in the background. We still need
to reset the termios state to exit raw input mode and make the shell
usable.
Fixes#3069
The `json_ui.cc` file contained both data-parsing and UI-related
code. This commit moves the JSON parsing code to its own `json.cc`
file, to separate concerns, make compilation faster when changes are
made to either UI or parsing code, and make the parsing code more
accessible to fuzzers.
The signature of the following function:
```
auto parse_json(StringView json);
```
was changed to:
```
JsonResult parse_json(StringView json);
```
to avoid `auto` deduction issues at compile-time.
-verbatim will disable argument parsing in evaluate-commands, making
it possible to forward a single command to a different context without
triggering a reparsing of the arguments.
Fixes -try-client support in grep.kak
Closes#3153
This commit allows using the <semicolon> expansion in commands, instead
of `\;`.
It makes commands look more elegant, and prevents new-comers from
falling into the trap of using <a-;> without escaping the semicolon.
Pass an ArrayView<DisplayAtom> instead of a DisplayLine& so that
the newly common case of passing a single atom does not require
constructing a Vector.
When trimming indent, the last line, if only containing
whitespaces does not need to match the indent, so that
this indentation style works:
-docstring %{
indented string
}
std::function is not necessary when we just want to pass a type
erased callback that does not need to own its target. FunctionRef
provides that functionality for a much lower compile time cost.
Whenever a tool spawns the editor (e.g. Git), suspending it with ^Z is not
enough to be sent back to the calling shell - another ^Z is necessary.
Fixes#3061
The UI now can send a 'Scroll' key, whose value is the scrolling
amount encoded as a signed integer. This replaces the MouseWheelUp
and MouseWheelDown keys.
The NCursesUI now has a ncurses_wheel_scroll_amount ui_option that
controls that amount, it can be negative to swap scrolling direction.
Fixes#3045
First try to break at a whitespace, if that fails (likely because
that last WORD is too long for the wrapping width), then try to
wrap at a 'word' boundary (on a non alphanumeric character).
Fixes#3048
Falls back on old mechanism if `XDG_RUNTIME_DIR` is not set.
The ability to specify a session as "<user>/<name>" was removed, since
it isn't possible to compute the value of `XDG_RUNTIME_DIR` for another
user, we wouldn't have access to it if we could, and it would be awkward
to support this feature only when `XDG_RUNTIME_DIR` is unset. Also,
`rename-session` did not work when another user's session was specified.
Closes#3019
This commit implements formatting behaviour when the first character of a
docstring is a newline. In that case, the exact indentation level of the
next line will be removed from that line and all subsequent non-empty lines.
An error will be returned if a subsequent non-empty line does not have the
same indentation level.
The docstrings are always trimmed (surrounding whitespaces) whether the
first character is a newline or not, as was the case prior to this commit.
Example: the following declaration
```
define-command test -docstring %{
test: do something
Nothing really.
More
indented
lines.
} nop
```
would be rendered as
```
test: do something
Nothing really.
More
indented
lines.
```
Related to #2405
file.cc:390:21: error: use of undeclared identifier 'rename'; did you mean 'devname'?
if (replace and rename(temp_filename, zfilename) != 0)
^~~~~~
devname
/usr/include/stdlib.h:277:7: note: 'devname' declared here
char *devname(__dev_t, __mode_t);
^
file.cc:390:28: error: cannot initialize a parameter of type '__dev_t' (aka 'unsigned long') with an lvalue of type 'char [1024]'
if (replace and rename(temp_filename, zfilename) != 0)
^~~~~~~~~~~~~
/usr/include/stdlib.h:277:22: note: passing argument to parameter here
char *devname(__dev_t, __mode_t);
^
2 errors generated.
---
highlighters.cc:1110:13: error: use of undeclared identifier 'snprintf'; did you mean 'vswprintf'?
snprintf(buffer, 16, format, std::abs(line_to_format));
^~~~~~~~
vswprintf
/usr/include/wchar.h:139:5: note: 'vswprintf' declared here
int vswprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
highlighters.cc:1110:22: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [16]'
snprintf(buffer, 16, format, std::abs(line_to_format));
^~~~~~
/usr/include/wchar.h:139:35: note: passing argument to parameter here
int vswprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
2 errors generated.
---
json_ui.cc:60:13: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
sprintf(buf, "\\u%04x", *next);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
json_ui.cc:60:21: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [7]'
sprintf(buf, "\\u%04x", *next);
^~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
json_ui.cc:74:9: error: use of undeclared identifier 'sprintf'
sprintf(buffer, R"("#%02x%02x%02x")", color.r, color.g, color.b);
^
3 errors generated.
---
regex_impl.cc:1039:9: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
sprintf(buf, " %03d ", count++);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
regex_impl.cc:1039:17: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [20]'
sprintf(buf, " %03d ", count++);
^~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1208:18: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<Kakoune::RegexMode::Forward>::TestVM' requested here
TestVM<> vm{R"(a*b)"};
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1283:56: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<5>::TestVM' requested here
TestVM<RegexMode::Forward | RegexMode::Search> vm{R"(f.*a(.*o))"};
^
regex_impl.cc:1197:17: error: use of undeclared identifier 'puts'
{ if (dump) puts(dump_regex(*this).c_str()); }
^
regex_impl.cc:1423:57: note: in instantiation of member function 'Kakoune::(anonymous namespace)::TestVM<6>::TestVM' requested here
TestVM<RegexMode::Backward | RegexMode::Search> vm{R"(fo{1,})"};
^
5 errors generated.
---
remote.cc:829:9: error: use of undeclared identifier 'rename'; did you mean 'devname'?
if (rename(old_socket_file.c_str(), new_socket_file.c_str()) != 0)
^~~~~~
devname
/usr/include/stdlib.h:277:7: note: 'devname' declared here
char *devname(__dev_t, __mode_t);
^
remote.cc:829:16: error: cannot initialize a parameter of type '__dev_t' (aka 'unsigned long') with an rvalue of type 'const char *'
if (rename(old_socket_file.c_str(), new_socket_file.c_str()) != 0)
^~~~~~~~~~~~~~~~~~~~~~~
/usr/include/stdlib.h:277:22: note: passing argument to parameter here
char *devname(__dev_t, __mode_t);
^
2 errors generated.
---
string_utils.cc:126:20: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
res.m_length = sprintf(res.m_data, "%i", val);
^~~~~~~
swprintf
/usr/include/wchar.h:133:5: note: 'swprintf' declared here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
string_utils.cc:126:28: error: cannot initialize a parameter of type 'wchar_t *' with an lvalue of type 'char [15]'
res.m_length = sprintf(res.m_data, "%i", val);
^~~~~~~~~~
/usr/include/wchar.h:133:34: note: passing argument to parameter here
int swprintf(wchar_t * __restrict, size_t n, const wchar_t * __restrict,
^
string_utils.cc:133:20: error: use of undeclared identifier 'sprintf'; did you mean 'swprintf'?
res.m_length = sprintf(res.m_data, "%u", val);
^~~~~~~
swprintf
[...]
History registers (for prompt commands, pipe primitive commands etc) are
populated interactively by the users, and currently have no size limit.
This commits silently drops the oldest entries in the storage space to
allow at most 100 entries, to prevent long-running editing sessions from
hogging memory for data most likely never used.
This commit makes the `*` and <a-*> primitives compose a search pattern
comprised of all the current selections, as opposed to only the main one.
All selections are OR'd into the default search register, which makes it
convenient to search for several identifiers already selected.
To retain the old behaviour, the following mappings can be used:
```
map global normal * ': exec -draft -save-regs "" %{<space>*}<ret>'
map global normal <a-*> ': exec -draft -save-regs "" %{<space><a-*>}<ret>'
```
Fixes#2994
The `parse_keys()` function is case insensitive when parsing function keys,
while the `key_to_str()` function always returns a capitalized key
description.
When users hook on the lowercase name of a function key,
e.g. `NormalKey <f10>`, and later hit that same key in normal mode, the
`key_to_str()` will convert it to the uppercase description ("<F10>").
This results into a hook with a lowercase regex predicate being unsuccessfully
matched against an uppercase key description by the hook manager, which
works on a case sensitive basis.
One solution could be to uppercase all function key descriptions passed as
hook filter upon declaration, but detecting that is not trivial as the
filter can contain more than just the simple <f\d+> data, e.g.
---
hook global InsertKey '<(?<name>\w+)>' %{…}
---
Another simpler solution that this commit implements is to allow only <F\d+>
descriptions in `parse_keys()`, and hope users will know not to use the
lowercase notation when declaring hooks.
Fixes#2907
The previous "optimized" history register logic was unfortunately
not restoring correctly as the order of entries in the history
register could have been mutated.
The hook parameter should not be adjusted for the prevention of
scrolling. Also, ensure that the last BufReadFifo is triggered if we
encounter an error or EOF after appending some data to the buffer.
Closes#2946
On my system, some optimizations are on by default (NixOS), resulting in
variables being optimized out on debug builds. It *seems to be*
something about a "_FORTIFY_SOURCE" feature? In any case, `-Og` is
documented as "Optimize debugging experience".