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);