1
1
mirror of https://github.com/mawww/kakoune.git synced 2024-08-16 16:20:38 +03:00

Merge branch 'master' into make-error

This commit is contained in:
Bob 2024-06-19 16:13:17 +08:00 committed by GitHub
commit e5e7930397
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
59 changed files with 527 additions and 176 deletions

View File

@ -1,6 +1,6 @@
freebsd_task:
freebsd_instance:
image_family: freebsd-13-2
image_family: freebsd-13-3
matrix:
- name: freebsd_clang
env:
@ -27,6 +27,7 @@ linux_task:
- name: linux_gcc
container:
image: gcc:10
memory: 8G
env:
CXX: g++
test_script: make CXX=$CXX -j$(nproc) test

View File

@ -632,7 +632,7 @@ use `n` followed by `,`, in order to remove a selection, use `<a-,>`.
contains a match for this regex. Using `<a-K>` you can keep the selections
not containing a match.
`C` duplicates selections on the lines that follow them.
`C` duplicates selections on the lines that follow them, column-wise.
`<a-C>` does the same but on the preceding lines.
`$` allows you to enter a shell command and pipe each selection to it.

View File

@ -1,7 +1,7 @@
C++ Coding Style
================
Kakoune is written in C++14, here are the main coding style points:
Kakoune is written in C++20, here are the main coding style points:
* Avoid external dependencies besides posix/stdc++

View File

@ -3,6 +3,14 @@
This changelog contains major and/or breaking changes to Kakoune between
released versions.
== Development version
* Expose env vars that are mentionned in the arguments passed to shell expansions
== Kakoune 2024.05.18
* Fixed tests on Alpine Linux and *BSD
== Kakoune 2024.05.09
* `flag-lines -after` switch to display text after the line

View File

@ -146,14 +146,16 @@ and unquote them, then execute the resulting `set` command, which sets
the shell's argument variables to the items from `$kak_selections`. The
`while` loop with `shift` iterates through the arguments one by one.
Only variables actually mentioned in the body of the shell expansion will
be exported into the shell's environment. For example:
Only variables actually mentioned in the body of the shell expansion or
in passed arguments will be exported into the shell's environment.
For example:
----
echo %sh{ env | grep ^kak_ }
----
... will find none of Kakoune's special environment variables, but:
... will not find any of Kakoune's special environment variables, but:
----
echo %sh{ env | grep ^kak_ # kak_session }
@ -162,6 +164,15 @@ echo %sh{ env | grep ^kak_ # kak_session }
... will find the `$kak_session` variable because it was mentioned by name
in a comment, even though it wasn't directly used.
----
define-command -params .. eval-shell %{ echo %sh{ eval "$@" } }
eval-shell 'echo $kak_session'
----
... will also find the `$kak_session` variable because it was mentioned by name
in the command arguments, which are automatically made available to shell blocks
as "$@"
TIP: These environment variables are also available in other contexts where
Kakoune uses a shell command, such as the `|`, `!` or `$` normal mode commands
(See <<keys#,`:doc keys`>>).
@ -215,8 +226,7 @@ The following expansions are supported (with required context _in italics_):
number of lines in the current buffer
*%val{buflist}*::
quoted list of the names of currently-open buffers (as seen in
`%val{bufname}`)
list of the names of currently-open buffers (as seen in %val{bufname})
*%val{bufname}*::
_in buffer, window scope_ +

View File

@ -479,6 +479,12 @@ Searches use the */* register by default (See <<registers#,`:doc registers`>>)
*b*:::
scroll to put the main selection on the bottom line of the window
*<*:::
scroll to put the main cursor on the leftmost column of the window
*>*:::
scroll to put the main cursor on the rightmost column of the window
*h*:::
scroll the window `count` columns left

View File

@ -133,9 +133,9 @@ be used:
*<F1>*, *<F2>*, ...*<F12>*::
Function keys.
*<semicolon>*, *<percent>*::
The *;* and *%* characters, these keys allow reducing the amount of
backslash escaping in scripts (for example, `exec \%` becomes `exec
*<semicolon>*, *<percent>*, *<quote>*, *<dquote>*::
The *;*, *%*, *'* and *"* characters, these keys allow reducing the amount
of backslash escaping in scripts (for example, `exec \%` becomes `exec
<percent>`)
NOTE: Although Kakoune allows many key combinations to be mapped, not every

32
rc/filetype/perf.kak Normal file
View File

@ -0,0 +1,32 @@
provide-module perf-report %{
add-highlighter shared/perf-report group
add-highlighter shared/perf-report/above_threshold regex '\b([5-9]|\d{2})\.\d+%' 0:red
add-highlighter shared/perf-report/below_threshold regex '\b[0-4]\.\d+%' 0:green
define-command -override perf-report-focus %{
execute-keys 'xs...\d+\.\d+%<ret><a-:><a-semicolon>vtv<lt><semicolon>'
}
}
hook -group perf-report-highlight global WinSetOption filetype=perf-report %{
require-module perf-report
add-highlighter window/perf-report ref perf-report
hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/perf-report }
map window normal <ret> ': perf-report-focus<ret>'
}
provide-module perf-annotate %{
require-module gas
add-highlighter shared/perf-annotate group
add-highlighter shared/perf-annotate/gas ref gas
add-highlighter shared/perf-annotate/above_threshold regex '^\h+([1-9]|\d{2})\.\d+\b' 0:red
add-highlighter shared/perf-annotate/below_threshold regex '^\h+0\.\d+\b' 0:green
}
hook -group perf-annotate-highlight global WinSetOption filetype=perf-annotate %{
require-module perf-annotate
add-highlighter window/perf-annotate ref perf-annotate
hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/perf-annotate }
}

View File

@ -167,14 +167,35 @@ define-command -hidden python-insert-on-new-line %{ evaluate-commands -itersel -
execute-keys <semicolon>
try %{
evaluate-commands -draft -save-regs '/"' %{
# copy the commenting prefix
execute-keys -save-regs '' k x1s^\h*(#+\h*)<ret> y
# Ensure previous line is a comment
execute-keys -draft kxs^\h*#+\h*<ret>
# now handle the coment continuation logic
try %{
# if the previous comment isn't empty, create a new one
execute-keys x<a-K>^\h*#+\h*$<ret> jxs^\h*<ret>P
# try and match a regular block comment, copying the prefix
execute-keys -draft -save-regs '' k x 1s^(\h*#+\h*)\S.*$ <ret> y
execute-keys -draft P
} catch %{
# if there is no text in the previous comment, remove it completely
execute-keys d
try %{
# try and match a regular block comment followed by a single
# empty comment line
execute-keys -draft -save-regs '' kKx 1s^(\h*#+\h*)\S+\n\h*#+\h*$ <ret> y
execute-keys -draft P
} catch %{
try %{
# try and match a pair of empty comment lines, and delete
# them if we match
execute-keys -draft kKx <a-k> ^\h*#+\h*\n\h*#+\h*$ <ret> <a-d>
} catch %{
# finally, we need a special case for a new line inserted
# into a file that consists of a single empty comment - in
# that case we can't expect to copy the trailing whitespace,
# so we add our own
execute-keys -draft -save-regs '' k x1s^(\h*#+)\h*$<ret> y
execute-keys -draft P
execute-keys -draft i<space>
}
}
}
}

View File

@ -45,10 +45,10 @@ evaluate-commands %sh{
add-highlighter shared/tcl/code/function regex ^\h*proc\h+((\w|-)+) 1:function
add-highlighter shared/tcl/code/brackets regex [\[\]]{1,2} 0:operator
add-highlighter shared/tcl/code/parameters regex \s-\w+\b 0:attribute
add-highlighter shared/tcl/code/variable regex \$(\w|:)+ 0:value
add-highlighter shared/tcl/code/variable regex \$(\w|:)+ 0:variable
add-highlighter shared/tcl/code/numbers regex '\b\d+\.?' 0:value
add-highlighter shared/tcl/double_string/variable regex \$(\w|:)+ 0:value
add-highlighter shared/tcl/double_string/variable regex \$(\w|:)+ 0:variable
add-highlighter shared/tcl/double_string/brackets regex [\[\]]{1,2} 0:operator
# Commands

69
rc/filetype/typst.kak Normal file
View File

@ -0,0 +1,69 @@
# Detection
# ‾‾‾‾‾‾‾‾‾
hook global BufCreate .*[.](typ) %{
set-option buffer filetype typst
}
# Initialization
# ‾‾‾‾‾‾‾‾‾‾‾‾‾‾
hook -group typst-highlight global WinSetOption filetype=typst %{
require-module typst
add-highlighter window/typst ref typst
hook -once -always window WinSetOption filetype=.* %{ remove-highlighter window/typst }
hook window InsertChar \n -group typst typst-on-new-line
}
provide-module typst %§
# Highlighters
# ‾‾‾‾‾‾‾‾‾‾‾‾
add-highlighter shared/typst group
# Comments
add-highlighter shared/typst/ regex ^//(?:[^\n/][^\n]*|)$ 0:comment
# Strings
add-highlighter shared/typst/ regex '"[^"]*"' 0:string
# Headings
add-highlighter shared/typst/ regex ^=+\h+[^\n]+$ 0:header
# Code blocks
# Raw with optional syntax highlighting
add-highlighter shared/typst/ regex '^```[^(```)]*```' 0:mono
# Multiline monospace
add-highlighter shared/typst/ regex '^`[^(`)]*`' 0:mono
# Monospace text
add-highlighter shared/typst/ regex \B(`[^\n]+?`)\B 0:mono
add-highlighter shared/typst/ regex \B(```[^\n]+?```)\B 0:mono
# Bold text
add-highlighter shared/typst/ regex \s\*[^\*]+\*\B 0:+b
# Italic text
add-highlighter shared/typst/ regex \b_.*?_\b 0:+i
# Code expressions: functions, variables
add-highlighter shared/typst/ regex (^|\h)#(\w|\.|-)+ 0:meta
# Bold terms in term lists
add-highlighter shared/typst/ regex ^/\h[^:]*: 0:+b
§
# Commands
# ‾‾‾‾‾‾‾‾
define-command -hidden typst-on-new-line %<
evaluate-commands -draft -itersel %<
# Preserve previous line indent
try %{ execute-keys -draft <semicolon> K <a-&> }
# Cleanup trailing whitespaces from previous line
try %{ execute-keys -draft k x s \h+$ <ret> d }
>
>

29
rc/tools/fifo.kak Normal file
View File

@ -0,0 +1,29 @@
provide-module fifo %{
define-command -params .. -docstring %{
fifo [-name <buffer-name>] [-scroll] [--] <command>...: run command in a fifo buffer
} fifo %{ evaluate-commands %sh{
name='*fifo*'
while true; do
case "$1" in
"-scroll") scroll="-scroll"; shift ;;
"-name") name="$2"; shift 2 ;;
"--") shift; break ;;
*) break ;;
esac
done
output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-fifo.XXXXXXXX)/fifo
mkfifo ${output}
( eval "$@" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
printf %s\\n "
edit! -fifo ${output} ${scroll} ${name}
hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
"
}}
complete-command fifo shell
}
hook -once global KakBegin .* %{ require-module fifo }

View File

@ -109,6 +109,7 @@ define-command -params 1.. \
} -shell-script-candidates %{
if [ $kak_token_to_complete -eq 0 ]; then
printf %s\\n \
add \
apply \
blame \
blame-jump \

View File

@ -3,38 +3,37 @@ declare-option -docstring "shell command run to search for subtext in a file/dir
provide-module grep %{
require-module fifo
require-module jump
define-command -params .. -docstring %{
grep [<arguments>]: grep utility wrapper
All optional arguments are forwarded to the grep utility
Passing no argument will perform a literal-string grep for the current selection
} grep %{ evaluate-commands %sh{
if [ $# -eq 0 ]; then
case "$kak_opt_grepcmd" in
ag\ * | git\ grep\ * | grep\ * | rg\ * | ripgrep\ * | ugrep\ * | ug\ *)
set -- -F "${kak_selection}"
;;
ack\ *)
set -- -Q "${kak_selection}"
;;
*)
set -- "${kak_selection}"
;;
esac
fi
output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-grep.XXXXXXXX)/fifo
mkfifo ${output}
( { trap - INT QUIT; ${kak_opt_grepcmd} "$@" 2>&1 | tr -d '\r'; } > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
edit! -fifo ${output} *grep*
set-option buffer filetype grep
set-option buffer jump_current_line 0
hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
}"
}}
} grep %{
evaluate-commands -try-client %opt{toolsclient} %{
fifo -name *grep* %{
shift 2
trap - INT QUIT
if [ $# -eq 0 ]; then
case "$kak_opt_grepcmd" in
ag\ * | git\ grep\ * | grep\ * | rg\ * | ripgrep\ * | ugrep\ * | ug\ *)
set -- -F "$kak_selection"
;;
ack\ *)
set -- -Q "$kak_selection"
;;
*)
set -- "$kak_selection"
;;
esac
fi
$kak_opt_grepcmd "$@" 2>&1 | tr -d '\r'
} 'exit;' %arg{@} # pass arguments for "$@" above, exit to avoid evaluating them
set-option buffer filetype grep
set-option buffer jump_current_line 0
}
}
complete-command grep file
hook -group grep-highlight global WinSetOption filetype=grep %{

View File

@ -5,25 +5,24 @@ declare-option -docstring "pattern that describes lines containing information a
provide-module make %{
require-module fifo
require-module jump
define-command -params .. \
-docstring %{
make [<arguments>]: make utility wrapper
All the optional arguments are forwarded to the make utility
} make %{ evaluate-commands %sh{
output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-make.XXXXXXXX)/fifo
mkfifo ${output}
( { trap - INT QUIT; eval "${kak_opt_makecmd}" "$@"; } > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
printf %s\\n "evaluate-commands -try-client '$kak_opt_toolsclient' %{
edit! -fifo ${output} -scroll *make*
set-option buffer filetype make
set-option buffer jump_current_line 0
set-option buffer make_error_pattern '$kak_opt_make_error_pattern'
hook -always -once buffer BufCloseFifo .* %{ nop %sh{ rm -r $(dirname ${output}) } }
}"
}}
define-command -params .. -docstring %{
make [<arguments>]: make utility wrapper
All the optional arguments are forwarded to the make utility
} make %{
evaluate-commands -try-client %opt{toolsclient} %{
fifo -scroll -name *make* %{
shift 2
trap - INT QUIT
$kak_opt_makecmd "$@"
} 'exit;' %arg{@} # pass arguments for "$@" above, exit to avoid evaluating them
set-option buffer filetype make
set-option buffer jump_current_line 0
set-option buffer make_error_pattern '$kak_opt_make_error_pattern'
}
}
add-highlighter shared/make group
add-highlighter shared/make/ regex "^\h*(~*(?:(\^)~*)?)$" 1:green 2:cyan+b

View File

@ -101,9 +101,7 @@ Buffer& BufferManager::get_buffer_matching(const Regex& regex)
Buffer& BufferManager::get_first_buffer()
{
if (all_of(m_buffers, [](auto& b) { return (b->flags() & Buffer::Flags::Debug); }))
create_buffer("*scratch*", Buffer::Flags::None,
{StringData::create("*** this is a *scratch* buffer which won't be automatically saved ***\n"),
StringData::create("*** use it for notes or open a file buffer with the :edit command ***\n")},
create_buffer("*scratch*", Buffer::Flags::None, {StringData::create("\n")},
ByteOrderMark::None, EolFormat::Lf, {InvalidTime, {}, {}});
return *m_buffers.back();

View File

@ -64,24 +64,28 @@ Client* ClientManager::create_client(std::unique_ptr<UserInterface>&& ui, int pi
std::move(on_exit)};
m_clients.emplace_back(client);
auto& context = client->context();
if (buffer->name() == "*scratch*")
context.print_status({"This *scratch* buffer won't be automatically saved",
context.faces()["Information"]});
if (init_coord)
{
auto& selections = client->context().selections_write_only();
auto& selections = context.selections_write_only();
selections = SelectionList(*buffer, buffer->clamp(*init_coord));
client->context().window().center_line(init_coord->line);
context.window().center_line(init_coord->line);
}
try
{
auto& context = client->context();
context.hooks().run_hook(Hook::ClientCreate, context.name(), context);
CommandManager::instance().execute(init_cmds, context);
}
catch (Kakoune::runtime_error& error)
{
client->context().print_status({error.what().str(), client->context().faces()["Error"]});
client->context().hooks().run_hook(Hook::RuntimeError, error.what(),
client->context());
context.print_status({error.what().str(), context.faces()["Error"]});
context.hooks().run_hook(Hook::RuntimeError, error.what(),
context);
}
// Do not return the client if it already got moved to the trash

View File

@ -1671,9 +1671,14 @@ const CommandDesc debug_cmd = {
for (auto mode : concatenated(modes, user_modes))
{
KeymapMode m = parse_keymap_mode(mode, user_modes);
for (auto& key : keymaps.get_mapped_keys(m))
write_to_debug_buffer(format(" * {} {}: {}",
mode, key, keymaps.get_mapping_docstring(key, m)));
for (auto& key : keymaps.get_mapped_keys(m)) {
KeyList kl = keymaps.get_mapping_keys(key, m);
String mapping;
for (const auto& k : kl)
mapping += to_string(k);
write_to_debug_buffer(format(" * {} {}: '{}' {}",
mode, key, mapping, keymaps.get_mapping_docstring(key, m)));
}
}
}
else if (parser[0] == "regex")

View File

@ -82,6 +82,8 @@ static constexpr KeyAndName keynamemap[] = {
{ "minus", '-' },
{ "semicolon", ';' },
{ "percent", '%' },
{ "quote", '\'' },
{ "dquote", '"' },
{ "focus_in", Key::FocusIn },
{ "focus_out", Key::FocusOut },
};

View File

@ -45,6 +45,12 @@ struct {
unsigned int version;
StringView notes;
} constexpr version_notes[] = { {
0,
"» kak_* appearing in shell arguments will be added to the environment\n"
}, {
20240518,
"» Fix tests failing on some platforms\n"
}, {
20240509,
"» {+u}flag-lines -after{} highlighter\n"
"» asynchronous {+u}shell-script-candidates{} completion\n"

View File

@ -399,6 +399,13 @@ void view_commands(Context& context, NormalParams params)
case 'b':
window.display_line_at(cursor.line, window.dimensions().line-1);
break;
case '<':
window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column), 0);
break;
case '>':
window.display_column_at(context.buffer()[cursor.line].column_count_to(cursor.column),
window.dimensions().column-1);
break;
case 'h':
window.scroll(-std::max<ColumnCount>(1, count));
break;
@ -420,6 +427,8 @@ void view_commands(Context& context, NormalParams params)
{{'m'}, "center cursor (horizontally)"},
{{'t'}, "cursor on top"},
{{'b'}, "cursor on bottom"},
{{'<'}, "cursor on left"},
{{'>'}, "cursor on right"},
{{'h'}, "scroll left"},
{{'j'}, "scroll down"},
{{'k'}, "scroll up"},

View File

@ -706,7 +706,7 @@ private:
{
auto& node = get_node(index);
const uint32_t start_pos = (uint32_t)m_program.instructions.size();
const OpIndex start_pos = op_count();
const bool ignore_case = node.ignore_case;
const bool save = (node.op == ParsedRegex::Alternation or node.op == ParsedRegex::Sequence) and
@ -746,14 +746,14 @@ private:
if (child != index+1)
push_inst(CompiledRegex::Split);
}
auto split_pos = m_program.instructions.size();
auto split_pos = op_count();
const auto end = node.children_end;
for (auto child : Children<>{m_parsed_regex, index})
{
auto node = compile_node<direction>(child);
if (child != index+1)
m_program.instructions[--split_pos].param.split = CompiledRegex::Param::Split{.target = node, .prioritize_parent = true};
m_program.instructions[--split_pos].param.split = CompiledRegex::Param::Split{.offset = offset(node, split_pos), .prioritize_parent = true};
if (get_node(child).children_end != end)
{
auto jump = push_inst(CompiledRegex::Jump);
@ -801,8 +801,8 @@ private:
break;
}
for (auto& offset : goto_inner_end_offsets)
m_program.instructions[offset].param.jump_target = m_program.instructions.size();
for (auto& index : goto_inner_end_offsets)
m_program.instructions[index].param.jump_offset = offset(op_count(), index);
if (save)
push_inst(CompiledRegex::Save, {.save_index=int16_t(node.value * 2 + (forward ? 1 : 0))});
@ -810,19 +810,29 @@ private:
return start_pos;
}
OpIndex op_count() const
{
return static_cast<OpIndex>(m_program.instructions.size());
}
static OpIndex offset(OpIndex to, OpIndex from)
{
return static_cast<OpIndex>(to - from);
}
template<RegexMode direction>
OpIndex compile_node(ParsedRegex::NodeIndex index)
{
auto& node = get_node(index);
const OpIndex start_pos = (OpIndex)m_program.instructions.size();
const OpIndex start_pos = op_count();
Vector<OpIndex> goto_ends;
auto& quantifier = node.quantifier;
if (quantifier.allows_none())
{
auto split_pos = push_inst(CompiledRegex::Split, {.split={.target=0, .prioritize_parent=quantifier.greedy}});
auto split_pos = push_inst(CompiledRegex::Split, {.split={.offset=0, .prioritize_parent=quantifier.greedy}});
goto_ends.push_back(split_pos);
}
@ -832,18 +842,18 @@ private:
inner_pos = compile_node_inner<direction>(index);
if (quantifier.allows_infinite_repeat())
push_inst(CompiledRegex::Split, {.split = {.target=inner_pos, .prioritize_parent=not quantifier.greedy}});
push_inst(CompiledRegex::Split, {.split = {.offset=offset(inner_pos, op_count()), .prioritize_parent=not quantifier.greedy}});
// Write the node as an optional match for the min -> max counts
else for (int i = std::max((int16_t)1, quantifier.min); // STILL UGLY !
i < quantifier.max; ++i)
{
auto split_pos = push_inst(CompiledRegex::Split, {.split={.target=0, .prioritize_parent=quantifier.greedy}});
auto split_pos = push_inst(CompiledRegex::Split, {.split={.offset=0, .prioritize_parent=quantifier.greedy}});
goto_ends.push_back(split_pos);
compile_node_inner<direction>(index);
}
for (auto offset : goto_ends)
m_program.instructions[offset].param.split.target = m_program.instructions.size();
for (auto index : goto_ends)
m_program.instructions[index].param.split.offset = offset(op_count(), index);
return start_pos;
}
@ -851,7 +861,7 @@ private:
OpIndex push_inst(CompiledRegex::Op op, CompiledRegex::Param param = {})
{
constexpr auto max_instructions = std::numeric_limits<OpIndex>::max();
const auto res = m_program.instructions.size();
const auto res = op_count();
if (res >= max_instructions)
throw regex_error(format("regex compiled to more than {} instructions", max_instructions));
m_program.instructions.push_back({ op, 0, param });
@ -1031,7 +1041,7 @@ private:
{
auto& inst = m_program.instructions[i];
if (is_jump(inst.op))
m_program.instructions[inst.param.jump_target].last_step = 0xffff; // tag as jump target
m_program.instructions[i + inst.param.jump_offset].last_step = 0xffff; // tag as jump target
}
for (auto block_begin = begin; block_begin < end; )
@ -1071,11 +1081,11 @@ private:
String dump_regex(const CompiledRegex& program)
{
String res;
int count = 0;
int index = 0;
for (auto& inst : program.instructions)
{
char buf[20];
format_to(buf, " {:03} ", count++);
format_to(buf, " {:03} ", index);
res += buf;
switch (inst.op)
{
@ -1095,13 +1105,13 @@ String dump_regex(const CompiledRegex& program)
res += format("character type {}\n", to_underlying(inst.param.character_type));
break;
case CompiledRegex::Jump:
res += format("jump {}\n", inst.param.jump_target);
res += format("jump {} ({:03})\n", inst.param.jump_offset, index + inst.param.jump_offset);
break;
case CompiledRegex::Split:
{
res += format("split (prioritize {}) {}\n",
res += format("split (prioritize {}) {} ({:03})\n",
(inst.param.split.prioritize_parent) ? "parent" : "child",
inst.param.split.target);
inst.param.split.offset, index + inst.param.split.offset);
break;
}
case CompiledRegex::Save:
@ -1135,6 +1145,7 @@ String dump_regex(const CompiledRegex& program)
case CompiledRegex::Match:
res += "match\n";
}
++index;
}
auto dump_start_desc = [&](const CompiledRegex::StartDesc& desc, StringView name) {
res += name + " start desc: [";

View File

@ -54,11 +54,11 @@ struct CharacterClass
if (ignore_case)
cp = to_lower(cp);
for (auto& range : ranges)
for (auto& [min, max] : ranges)
{
if (cp < range.min)
if (cp < min)
break;
else if (cp <= range.max)
else if (cp <= max)
return not negative;
}
@ -106,11 +106,11 @@ struct CompiledRegex : UseMemoryDomain<MemoryDomain::Regex>
} literal;
int16_t character_class_index;
CharacterType character_type;
int16_t jump_target;
int16_t jump_offset;
int16_t save_index;
struct Split
{
int16_t target;
int16_t offset;
bool prioritize_parent : 1;
} split;
bool line_start;
@ -351,10 +351,10 @@ private:
--saves.refcount;
};
struct alignas(int32_t) Thread
struct Thread
{
int16_t inst;
int16_t saves;
const CompiledRegex::Instruction* inst;
int saves;
};
using StartDesc = CompiledRegex::StartDesc;
@ -370,8 +370,7 @@ private:
// Steps a thread until it consumes the current character, matches or fail
[[gnu::always_inline]]
void step_thread(const CompiledRegex::Instruction* instructions, const Iterator& pos, Codepoint cp,
uint16_t current_step, Thread thread, const ExecConfig& config)
void step_thread(const Iterator& pos, Codepoint cp, uint16_t current_step, Thread thread, const ExecConfig& config)
{
auto failed = [this, &thread]() {
release_saves(thread.saves);
@ -382,7 +381,7 @@ private:
while (true)
{
auto& inst = instructions[thread.inst++];
auto& inst = *thread.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)
@ -416,19 +415,20 @@ private:
return consumed();
return failed();
case CompiledRegex::CharClass:
if (pos == config.end)
return failed();
return m_program.character_classes[inst.param.character_class_index].matches(cp) ? consumed() : failed();
if (pos != config.end and
m_program.character_classes[inst.param.character_class_index].matches(cp))
return consumed();
return failed();
case CompiledRegex::CharType:
if (pos == config.end)
return failed();
return is_ctype(inst.param.character_type, cp) ? consumed() : failed();
if (pos != config.end and is_ctype(inst.param.character_type, cp))
return consumed();
return failed();
case CompiledRegex::Jump:
thread.inst = inst.param.jump_target;
thread.inst = &inst + inst.param.jump_offset;
break;
case CompiledRegex::Split:
if (auto target = inst.param.split.target;
instructions[target].last_step != current_step)
if (auto* target = &inst + inst.param.split.offset;
target->last_step != current_step)
{
if (thread.saves >= 0)
++m_saves[thread.saves].refcount;
@ -478,15 +478,15 @@ private:
m_captures = -1;
m_threads.ensure_initial_capacity();
const int16_t first_inst = forward ? 0 : m_program.first_backward_inst;
ConstArrayView<CompiledRegex::Instruction> insts{m_program.instructions};
const auto* first_inst = insts.begin() + (forward ? 0 : m_program.first_backward_inst);
m_threads.push_current({first_inst, -1});
const auto& start_desc = forward ? m_program.forward_start_desc : m_program.backward_start_desc;
const auto* start_desc = (forward ? m_program.forward_start_desc : m_program.backward_start_desc).get();
auto next_start = pos;
constexpr bool search = mode & RegexMode::Search;
constexpr bool any_match = mode & RegexMode::AnyMatch;
ConstArrayView<CompiledRegex::Instruction> insts{m_program.instructions};
uint16_t current_step = -1;
m_found_match = false;
while (true) // Iterate on all codepoints and once at the end
@ -506,7 +506,7 @@ private:
Codepoint cp = codepoint(next, config);
while (not m_threads.current_is_empty())
step_thread(insts.pointer(), pos, cp, current_step, m_threads.pop_current(), config);
step_thread(pos, cp, current_step, m_threads.pop_current(), config);
if (pos == config.end or
(m_threads.next_is_empty() and (not search or m_found_match)) or
@ -647,8 +647,6 @@ private:
}
}
const CompiledRegex& m_program;
struct DualThreadStack
{
bool current_is_empty() const { return m_current == m_next_begin; }
@ -721,6 +719,7 @@ private:
static constexpr bool forward = mode & RegexMode::Forward;
const CompiledRegex& m_program;
DualThreadStack m_threads;
Vector<Saves, MemoryDomain::Regex> m_saves;
int16_t m_first_free = -1;

View File

@ -141,30 +141,35 @@ Shell spawn_shell(const char* shell, StringView cmdline,
}
template<typename GetValue>
Vector<String> generate_env(StringView cmdline, const Context& context, GetValue&& get_value)
Vector<String> generate_env(StringView cmdline, ConstArrayView<String> params, const Context& context, GetValue&& get_value)
{
static const Regex re(R"(\bkak_(quoted_)?(\w+)\b)");
Vector<String> env;
for (auto&& match : RegexIterator{cmdline.begin(), cmdline.end(), re})
{
StringView name{match[2].first, match[2].second};
StringView shell_name{match[0].first, match[0].second};
auto match_name = [&](const String& s) {
return s.substr(0_byte, shell_name.length()) == shell_name and
s.substr(shell_name.length(), 1_byte) == "=";
};
if (any_of(env, match_name))
continue;
try
auto add_matches = [&](StringView s) {
for (auto&& match : RegexIterator{s.begin(), s.end(), re})
{
StringView quoted{match[1].first, match[1].second};
Quoting quoting = match[1].matched ? Quoting::Shell : Quoting::Raw;
env.push_back(format("kak_{}{}={}", quoted, name, get_value(name, quoting)));
} catch (runtime_error&) {}
}
StringView name{match[2].first, match[2].second};
StringView shell_name{match[0].first, match[0].second};
auto match_name = [&](const String& s) {
return s.substr(0_byte, shell_name.length()) == shell_name and
s.substr(shell_name.length(), 1_byte) == "=";
};
if (any_of(env, match_name))
continue;
try
{
StringView quoted{match[1].first, match[1].second};
Quoting quoting = match[1].matched ? Quoting::Shell : Quoting::Raw;
env.push_back(format("kak_{}{}={}", quoted, name, get_value(name, quoting)));
} catch (runtime_error&) {}
}
};
add_matches(cmdline);
for (auto&& param : params)
add_matches(param);
return env;
}
@ -265,13 +270,13 @@ std::pair<String, int> ShellManager::eval(
const DebugFlags debug_flags = context.options()["debug"].get<DebugFlags>();
const bool profile = debug_flags & DebugFlags::Profile;
if (debug_flags & DebugFlags::Shell)
write_to_debug_buffer(format("shell:\n{}\n----\n", cmdline));
write_to_debug_buffer(format("shell:\n{}\n----\nargs: {}\n----\n", cmdline, join(shell_context.params | transform(shell_quote), ' ')));
auto start_time = profile ? Clock::now() : Clock::time_point{};
Optional<CommandFifos> command_fifos;
auto kak_env = generate_env(cmdline, context, [&](StringView name, Quoting quoting) {
auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) {
if (name == "command_fifo" or name == "response_fifo")
{
if (not command_fifos)
@ -377,7 +382,7 @@ std::pair<String, int> ShellManager::eval(
Shell ShellManager::spawn(StringView cmdline, const Context& context,
bool open_stdin, const ShellContext& shell_context)
{
auto kak_env = generate_env(cmdline, context, [&](StringView name, Quoting quoting) {
auto kak_env = generate_env(cmdline, shell_context.params, context, [&](StringView name, Quoting quoting) {
if (auto it = shell_context.env_vars.find(name); it != shell_context.env_vars.end())
return it->value;
return join(get_val(name, context) | transform(quoter(quoting)), ' ', false);

View File

@ -1,22 +1,27 @@
#include "string.hh"
#include <cstdio>
#include <cstring>
#include "assert.hh"
#include "unit_tests.hh"
namespace Kakoune
{
namespace
{
// Avoid including all of <algorithm> just for this.
constexpr auto max(auto lhs, auto rhs) { return lhs > rhs ? lhs : rhs;}
constexpr auto min(auto lhs, auto rhs) { return lhs < rhs ? lhs : rhs;}
}
String::Data::Data(const char* data, size_t size, size_t capacity)
{
if (capacity > Short::capacity)
{
if (capacity & 1)
++capacity;
kak_assert(capacity < Long::max_capacity);
kak_assert(capacity <= Long::max_capacity);
u.l.ptr = Alloc{}.allocate(capacity+1);
u.l.size = size;
u.l.capacity = capacity;
u.l.capacity = (capacity & Long::max_capacity);
u.l.mode = Long::active_mask;
if (data != nullptr)
memcpy(u.l.ptr, data, size);
@ -60,26 +65,28 @@ String::Data& String::Data::operator=(Data&& other) noexcept
template<bool copy>
void String::Data::reserve(size_t new_capacity)
{
if (capacity() != 0 and new_capacity <= capacity())
auto const current_capacity = capacity();
if (current_capacity != 0 and new_capacity <= current_capacity)
return;
if (is_long())
new_capacity = std::max(u.l.capacity * 2, new_capacity);
if (!is_long() and new_capacity <= Short::capacity)
return;
if (new_capacity & 1)
++new_capacity;
kak_assert(new_capacity <= Long::max_capacity);
new_capacity = max(new_capacity, // Do not upgrade new_capacity to be over limit.
min(current_capacity * 2, Long::max_capacity));
kak_assert(new_capacity < Long::max_capacity);
char* new_ptr = Alloc{}.allocate(new_capacity+1);
if (copy)
{
memcpy(new_ptr, data(), size()+1);
u.l.size = size();
}
release();
u.l.size = size();
u.l.ptr = new_ptr;
u.l.capacity = new_capacity;
u.l.capacity = (new_capacity & Long::max_capacity);
u.l.mode = Long::active_mask;
}
template void String::Data::reserve<true>(size_t);
@ -89,6 +96,7 @@ void String::Data::force_size(size_t new_size)
{
reserve<false>(new_size);
set_size(new_size);
data()[new_size] = 0;
}
void String::Data::append(const char* str, size_t len)
@ -131,17 +139,47 @@ void String::Data::set_size(size_t size)
if (is_long())
u.l.size = size;
else
u.s.size = (size << 1) | 1;
u.s.remaining_size = Short::capacity - size;
}
void String::Data::set_short(const char* data, size_t size)
{
u.s.size = (size << 1) | 1;
kak_assert(size <= Short::capacity);
u.s.remaining_size = Short::capacity - size;
if (data != nullptr)
memcpy(u.s.string, data, size);
u.s.string[size] = 0;
}
UnitTest test_data{[]{
using Data = String::Data;
{ // Basic data usage.
Data data;
kak_assert(data.size() == 0);
kak_assert(not data.is_long());
kak_assert(data.capacity() == 23);
// Should be SSO-ed.
data.append("test", 4);
kak_assert(data.size() == 4);
kak_assert(data.capacity() == 23);
kak_assert(not data.is_long());
kak_assert(data.data() == StringView("test"));
}
{
char large_buf[2048];
memset(large_buf, 'x', 2048);
Data data(large_buf, 2048);
kak_assert(data.size() == 2048);
kak_assert(data.capacity() >= 2048);
data.clear();
kak_assert(data.size() == 0);
kak_assert(not data.is_long());
kak_assert(data.capacity() == 23);
}
}};
const String String::ms_empty;
}

View File

@ -1,12 +1,13 @@
#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 <climits>
namespace Kakoune
{
@ -156,17 +157,22 @@ public:
// String data storage using small string optimization.
//
// the LSB of the last byte is used to flag if we are using the small buffer
// or an allocated one. On big endian systems that means the allocated
// capacity must be pair, on little endian systems that means the allocated
// capacity cannot use its most significant byte, so we effectively limit
// capacity to 2^24 on 32bit arch, and 2^60 on 64.
// The MSB of the last byte is used to flag if we are using the allocated buffer
// (1) or in-situ storage, the small one (0). That means the allocated capacity
// cannot use its most significant byte, so we effectively limit capacity to
// 2^24 on 32bit arch, and 2^56 on 64bit.
//
// There is also a special NoCopy mode in which the data referred to is un-owned.
// It is indicated by being in Long mode with capacity == 0.
struct Data
{
using Alloc = Allocator<char, MemoryDomain::String>;
Data() { set_empty(); }
Data(NoCopy, const char* data, size_t size) : u{Long{const_cast<char*>(data), size, 0}} {}
Data(NoCopy, const char* data, size_t size) : u{Long{const_cast<char*>(data),
size,
/*capacity=*/0,
/*mode=*/Long::active_mask}} {}
Data(const char* data, size_t size, size_t capacity);
Data(const char* data, size_t size) : Data(data, size, size) {}
@ -177,8 +183,8 @@ public:
Data& operator=(const Data& other);
Data& operator=(Data&& other) noexcept;
bool is_long() const { return (u.s.size & 1) == 0; }
size_t size() const { return is_long() ? u.l.size : (u.s.size >> 1); }
bool is_long() const { return (u.l.mode& Long::active_mask) > 0; }
size_t size() const { return is_long() ? u.l.size : (Short::capacity - u.s.remaining_size); }
size_t capacity() const { return is_long() ? u.l.capacity : Short::capacity; }
const char* data() const { return is_long() ? u.l.ptr : u.s.string; }
@ -194,20 +200,27 @@ public:
private:
struct Long
{
static constexpr size_t max_capacity =
(size_t)1 << 8 * (sizeof(size_t) - 1);
static constexpr size_t capacity_bits = CHAR_BIT * (sizeof(size_t) - 1);
static constexpr size_t max_capacity = ((size_t)1 << capacity_bits) - 1;
char* ptr;
size_t size;
size_t capacity;
size_t capacity : capacity_bits;
unsigned char mode;
static constexpr unsigned char active_mask = 0b1000'0000;
};
static_assert(sizeof(Long) == sizeof(char*) * 3);
struct Short
{
static constexpr size_t capacity = sizeof(Long) - 2;
char string[capacity+1];
unsigned char size;
static constexpr size_t capacity = sizeof(Long) - 1;
char string[capacity];
// When string is full remaining_size will be 0 and be the null terminator.
// When string is empty remaining size will be 23 (0b00010111)
// and not collide with Long::active_mask.
unsigned char remaining_size;
};
static_assert(offsetof(Long, mode) == offsetof(Short, remaining_size));
union
{
@ -217,11 +230,11 @@ public:
void release()
{
if (is_long() and u.l.capacity != 0)
if (is_long() and (u.l.capacity != 0))
Alloc{}.deallocate(u.l.ptr, u.l.capacity+1);
}
void set_empty() { u.s.size = 1; u.s.string[0] = 0; }
void set_empty() { u.s.remaining_size = Short::capacity; u.s.string[0] = '\0'; }
void set_short(const char* data, size_t size);
};

View File

@ -702,7 +702,7 @@ Optional<Key> TerminalUI::get_next_key()
static constexpr auto control = [](char c) { return c & 037; };
static auto convert = [this](Codepoint c) -> Codepoint {
auto convert = [this](Codepoint c) -> Codepoint {
if (c == control('m') or c == control('j'))
return Key::Return;
if (c == control('i'))
@ -717,7 +717,7 @@ Optional<Key> TerminalUI::get_next_key()
return Key::Escape;
return c;
};
static auto parse_key = [](unsigned char c) -> Key {
auto parse_key = [&convert](unsigned char c) -> Key {
if (Codepoint cp = convert(c); cp > 255)
return Key{cp};
// Special case: you can type NUL with Ctrl-2 or Ctrl-Shift-2 or
@ -756,7 +756,7 @@ Optional<Key> TerminalUI::get_next_key()
return mod;
};
auto parse_csi = [this]() -> Optional<Key> {
auto parse_csi = [this, &convert]() -> Optional<Key> {
auto next_char = [] { return get_char().value_or((unsigned char)0xff); };
int params[16][4] = {};
auto c = next_char();

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1,2 @@
#
#

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1 @@
#

View File

@ -0,0 +1,2 @@
#
#

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,4 @@
# A new line after a pair of empty comment lines should exit the
# block comment.
#
#

View File

@ -0,0 +1,3 @@
# A new line after a pair of empty comment lines should exit the
# block comment.

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,4 @@
# A new line after a pair of empty comment lines should exit the
# block comment.
#
#

View File

@ -0,0 +1,3 @@
# A new line after a pair of empty comment lines should exit the
# block comment.

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,4 @@
# If we're not in a comment at all, make sure we do the right thing.
# (one empty line and a 4-space prefixed line follows)

View File

@ -0,0 +1,5 @@
# If we're not in a comment at all, make sure we do the right thing.
# (one empty line and a 4-space prefixed line follows)

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,4 @@
# If we're not in a comment at all, make sure we do the right thing.
# (two empty lines follow)

View File

@ -0,0 +1,5 @@
# If we're not in a comment at all, make sure we do the right thing.
# (two empty lines follow)

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,3 @@
# A new line after a single empty comment line should leave the comment in
# place as a possible paragraph separator.
#

View File

@ -0,0 +1,4 @@
# A new line after a single empty comment line should leave the comment in
# place as a possible paragraph separator.
#
#

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python

View File

@ -0,0 +1 @@
gjA<ret>

View File

@ -0,0 +1,4 @@
# A new line after a single empty comment line should leave the empty comment
# in place as a possible paragraph separator, starting a new comment with the
# prefix copied.
#

View File

@ -0,0 +1,5 @@
# A new line after a single empty comment line should leave the empty comment
# in place as a possible paragraph separator, starting a new comment with the
# prefix copied.
#
#

View File

@ -0,0 +1,3 @@
source "%val{runtime}/colors/default.kak"
source "%val{runtime}/rc/filetype/python.kak"
set buffer filetype python