Add support for selecting and exporting selections in display columns
Fixes #2724
This commit is contained in:
parent
7a8f57f97b
commit
e964b68ab8
|
@ -347,8 +347,12 @@ but not really useful in that context.
|
||||||
|
|
||||||
*-codepoint*::
|
*-codepoint*::
|
||||||
provided columns are to be interpreted as codepoint counts, not byte counts.
|
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}::
|
*debug* {info,buffers,options,memory,shared-strings,profile-hash-maps,faces,mappings}::
|
||||||
print some debug information in the *\*debug** buffer
|
print some debug information in the *\*debug** buffer
|
||||||
|
|
|
@ -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
|
`%val{selection_desc}`, except that the columns are in codepoints rather
|
||||||
than bytes
|
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}*::
|
*%val{selections_desc}*::
|
||||||
_in window scope_ +
|
_in window scope_ +
|
||||||
unquoted list of the ranges of all selections, in the same format as
|
unquoted list of the ranges of all selections, in the same format as
|
||||||
|
|
|
@ -163,8 +163,9 @@ Buffer* create_fifo_buffer(String name, int fd, Buffer::Flags flags, bool scroll
|
||||||
|
|
||||||
if (insert_coord != buffer->back_coord())
|
if (insert_coord != buffer->back_coord())
|
||||||
{
|
{
|
||||||
buffer->run_hook_in_own_context(Hook::BufReadFifo,
|
buffer->run_hook_in_own_context(
|
||||||
selection_to_string({insert_coord, buffer->back_coord()}));
|
Hook::BufReadFifo,
|
||||||
|
selection_to_string(ColumnType::Byte, *buffer, {insert_coord, buffer->back_coord()}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closed)
|
if (closed)
|
||||||
|
|
|
@ -2330,10 +2330,10 @@ const CommandDesc select_cmd = {
|
||||||
"select <selection_desc>...: select given selections\n"
|
"select <selection_desc>...: select given selections\n"
|
||||||
"\n"
|
"\n"
|
||||||
"selection_desc format is <anchor_line>.<anchor_column>,<cursor_line>.<cursor_column>",
|
"selection_desc format is <anchor_line>.<anchor_column>,<cursor_line>.<cursor_column>",
|
||||||
ParameterDesc{
|
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"}}
|
{"codepoint", {false, "columns are specified in codepoints, not bytes"}},
|
||||||
|
{"display-column", {false, "columns are specified in display columns, not bytes"}}
|
||||||
},
|
},
|
||||||
ParameterDesc::Flags::SwitchesOnlyAtStart, 1
|
ParameterDesc::Flags::SwitchesOnlyAtStart, 1
|
||||||
},
|
},
|
||||||
|
@ -2344,7 +2344,12 @@ const CommandDesc select_cmd = {
|
||||||
{
|
{
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
const size_t timestamp = parser.get_switch("timestamp").map(str_to_int_ifp).cast<size_t>().value_or(buffer.timestamp());
|
const size_t timestamp = parser.get_switch("timestamp").map(str_to_int_ifp).cast<size_t>().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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
18
src/main.cc
18
src/main.cc
|
@ -248,15 +248,20 @@ static const EnvVarDesc builtin_env_vars[] = { {
|
||||||
}, {
|
}, {
|
||||||
"selection_desc", false,
|
"selection_desc", false,
|
||||||
[](StringView name, const Context& context, Quoting quoting)
|
[](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,
|
"selections_desc", false,
|
||||||
[](StringView name, const Context& context, Quoting quoting)
|
[](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,
|
"selections_char_desc", false,
|
||||||
[](StringView name, const Context& context, Quoting quoting)
|
[](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,
|
"selection_length", false,
|
||||||
[](StringView name, const Context& context, Quoting quoting) -> String
|
[](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)
|
else if (convert_to_client_pending)
|
||||||
{
|
{
|
||||||
kak_assert(local_client);
|
kak_assert(local_client);
|
||||||
const String client_name = local_client->context().name();
|
auto& local_context = local_client->context();
|
||||||
const String buffer_name = local_client->context().buffer().name();
|
const String client_name = local_context.name();
|
||||||
const String selections = selection_list_to_string(false, local_client->context().selections());
|
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);
|
ClientManager::instance().remove_client(*local_client, true, 0);
|
||||||
client_manager.clear_client_trash();
|
client_manager.clear_client_trash();
|
||||||
|
|
|
@ -1774,7 +1774,7 @@ SelectionList read_selections_from_register(char reg, Context& context)
|
||||||
const size_t timestamp = str_to_int(desc[1]);
|
const size_t timestamp = str_to_int(desc[1]);
|
||||||
size_t main = str_to_int(desc[2]);
|
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
|
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 save_to_reg = [reg](Context& context, const SelectionList& sels) {
|
||||||
auto& buffer = context.buffer();
|
auto& buffer = context.buffer();
|
||||||
|
auto to_string = [&] (const Selection& sel) { return selection_to_string(ColumnType::Byte, buffer, sel); };
|
||||||
auto descs = concatenated(ConstArrayView<String>{format("{}@{}@{}", buffer.name(), buffer.timestamp(), sels.main_index())},
|
auto descs = concatenated(ConstArrayView<String>{format("{}@{}@{}", buffer.name(), buffer.timestamp(), sels.main_index())},
|
||||||
sels | transform(selection_to_string)) | gather<Vector<String>>();
|
sels | transform(to_string)) | gather<Vector<String>>();
|
||||||
RegisterManager::instance()[reg].set(context, descs);
|
RegisterManager::instance()[reg].set(context, descs);
|
||||||
|
|
||||||
context.print_status({format("{} {} selections to register '{}'",
|
context.print_status({format("{} {} selections to register '{}'",
|
||||||
|
|
|
@ -472,31 +472,34 @@ void SelectionList::erase()
|
||||||
m_buffer->check_invariant();
|
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& cursor = selection.cursor();
|
||||||
const auto& anchor = selection.anchor();
|
const auto& anchor = selection.anchor();
|
||||||
return format("{}.{},{}.{}", anchor.line + 1, anchor.column + 1,
|
switch (column_type)
|
||||||
cursor.line + 1, cursor.column + 1);
|
{
|
||||||
|
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)
|
String selection_list_to_string(ColumnType column_type, const SelectionList& selections)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
auto& buffer = selections.buffer();
|
auto& buffer = selections.buffer();
|
||||||
kak_assert(selections.timestamp() == buffer.timestamp());
|
kak_assert(selections.timestamp() == buffer.timestamp());
|
||||||
|
|
||||||
auto to_string = [&](const Selection& selection) {
|
auto to_string = [&](const Selection& selection) {
|
||||||
return char_columns ? selection_to_string_char(buffer, selection)
|
return selection_to_string(column_type, buffer, selection);
|
||||||
: selection_to_string(selection);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto beg = &*selections.begin(), end = &*selections.end();
|
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);
|
transform(to_string), ' ', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename ComputeCoord>
|
Selection selection_from_string(ColumnType column_type, const Buffer& buffer, StringView desc)
|
||||||
Selection selection_from_string_impl(StringView desc, ComputeCoord compute_coord)
|
|
||||||
{
|
{
|
||||||
auto comma = find(desc, ',');
|
auto comma = find(desc, ',');
|
||||||
auto dot_anchor = find(StringView{desc.begin(), comma}, '.');
|
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())
|
if (comma == desc.end() or dot_anchor == comma or dot_cursor == desc.end())
|
||||||
throw runtime_error(format("'{}' does not follow <line>.<column>,<line>.<column> format", desc));
|
throw runtime_error(format("'{}' does not follow <line>.<column>,<line>.<column> 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,
|
auto anchor = compute_coord(str_to_int({desc.begin(), dot_anchor}) - 1,
|
||||||
str_to_int({dot_anchor+1, comma}) - 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,
|
auto cursor = compute_coord(str_to_int({comma+1, dot_cursor}) - 1,
|
||||||
str_to_int({dot_cursor+1, desc.end()}) - 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};
|
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})};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,22 +156,26 @@ private:
|
||||||
|
|
||||||
Vector<Selection> compute_modified_ranges(const Buffer& buffer, size_t timestamp);
|
Vector<Selection> compute_modified_ranges(const Buffer& buffer, size_t timestamp);
|
||||||
|
|
||||||
Selection selection_from_string(StringView desc);
|
enum class ColumnType
|
||||||
Selection selection_from_string_char(const Buffer& buffer, StringView desc);
|
{
|
||||||
String selection_to_string(const Selection& selection);
|
Byte,
|
||||||
String selection_to_string_char(const Buffer& buffer, const Selection& selection);
|
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<typename StringArray>
|
template<typename StringArray>
|
||||||
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)};
|
throw runtime_error{format("invalid timestamp '{}'", timestamp)};
|
||||||
|
|
||||||
auto from_string = [&](StringView desc) {
|
auto from_string = [&](StringView desc) {
|
||||||
return char_columns ? selection_from_string_char(buffer, desc)
|
return selection_from_string(column_type, buffer, desc);
|
||||||
: selection_from_string(desc);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
auto sels = descs | transform(from_string) | gather<Vector<Selection>>();
|
auto sels = descs | transform(from_string) | gather<Vector<Selection>>();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user