From e964b68ab818a9c5be1c16f024ba346f3983a3e3 Mon Sep 17 00:00:00 2001 From: Maxime Coste Date: Tue, 12 Nov 2019 21:56:45 +1100 Subject: [PATCH] Add support for selecting and exporting selections in display columns Fixes #2724 --- doc/pages/commands.asciidoc | 8 +++- doc/pages/expansions.asciidoc | 6 +++ src/buffer_utils.cc | 5 ++- src/commands.cc | 13 ++++-- src/main.cc | 18 ++++++--- src/normal.cc | 5 ++- src/selection.cc | 76 +++++++++++++++++------------------ src/selection.hh | 22 +++++----- 8 files changed, 89 insertions(+), 64 deletions(-) diff --git a/doc/pages/commands.asciidoc b/doc/pages/commands.asciidoc index 94290fb3..be1403e5 100644 --- a/doc/pages/commands.asciidoc +++ b/doc/pages/commands.asciidoc @@ -347,8 +347,12 @@ but not really useful in that context. *-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). + + *-display-column*:: + provided columns are to be interpreted as display column counts, not byte counts. + + both *-codepoint* and *-display-column* are 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/doc/pages/expansions.asciidoc b/doc/pages/expansions.asciidoc index 1823560c..2dca3c61 100644 --- a/doc/pages/expansions.asciidoc +++ b/doc/pages/expansions.asciidoc @@ -317,6 +317,12 @@ The following expansions are supported (with required context _in italics_): `%val{selection_desc}`, except that the columns are in codepoints rather than bytes +*%val{selections_display_column_desc}*:: + _in window scope_ + + unquoted list of the ranges of all selections, in the same format as + `%val{selection_desc}`, except that the columns are in display columns rather + than bytes + *%val{selections_desc}*:: _in window scope_ + unquoted list of the ranges of all selections, in the same format as diff --git a/src/buffer_utils.cc b/src/buffer_utils.cc index 66f5436c..b6519d7d 100644 --- a/src/buffer_utils.cc +++ b/src/buffer_utils.cc @@ -163,8 +163,9 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll if (insert_coord != buffer->back_coord()) { - buffer->run_hook_in_own_context(Hook::BufReadFifo, - selection_to_string({insert_coord, buffer->back_coord()})); + buffer->run_hook_in_own_context( + Hook::BufReadFifo, + selection_to_string(ColumnType::Byte, *buffer, {insert_coord, buffer->back_coord()})); } if (closed) diff --git a/src/commands.cc b/src/commands.cc index 961a1352..cfacea99 100644 --- a/src/commands.cc +++ b/src/commands.cc @@ -2330,10 +2330,10 @@ const CommandDesc select_cmd = { "select ...: select given selections\n" "\n" "selection_desc format is .,.", - ParameterDesc{ - { + ParameterDesc{{ {"timestamp", {true, "specify buffer timestamp at which those selections are valid"}}, - {"codepoint", {false, "columns are specified in codepoints, not bytes"}} + {"codepoint", {false, "columns are specified in codepoints, not bytes"}}, + {"display-column", {false, "columns are specified in display columns, not bytes"}} }, ParameterDesc::Flags::SwitchesOnlyAtStart, 1 }, @@ -2344,7 +2344,12 @@ 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, (bool)parser.get_switch("codepoint"), parser.positionals_from(0), timestamp, 0); + ColumnType column_type = ColumnType::Byte; + if (parser.get_switch("codepoint")) + column_type = ColumnType::Codepoint; + else if (parser.get_switch("display-column")) + column_type = ColumnType::DisplayColumn; + context.selections_write_only() = selection_list_from_strings(buffer, column_type, parser.positionals_from(0), timestamp, 0); } }; diff --git a/src/main.cc b/src/main.cc index 41a507ad..fca72b3b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -248,15 +248,20 @@ static const EnvVarDesc builtin_env_vars[] = { { }, { "selection_desc", false, [](StringView name, const Context& context, Quoting quoting) - { return selection_to_string(context.selections().main()); } + { return selection_to_string(ColumnType::Byte, context.buffer(), + context.selections().main()); } }, { "selections_desc", false, [](StringView name, const Context& context, Quoting quoting) - { return selection_list_to_string(false, context.selections()); } + { return selection_list_to_string(ColumnType::Byte, context.selections()); } }, { "selections_char_desc", false, [](StringView name, const Context& context, Quoting quoting) - { return selection_list_to_string(true, context.selections()); } + { return selection_list_to_string(ColumnType::Codepoint, context.selections()); } + }, { + "selections_display_column_desc", false, + [](StringView name, const Context& context, Quoting quoting) + { return selection_list_to_string(ColumnType::DisplayColumn, context.selections()); } }, { "selection_length", false, [](StringView name, const Context& context, Quoting quoting) -> String @@ -795,9 +800,10 @@ int run_server(StringView session, StringView server_init, else if (convert_to_client_pending) { 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(false, local_client->context().selections()); + auto& local_context = local_client->context(); + const String client_name = local_context.name(); + const String buffer_name = local_context.buffer().name(); + const String selections = selection_list_to_string(ColumnType::Byte, local_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 24d30c8b..58dc9698 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, false, content | skip(1), timestamp, main); + return selection_list_from_strings(buffer, ColumnType::Byte, content | skip(1), timestamp, main); } enum class CombineOp @@ -1889,8 +1889,9 @@ void save_selections(Context& context, NormalParams params) auto save_to_reg = [reg](Context& context, const SelectionList& sels) { auto& buffer = context.buffer(); + auto to_string = [&] (const Selection& sel) { return selection_to_string(ColumnType::Byte, buffer, sel); }; auto descs = concatenated(ConstArrayView{format("{}@{}@{}", buffer.name(), buffer.timestamp(), sels.main_index())}, - sels | transform(selection_to_string)) | gather>(); + sels | transform(to_string)) | gather>(); RegisterManager::instance()[reg].set(context, descs); context.print_status({format("{} {} selections to register '{}'", diff --git a/src/selection.cc b/src/selection.cc index ef5ada64..0b29d3d2 100644 --- a/src/selection.cc +++ b/src/selection.cc @@ -472,31 +472,34 @@ void SelectionList::erase() m_buffer->check_invariant(); } -String selection_to_string(const Selection& selection) +String selection_to_string(ColumnType column_type, const Buffer& buffer, const Selection& selection) { const auto& cursor = selection.cursor(); const auto& anchor = selection.anchor(); - return format("{}.{},{}.{}", anchor.line + 1, anchor.column + 1, - cursor.line + 1, cursor.column + 1); + switch (column_type) + { + default: + case ColumnType::Byte: + return format("{}.{},{}.{}", anchor.line + 1, anchor.column + 1, + cursor.line + 1, cursor.column + 1); + case ColumnType::Codepoint: + return format("{}.{},{}.{}", + anchor.line + 1, buffer[anchor.line].char_count_to(anchor.column) + 1, + cursor.line + 1, buffer[cursor.line].char_count_to(cursor.column) + 1); + case ColumnType::DisplayColumn: + return format("{}.{},{}.{}", + anchor.line + 1, buffer[anchor.line].column_count_to(anchor.column) + 1, + cursor.line + 1, buffer[cursor.line].column_count_to(cursor.column) + 1); + } } -String selection_to_string_char(const Buffer& buffer, const Selection& selection) -{ - const auto& cursor = selection.cursor(); - const auto& anchor = selection.anchor(); - return format("{}.{},{}.{}", - anchor.line + 1, buffer[anchor.line].char_count_to(anchor.column) + 1, - cursor.line + 1, buffer[cursor.line].char_count_to(cursor.column) + 1); -} - -String selection_list_to_string(bool char_columns, const SelectionList& selections) +String selection_list_to_string(ColumnType column_type, 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); + return selection_to_string(column_type, buffer, selection); }; auto beg = &*selections.begin(), end = &*selections.end(); @@ -506,8 +509,7 @@ String selection_list_to_string(bool char_columns, const SelectionList& selectio transform(to_string), ' ', false); } -template -Selection selection_from_string_impl(StringView desc, ComputeCoord compute_coord) +Selection selection_from_string(ColumnType column_type, const Buffer& buffer, StringView desc) { auto comma = find(desc, ','); auto dot_anchor = find(StringView{desc.begin(), comma}, '.'); @@ -516,6 +518,24 @@ Selection selection_from_string_impl(StringView desc, ComputeCoord compute_coord if (comma == desc.end() or dot_anchor == comma or dot_cursor == desc.end()) throw runtime_error(format("'{}' does not follow .,. format", desc)); + auto compute_coord = [&](int line, int column) -> BufferCoord { + if (line < 0 or column < 0) + throw runtime_error(format("coordinate {}.{} does exist in buffer", line, column)); + + switch (column_type) + { + default: + case ColumnType::Byte: return {line, column}; + case ColumnType::Codepoint: + if (buffer.line_count() <= line or buffer[line].char_length() <= column) + throw runtime_error(format("coordinate {}.{} does exist in buffer", line, column)); + return {line, buffer[line].byte_count_to(CharCount{column})}; + case ColumnType::DisplayColumn: + if (buffer.line_count() <= line or buffer[line].column_length() <= column) + throw runtime_error(format("coordinate {}.{} does exist in buffer", line, column)); + return {line, buffer[line].byte_count_to(ColumnCount{column})}; + } + }; auto anchor = compute_coord(str_to_int({desc.begin(), dot_anchor}) - 1, str_to_int({dot_anchor+1, comma}) - 1); @@ -523,29 +543,7 @@ Selection selection_from_string_impl(StringView desc, ComputeCoord compute_coord 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) - throw runtime_error(format("coordinates must be >= 1: '{}'", 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 52681ba0..a674e785 100644 --- a/src/selection.hh +++ b/src/selection.hh @@ -156,22 +156,26 @@ 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); +enum class ColumnType +{ + Byte, + Codepoint, + DisplayColumn +}; -String selection_list_to_string(bool char_columns, const SelectionList& selections); +Selection selection_from_string(ColumnType column_type, const Buffer& buffer, StringView desc); +String selection_to_string(ColumnType column_type, const Buffer& buffer, const Selection& selection); + +String selection_list_to_string(ColumnType column_type, const SelectionList& selections); template -SelectionList selection_list_from_strings(Buffer& buffer, bool char_columns, StringArray&& descs, size_t timestamp, size_t main) +SelectionList selection_list_from_strings(Buffer& buffer, ColumnType column_type, StringArray&& descs, size_t timestamp, size_t main) { - if ((char_columns and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp()) + if ((column_type != ColumnType::Byte and timestamp != buffer.timestamp()) or timestamp > buffer.timestamp()) throw runtime_error{format("invalid timestamp '{}'", timestamp)}; auto from_string = [&](StringView desc) { - return char_columns ? selection_from_string_char(buffer, desc) - : selection_from_string(desc); + return selection_from_string(column_type, buffer, desc); }; auto sels = descs | transform(from_string) | gather>();