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

Compare commits

...

26 Commits

Author SHA1 Message Date
Caleb Heuer
161b9e741a Add terminal_info_inline_borders option 2024-06-25 15:48:03 -06:00
Maxime Coste
374a7a8c99 Remove some selection copies in pipe and throw early if read only
Throwing early avoids losing selections in this case.
2024-06-24 21:18:17 +10:00
Maxime Coste
6674fe7587 Merge remote-tracking branch 'stacyharper/elixir-heex' 2024-06-23 11:14:34 +10:00
Maxime Coste
4a00a6edea Fix trailing whitespaces 2024-06-23 11:03:50 +10:00
Tobias Pisani
6493ddad42 Allow individual show-whitespace options to be turned off
This is especially useful to use the indent guides without the  other
parameters, but in general it can be a useful option.

It could be worth considering cleaning up these options to default off instead, but
the default also seems useful, so i consider this ok, as it might be the more advanced
usecase.
2024-06-23 11:03:03 +10:00
Maxime Coste
880ad98a30 Add a -script switch to the fifo to cater for grep/make use cases
Avoid the brittle `exit; %arg{@}` trick
2024-06-22 17:02:54 +10:00
Willow Barraco
0bb355557e
elixir: detect heex additionaly to leex 2024-06-19 12:45:20 +02:00
Maxime Coste
ba504431dc Small code style tweak 2024-06-15 16:09:14 +10:00
Maxime Coste
c4684d0d84 Store instruction pointers directly in ThreadedRegexVM::Thread
The previous tradeoff of having a very small Thread struct is not
necessary anymore as we do not memcpy Threads on swap_next since
d708b77186.

This requires offsets to be used instead of indices for jump/split
ops.
2024-06-15 14:36:33 +10:00
Maxime Coste
c84942c2ac Add perf-annotate highlighting to perf.kak 2024-06-15 11:45:19 +10:00
Maxime Coste
700265b25d Bump cirrus CI linux_gcc memory to avoid OOM 2024-06-12 22:39:45 +10:00
Maxime Coste
8045712595 Bump cirrus CI freebsd to 13.3 2024-06-12 22:31:41 +10:00
Maxime Coste
966deb514e Add some static_asserts in SSO code 2024-06-12 20:18:00 +10:00
Maxime Coste
fe8f0f3371 Merge remote-tracking branch 'Icantjuddle/master' 2024-06-12 19:55:34 +10:00
Ben Judd
faf83b10e2 Switch to bitfield. 2024-06-11 08:33:35 -07:00
Maxime Coste
5a6fb51bdb Tweak python block command test location and remove wall of text
We do not typically go into lengthy explanation of the code in the
support scripts. This would have a performance impact (as comments
are not trimmed in advance) and feels out of place.
2024-06-11 19:39:07 +10:00
Maxime Coste
17c25cc86a Merge remote-tracking branch 'sjjf/python_comment_paras' 2024-06-11 19:33:33 +10:00
Coleman McFarland
a35a7dc4a4
Fix up %val{buflist} description in expansions.asciidoc
The output is not quoted by default. Fixes #5158
2024-06-10 19:43:08 -04:00
Ben Judd
9c185249a2 Fix build by moving include. 2024-06-07 17:54:06 -07:00
Ben Judd
2754e27cf2 Increase SSO from 22 to 23 chars. 2024-06-07 17:48:07 -07:00
Simon Fowler
9c9aa2cf95 Support paragraph breaks in python block comments.
The current python filetype module treats a single empty comment line
(typically created by hitting enter twice while in a block comment) as
the end of a block comment, deleting the empty comment and ending
comment prefix copying. This runs contrary to PEP8, which explicitly
allows for paragraphs in block comments, with an empty comment as the
paragraph separator.

This change implements support for using a single empty comment as a
paragraph separator, with two consecutive empty comments being treated
as the end of the block comment; both empty comment lines are deleted
and comment prefix copying is ended.
2024-06-07 20:33:17 +10:00
Maxime Coste
c93cb5c4d8 Add a fifo helper command and refactor make and grep to use it
Running arbitrary commands in a fifo is very useful in its own right
and factoring out the common pattern is a nice added cleanup.
2024-06-07 19:03:50 +10:00
Maxime Coste
1bdae3f9c4 Also check shell parameters for kak_* references
This makes it easier to pass shell fragments as arguments so that
%sh{ eval "$@" } just works even if arguments refer to Kakoune's
vars.
2024-06-07 18:59:30 +10:00
Maxime Coste
688e87d668 Add rc/filetype/perf.kak for perf-report highlight 2024-06-06 09:46:37 +10:00
Tobias Pisani
1c352e996c Add <quote> and <dquote> key name aliases.
These two can also be annoying to have to escape, so this should make it slightly easier to manage
2024-06-05 20:02:38 +10:00
Maxime Coste
51ec633718 Echo an information message about *scratch* buffer and leave it empty
Having to manually clear the scratch was never really nice and hopefully
this will be less annoying and as helpful to newcomers.
2024-06-05 19:49:35 +10:00
62 changed files with 477 additions and 202 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

@ -3,6 +3,10 @@
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

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

@ -41,22 +41,27 @@ highlighter is replaced with the new one.
using the `Whitespace` face, with the following *options*:
*-lf* <separator>:::
a one character long separator that will replace line feeds
a one character long separator that will replace line feeds,
or an empty string to ignore them.
*-spc* <separator>:::
a one character long separator that will replace spaces
a one character long separator that will replace spaces,
or an empty string to ignore them.
*-nbsp* <separator>:::
a one character long separator that will replace non-breakable spaces
a one character long separator that will replace non-breakable spaces,
or an empty string to ignore them.
*-tab* <separator>:::
a one character long separator that will replace tabulations
a one character long separator that will replace tabulations,
or an empty string to ignore them.
*-tabpad* <separator>:::
a one character long separator that will be appended to tabulations to honor the *tabstop* option
*-indent* <separator>:::
a one character long separator that will replace the first space in indentation according to the *indentwidth* option
a one character long separator that will replace the first space in indentation
according to the *indentwidth* option, or an empty string to ignore them.
This will use the `WhitespaceIndent` face.
*-only-trailing*:::

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

View File

@ -381,6 +381,10 @@ are exclusively available to built-in options.
set the maximum allowable width of an info box. set to zero for
no limit.
*terminal_info_inline_borders*:::
if *yes* or *true*, borders will be drawn around info boxes with the
*inline* style.
[[startup-info]]
*startup_info_version* `int`::
_default_ 0 +

View File

@ -8,7 +8,7 @@ hook global BufCreate .*[.](ex|exs) %{
set-option buffer filetype elixir
}
hook global BufCreate .*[.]html[.]l?eex %{
hook global BufCreate .*[.]html[.][lh]?eex %{
set-option buffer filetype eex
}
@ -64,6 +64,7 @@ add-highlighter shared/elixir/single_string region "'" "(?<!\\)(?:\\\\)*'" fill
add-highlighter shared/elixir/comment region '#' '$' fill comment
add-highlighter shared/elixir/leex region -match-capture '~L("""|")' '(?<!\\)(?:\\\\)*("""|")' ref eex
add-highlighter shared/elixir/heex region -match-capture '~H("""|")' '(?<!\\)(?:\\\\)*("""|")' ref eex
add-highlighter shared/elixir/double_string/base default-region fill string
add-highlighter shared/elixir/double_string/interpolation region -recurse \{ \Q#{ \} fill builtin

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>
}
}
}
}

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

@ -0,0 +1,35 @@
provide-module fifo %{
define-command -params .. -docstring %{
fifo [-name <name>] [-scroll] [-script <script>] [--] <args>...: run command in a fifo buffer
if <script> is used, eval it with <args> as '$@', else pass arguments directly to the shell
} fifo %{ evaluate-commands %sh{
name='*fifo*'
while true; do
case "$1" in
"-scroll") scroll="-scroll"; shift ;;
"-script") script="$2"; shift 2 ;;
"-name") name="$2"; shift 2 ;;
"--") shift; break ;;
*) break ;;
esac
done
output=$(mktemp -d "${TMPDIR:-/tmp}"/kak-fifo.XXXXXXXX)/fifo
mkfifo ${output}
if [ -n "$script" ]; then
( eval "$script" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
else
( "$@" > ${output} 2>&1 & ) > /dev/null 2>&1 < /dev/null
fi
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

@ -3,38 +3,36 @@ 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* -script %{
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'
} %arg{@}
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,24 +5,22 @@ 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
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* -script %{
trap - INT QUIT
$kak_opt_makecmd "$@"
} %arg{@} # pass arguments for "$@" above, exit to avoid evaluating them
set-option buffer filetype make
set-option buffer jump_current_line 0
}
}
add-highlighter shared/make group
add-highlighter shared/make/ regex "^([^:\n]+):(\d+):(?:(\d+):)?\h+(?:((?:fatal )?error)|(warning)|(note)|(required from(?: here)?))?.*?$" 1:cyan 2:green 3:green 4:red 5:yellow 6:blue 7:yellow

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

@ -424,7 +424,7 @@ StringView Context::main_sel_register_value(StringView reg) const
return RegisterManager::instance()[reg].get_main(*this, index);
}
void Context::set_name(String name) {
void Context::set_name(String name) {
String old_name = std::exchange(m_name, std::move(name));
hooks().run_hook(Hook::ClientRenamed, format("{}:{}", old_name, m_name), *this);
}

View File

@ -239,7 +239,7 @@ struct HashMap
constexpr bool contains(const KeyType& key) const { return find_index(key) >= 0; }
template<typename KeyType> requires IsHashCompatible<Key, std::remove_cvref_t<KeyType>>
constexpr EffectiveValue& operator[](KeyType&& key)
constexpr EffectiveValue& operator[](KeyType&& key)
{
const auto hash = hash_value(key);
auto index = find_index(key, hash);

View File

@ -981,18 +981,14 @@ struct ShowWhitespacesHighlighter : Highlighter
bool only_trailing = (bool) parser.get_switch("only-trailing");
auto get_param = [&](StringView param, StringView fallback) {
StringView value = parser.get_switch(param).value_or(fallback);
if (value.char_length() != 1)
throw runtime_error{format("-{} expects a single character parameter", param)};
if (value.char_length() > 1)
throw runtime_error{format("-{} expects a single character or empty parameter", param)};
return value.str();
};
String indent = parser.get_switch("indent").value_or("").str();
if (indent.char_length() > 1)
throw runtime_error{format("-indent expects a single character or empty parameter")};
return std::make_unique<ShowWhitespacesHighlighter>(
get_param("tab", ""), get_param("tabpad", " "), get_param("spc", "·"),
get_param("lf", "¬"), get_param("nbsp", ""), indent, only_trailing);
get_param("lf", "¬"), get_param("nbsp", ""), get_param("indent", ""), only_trailing);
}
private:
@ -1043,28 +1039,28 @@ private:
if (it != end)
atom_it = line.split(atom_it, it.coord());
if (cp == '\t')
if (cp == '\t' and not m_tab.empty() and not m_tabpad.empty())
{
const ColumnCount column = get_column(buffer, tabstop, coord);
const ColumnCount count = tabstop - (column % tabstop);
atom_it->replace(m_tab + String(m_tabpad[(CharCount)0], count - m_tab.column_length()));
}
else if (cp == ' ') {
if (m_indent.empty() or indentwidth == 0 or not is_indentation) {
else if (cp == ' ' and is_indentation and indentwidth > 0 and not m_indent.empty()) {
const ColumnCount column = get_column(buffer, tabstop, coord);
if (column % indentwidth == 0 and column != 0) {
atom_it->replace(m_indent);
face = indentface;
}
else {
atom_it->replace(m_spc);
} else {
const ColumnCount column = get_column(buffer, tabstop, coord);
if (column % indentwidth == 0 and column != 0) {
atom_it->replace(m_indent);
face = indentface;
} else {
atom_it->replace(m_spc);
}
}
}
else if (cp == '\n')
else if (cp == ' ' and not m_spc.empty()) {
atom_it->replace(m_spc);
}
else if (cp == '\n' and not m_lf.empty())
atom_it->replace(m_lf);
else if (cp == 0xA0 or cp == 0x202F)
else if ((cp == 0xA0 or cp == 0x202F) and not m_nbsp.empty())
atom_it->replace(m_nbsp);
atom_it->face = merge_faces(atom_it->face, face);
break;

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,9 @@ 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"
}, {
@ -597,7 +600,8 @@ void register_options()
" terminal_shift_function_key int\n"
" terminal_padding_char codepoint\n"
" terminal_padding_fill bool\n"
" terminal_info_max_width int\n",
" terminal_info_max_width int\n"
" terminal_info_inline_borders bool\n",
UserInterface::Options{});
reg.declare_option("modelinefmt", "format string used to generate the modeline",
"%val{bufname} %val{cursor_line}:%val{cursor_char_column} {{context_info}} {{mode_info}} - %val{client}@[%val{session}]"_str);

View File

@ -589,13 +589,13 @@ void pipe(Context& context, NormalParams params)
return;
Buffer& buffer = context.buffer();
SelectionList selections = context.selections();
if (replace)
{
buffer.throw_if_read_only();
ScopedEdition edition(context);
ForwardChangesTracker changes_tracker;
size_t timestamp = buffer.timestamp();
Vector<Selection> new_sels;
SelectionList selections = context.selections();
for (auto& sel : selections)
{
const auto beg = changes_tracker.get_new_coord_tolerant(sel.min());
@ -614,20 +614,27 @@ void pipe(Context& context, NormalParams params)
auto new_end = apply_diff(buffer, beg, in, out);
if (new_end != beg)
new_sels.push_back(keep_direction({beg, buffer.char_prev(new_end), std::move(sel.captures())}, sel));
{
auto& min = sel.min();
auto& max = sel.max();
min = beg;
max = buffer.char_prev(new_end);
}
else
{
if (new_end != BufferCoord{})
new_end = buffer.char_prev(new_end);
new_sels.push_back({new_end, new_end, std::move(sel.captures())});
sel.set(new_end);
}
changes_tracker.update(buffer, timestamp);
}
context.selections_write_only().set(std::move(new_sels), selections.main_index());
selections.force_timestamp(timestamp);
context.selections_write_only() = std::move(selections);
}
else
{
SelectionList& selections = context.selections();
const auto old_main = selections.main_index();
for (int i = 0; i < selections.size(); ++i)
{

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,7 +478,8 @@ 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).get();
@ -486,7 +487,6 @@ private:
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

View File

@ -40,7 +40,7 @@ void HistoryRegister::set(Context& context, ConstArrayView<String> values, bool
return;
}
for (auto&& entry : values | reverse())
for (auto&& entry : values | reverse())
{
m_content.erase(std::remove(m_content.begin(), m_content.end(), entry), m_content.end());
m_content.insert(m_content.begin(), entry);

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

@ -202,7 +202,7 @@ InplaceString<23> to_string(Grouped val)
InplaceString<23> res;
for (int pos = 0, len = ungrouped.m_length; pos != len; ++pos)
{
if (res.m_length and ((len - pos) % 3) == 0)
if (res.m_length and ((len - pos) % 3) == 0)
res.m_data[res.m_length++] = ',';
res.m_data[res.m_length++] = ungrouped.m_data[pos];
}

View File

@ -144,10 +144,10 @@ decltype(auto) to_string(const StronglyTypedNumber<RealType, ValueType>& val)
namespace detail
{
template<typename T> requires std::is_convertible_v<T, StringView>
template<typename T> requires std::is_convertible_v<T, StringView>
StringView format_param(const T& val) { return val; }
template<typename T> requires (not std::is_convertible_v<T, StringView>)
template<typename T> requires (not std::is_convertible_v<T, StringView>)
decltype(auto) format_param(const T& val) { return to_string(val); }
}

View File

@ -377,7 +377,7 @@ void TerminalUI::Screen::output(bool force, bool synchronized, Writer& writer)
{
for (int line = 0; line < (int)size.line; ++line)
{
auto hash = hash_line(lines[line]);
auto hash = hash_line(lines[line]);
if (hash == hashes[line])
continue;
hashes[line] = hash;
@ -1300,7 +1300,7 @@ void TerminalUI::info_show(const DisplayLine& title, const DisplayLineList& cont
m_info.face = face;
m_info.style = style;
const bool framed = style == InfoStyle::Prompt or style == InfoStyle::Modal;
const bool framed = style == InfoStyle::Prompt or style == InfoStyle::Modal or m_info_inline_borders;
const bool assisted = style == InfoStyle::Prompt and m_assistant.size() != 0;
DisplayCoord max_size = m_dimensions;
@ -1555,6 +1555,7 @@ void TerminalUI::set_ui_options(const Options& options)
m_padding_fill = find("terminal_padding_fill").map(to_bool).value_or(false);
m_info_max_width = find("terminal_info_max_width").map(str_to_int_ifp).value_or(0);
m_info_inline_borders = find("terminal_info_inline_borders").map(to_bool).value_or(false);
}
}

View File

@ -175,6 +175,7 @@ private:
ColumnCount m_status_len = 0;
ColumnCount m_info_max_width = 0;
bool m_info_inline_borders = false;
};
}

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