diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc index 8a21ca3a..94290fb3 100644 --- a/doc/pages/commands.asciidoc +++ b/doc/pages/commands.asciidoc @@ -338,8 +338,17 @@ but not really useful in that context. set register *name* to *content*, each content parameter is assigned to a different string in the register. (See <>) -*select* .,....:: +*select* [] .,....:: replace the current selections with the ones described in the arguments + + *-timestamp* ::: + specify which buffer timestamp those coordinates apply to. Uses current + buffer timestamp if not specified. + + *-codepoint*:: + provided columns are to be interpreted as codepoint counts, not byte counts. + This is only valid if *-timestamp* matches the current buffer timestamp (or + is not specified). *debug* {info,buffers,options,memory,shared-strings,profile-hash-maps,faces,mappings}:: print some debug information in the *\*debug** buffer diff --git a/src/commands.cc b/src/commands.cc index 53257096..961a1352 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -2331,7 +2331,10 @@ const CommandDesc select_cmd = { "\n" "selection_desc format is .,.", ParameterDesc{ - {{"timestamp", {true, "specify buffer timestamp at which those selections are valid"}}}, + { + {"timestamp", {true, "specify buffer timestamp at which those selections are valid"}}, + {"codepoint", {false, "columns are specified in codepoints, not bytes"}} + }, ParameterDesc::Flags::SwitchesOnlyAtStart, 1 }, CommandFlags::None, @@ -2341,7 +2344,7 @@ const CommandDesc select_cmd = { { auto& buffer = context.buffer(); const size_t timestamp = parser.get_switch("timestamp").map(str_to_int_ifp).cast().value_or(buffer.timestamp()); - context.selections_write_only() = selection_list_from_strings(buffer, parser.positionals_from(0), timestamp, 0); + context.selections_write_only() = selection_list_from_strings(buffer, (bool)parser.get_switch("codepoint"), parser.positionals_from(0), timestamp, 0); } }; diff --git a/src/main.cc b/src/main.cc index 8ebd6f76..41a507ad 100644 --- a/src/main.cc +++ b/src/main.cc @@ -252,11 +252,11 @@ static const EnvVarDesc builtin_env_vars[] = { { }, { "selections_desc", false, [](StringView name, const Context& context, Quoting quoting) - { return selection_list_to_string(context.selections()); } + { return selection_list_to_string(false, context.selections()); } }, { "selections_char_desc", false, [](StringView name, const Context& context, Quoting quoting) - { return selection_list_to_string(context.selections()); } + { return selection_list_to_string(true, context.selections()); } }, { "selection_length", false, [](StringView name, const Context& context, Quoting quoting) -> String @@ -797,7 +797,7 @@ int run_server(StringView session, StringView server_init, kak_assert(local_client); const String client_name = local_client->context().name(); const String buffer_name = local_client->context().buffer().name(); - const String selections = selection_list_to_string(local_client->context().selections()); + const String selections = selection_list_to_string(false, local_client->context().selections()); ClientManager::instance().remove_client(*local_client, true, 0); client_manager.clear_client_trash(); diff --git a/src/normal.cc b/src/normal.cc index 84ca070b..24d30c8b 100644 --- a/src/normal.cc +++ b/src/normal.cc @@ -1774,7 +1774,7 @@ SelectionList read_selections_from_register(char reg, Context& context) const size_t timestamp = str_to_int(desc[1]); size_t main = str_to_int(desc[2]); - return selection_list_from_strings(buffer, content | skip(1), timestamp, main); + return selection_list_from_strings(buffer, false, content | skip(1), timestamp, main); } enum class CombineOp diff --git a/src/selection.cc b/src/selection.cc index 412d4f2d..ef5ada64 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -489,7 +489,25 @@ String selection_to_string_char(const Buffer& buffer, const Selection& selection cursor.line + 1, buffer[cursor.line].char_count_to(cursor.column) + 1); } -Selection selection_from_string(StringView desc) +String selection_list_to_string(bool char_columns, const SelectionList& selections) +{ + auto& buffer = selections.buffer(); + kak_assert(selections.timestamp() == buffer.timestamp()); + + auto to_string = [&](const Selection& selection) { + return char_columns ? selection_to_string_char(buffer, selection) + : selection_to_string(selection); + }; + + auto beg = &*selections.begin(), end = &*selections.end(); + auto main = beg + selections.main_index(); + using View = ConstArrayView; + return join(concatenated(View{main, end}, View{beg, main}) | + transform(to_string), ' ', false); +} + +template +Selection selection_from_string_impl(StringView desc, ComputeCoord compute_coord) { auto comma = find(desc, ','); auto dot_anchor = find(StringView{desc.begin(), comma}, '.'); @@ -498,11 +516,12 @@ Selection selection_from_string(StringView desc) if (comma == desc.end() or dot_anchor == comma or dot_cursor == desc.end()) throw runtime_error(format("'{}' does not follow .,. format", desc)); - BufferCoord anchor{str_to_int({desc.begin(), dot_anchor}) - 1, - str_to_int({dot_anchor+1, comma}) - 1}; - BufferCoord cursor{str_to_int({comma+1, dot_cursor}) - 1, - str_to_int({dot_cursor+1, desc.end()}) - 1}; + auto anchor = compute_coord(str_to_int({desc.begin(), dot_anchor}) - 1, + str_to_int({dot_anchor+1, comma}) - 1); + + auto cursor = compute_coord(str_to_int({comma+1, dot_cursor}) - 1, + str_to_int({dot_cursor+1, desc.end()}) - 1); if (anchor.line < 0 or anchor.column < 0 or cursor.line < 0 or cursor.column < 0) @@ -511,4 +530,22 @@ Selection selection_from_string(StringView desc) return Selection{anchor, cursor}; } +Selection selection_from_string(StringView desc) +{ + return selection_from_string_impl(desc, [](int line, int column) { + return BufferCoord{line, column}; + }); +} + +Selection selection_from_string_char(const Buffer& buffer, StringView desc) +{ + return selection_from_string_impl(desc, [&](int line, int column) { + if (line < 0 or buffer.line_count() <= line or + column < 0 or buffer[line].char_length() <= column) + throw runtime_error(format("coordinate {}.{} does exist in buffer", line, column)); + + return BufferCoord{line, buffer[line].byte_count_to(CharCount{column})}; + }); +} + } diff --git a/src/selection.hh b/src/selection.hh index 568b438a..52681ba0 100644 --- a/src/selection.hh +++ b/src/selection.hh @@ -157,34 +157,24 @@ private: Vector compute_modified_ranges(const Buffer& buffer, size_t timestamp); Selection selection_from_string(StringView desc); +Selection selection_from_string_char(const Buffer& buffer, StringView desc); String selection_to_string(const Selection& selection); String selection_to_string_char(const Buffer& buffer, const Selection& selection); -template -String selection_list_to_string(const SelectionList& selections) +String selection_list_to_string(bool char_columns, const SelectionList& selections); + +template +SelectionList selection_list_from_strings(Buffer& buffer, bool char_columns, StringArray&& descs, size_t timestamp, size_t main) { - auto& buffer = selections.buffer(); - kak_assert(selections.timestamp() == buffer.timestamp()); - - auto to_string = [&](const Selection& selection) { - return char_columns ? selection_to_string_char(buffer, selection) - : selection_to_string(selection); - }; - - auto beg = &*selections.begin(), end = &*selections.end(); - auto main = beg + selections.main_index(); - using View = ConstArrayView; - return join(concatenated(View{main, end}, View{beg, main}) | - transform(to_string), ' ', false); -} - -template -SelectionList selection_list_from_strings(Buffer& buffer, StringArray&& descs, size_t timestamp, size_t main) -{ - if (timestamp > buffer.timestamp()) + if ((char_columns and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp()) throw runtime_error{format("invalid timestamp '{}'", timestamp)}; - auto sels = descs | transform(selection_from_string) | gather>(); + auto from_string = [&](StringView desc) { + return char_columns ? selection_from_string_char(buffer, desc) + : selection_from_string(desc); + }; + + auto sels = descs | transform(from_string) | gather>(); if (sels.empty()) throw runtime_error{"empty selection description"}; if (main >= sels.size()) diff --git a/test/compose/select-codepoints/cmd b/test/compose/select-codepoints/cmd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/compose/select-codepoints/cmd @@ -0,0 +1 @@ + diff --git a/test/compose/select-codepoints/in b/test/compose/select-codepoints/in new file mode 100644 index 00000000..c7291cba --- /dev/null +++ b/test/compose/select-codepoints/in @@ -0,0 +1 @@ +😄😊😉😍😘😚😜😝😳😁😣😢😂😭😪😥😰😩 diff --git a/test/compose/select-codepoints/kak_selections_char_desc b/test/compose/select-codepoints/kak_selections_char_desc new file mode 100644 index 00000000..74669221 --- /dev/null +++ b/test/compose/select-codepoints/kak_selections_char_desc @@ -0,0 +1 @@ +1.2,1.4 diff --git a/test/compose/select-codepoints/kak_selections_desc b/test/compose/select-codepoints/kak_selections_desc new file mode 100644 index 00000000..761fc09a --- /dev/null +++ b/test/compose/select-codepoints/kak_selections_desc @@ -0,0 +1 @@ +1.5,1.13 diff --git a/test/compose/select-codepoints/rc b/test/compose/select-codepoints/rc new file mode 100644 index 00000000..0fb22c77 --- /dev/null +++ b/test/compose/select-codepoints/rc @@ -0,0 +1 @@ +select -codepoint 1.2,1.4