From da2f6c296ae3ee280ffaca3937c6ae35c78d1e38 Mon Sep 17 00:00:00 2001 From: Frank LENORMAND Date: Tue, 23 Jul 2019 13:09:41 +0300 Subject: [PATCH] src: De-indent docstrings passed to command/option/mapping definitions 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 --- src/commands.cc | 8 ++++---- src/string_utils.cc | 38 ++++++++++++++++++++++++++++++++++++++ src/string_utils.hh | 1 + 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/commands.cc b/src/commands.cc index 779e8d6d..443e4b62 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -1163,9 +1163,9 @@ void define_command(const ParametersParser& parser, Context& context, const Shel }; } - auto docstring = trim_whitespaces(parser.get_switch("docstring").value_or(StringView{})); + auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{})); - cm.register_command(cmd_name, cmd, docstring.str(), desc, flags, CommandHelper{}, completer); + cm.register_command(cmd_name, cmd, docstring, desc, flags, CommandHelper{}, completer); } const CommandDesc define_command_cmd = { @@ -1590,7 +1590,7 @@ const CommandDesc declare_option_cmd = { if (parser.get_switch("hidden")) flags = OptionFlags::Hidden; - auto docstring = trim_whitespaces(parser.get_switch("docstring").value_or(StringView{})).str(); + auto docstring = trim_indent(parser.get_switch("docstring").value_or(StringView{})); OptionsRegistry& reg = GlobalScope::instance().option_registry(); @@ -1672,7 +1672,7 @@ const CommandDesc map_key_cmd = { KeyList mapping = parse_keys(parser[3]); keymaps.map_key(key[0], keymap_mode, std::move(mapping), - trim_whitespaces(parser.get_switch("docstring").value_or("")).str()); + trim_indent(parser.get_switch("docstring").value_or(""))); } }; diff --git a/src/string_utils.cc b/src/string_utils.cc index 3073a870..348526e9 100644 --- a/src/string_utils.cc +++ b/src/string_utils.cc @@ -19,6 +19,36 @@ StringView trim_whitespaces(StringView str) return {beg, end}; } +String trim_indent(StringView str) +{ + if (str.empty()) + return {}; + else if (str[0_byte] != '\n') + return trim_whitespaces(str).str(); + + str = str.substr(1_byte); + const CharCount docstring_length = str.char_length(); + + CharCount level_indent = 0; + while (level_indent < docstring_length + and is_horizontal_blank(str[level_indent])) + level_indent++; + + if (level_indent >= docstring_length or not level_indent) + return trim_whitespaces(str).str(); + + const auto str_indent = str.substr(0, level_indent); + auto s = str | split('\n') | transform([&](auto&& line) { + if (line.empty()) + return line; + else if (not prefix_match(line, str_indent)) + throw runtime_error("inconsistent indentation in the string"); + + return line.substr(str_indent.char_length()); + }); + + return trim_whitespaces(join(s, '\n', false)).str(); +} String escape(StringView str, StringView characters, char escape) { @@ -379,6 +409,14 @@ UnitTest test_string{[]() kak_assert(wrapped2[1] == "unknown"); kak_assert(wrapped2[2] == "type"); + kak_assert(trim_indent(" ") == ""); + kak_assert(trim_indent("no-indent") == "no-indent"); + kak_assert(trim_indent("\nno-indent") == "no-indent"); + kak_assert(trim_indent("\n indent\n indent") == "indent\nindent"); + kak_assert(trim_indent("\n indent\n indent") == "indent\n indent"); + + kak_expect_throw(runtime_error, trim_indent("\n indent\nno-indent")); + kak_assert(escape(R"(\youpi:matin:tchou\:)", ":\\", '\\') == R"(\\youpi\:matin\:tchou\\\:)"); kak_assert(unescape(R"(\\youpi\:matin\:tchou\\\:)", ":\\", '\\') == R"(\youpi:matin:tchou\:)"); diff --git a/src/string_utils.hh b/src/string_utils.hh index 928070d3..9c31d767 100644 --- a/src/string_utils.hh +++ b/src/string_utils.hh @@ -10,6 +10,7 @@ namespace Kakoune { StringView trim_whitespaces(StringView str); +String trim_indent(StringView str); String escape(StringView str, StringView characters, char escape); String unescape(StringView str, StringView characters, char escape);