Support empty ranges in replace-ranges highlighter

This commit is contained in:
Maxime Coste 2020-04-06 11:13:24 +10:00
parent c585107ab5
commit f7a2ecfacb
4 changed files with 78 additions and 47 deletions

View File

@ -5,6 +5,8 @@ released versions.
== Development version == Development version
* replace-ranges highlighter now support empty ranges
* `%var{...}` now expands to list of strings, `$kak_quoted_...` now work * `%var{...}` now expands to list of strings, `$kak_quoted_...` now work
as expected with these. as expected with these.

View File

@ -88,19 +88,37 @@ are exclusively available to built-in options.
*range-specs*:: *range-specs*::
a timestamp (like `%val{timestamp}`, a timestamp (like `%val{timestamp}`,
see <<expansions#value-expansions,`:doc expansions value-expansions`>>) see <<expansions#value-expansions,`:doc expansions value-expansions`>>)
followed by a list of range descriptors. Each range descriptor must use followed by a list of range descriptors.
the syntax `a.b,c.d|string` or `a.b+length|string`, where _a_ is the line
containing the first character, _b_ is the number of bytes from the start Each range descriptor must use the syntax `a.b,c.d|string` or
of the line to the first byte of the first character, _c_ is the line `a.b+length|string`, with:
containing the last character, _d_ is the number of bytes from the start of
the line to the first byte of the last character, _length_ is the length of * _a_ is the line containing the first character
the range in bytes and _string_ is an arbitrary string which is associated
with the range. All numeric fields are 1-based. When the `update-option` is * _b_ is the number of bytes from the start of the line to the
used on an option of this type, its ranges gets updated according to all the first byte of the first character
buffer modifications that happened since its timestamp. See
<<highlighters#specs-highlighters,`:doc highlighters specs-highlighters`>>) * _c_ is the line containing the last character
* _d_ is the number of bytes from the start of the line to the
first byte of the last character
* _length_ is the length of the range in bytes, if 0 the range
is empty, but still valid.
* _string_ is an arbitrary string which is associated with
the range.
All numeric fields are 1-based.
When the `update-option` is used on an option of this type, its
ranges gets updated according to all the buffer modifications that
happened since its timestamp.
`set -add` appends the new pair to the list `set -add` appends the new pair to the list
See <<highlighters#specs-highlighters,`:doc highlighters specs-highlighters`>>)
*line-specs*:: *line-specs*::
a list of a line number and a corresponding flag (`<line>|<flag a list of a line number and a corresponding flag (`<line>|<flag
text>`), except for the first element which is just the timestamp text>`), except for the first element which is just the timestamp

View File

@ -28,6 +28,21 @@ struct ForwardChangesTracker
const Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last); const Buffer::Change* forward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);
const Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last); const Buffer::Change* backward_sorted_until(const Buffer::Change* first, const Buffer::Change* last);
template<typename Range, typename AdvanceFunc>
auto update_range(ForwardChangesTracker& changes_tracker, Range& range, AdvanceFunc&& advance_while_relevant)
{
auto& first = get_first(range);
auto& last = get_last(range);
advance_while_relevant(first);
first = changes_tracker.get_new_coord_tolerant(first);
if (last < BufferCoord{0,0})
return;
advance_while_relevant(last);
last = changes_tracker.get_new_coord_tolerant(last);
}
template<typename RangeContainer> template<typename RangeContainer>
void update_forward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges) void update_forward(ConstArrayView<Buffer::Change> changes, RangeContainer& ranges)
{ {
@ -39,15 +54,7 @@ void update_forward(ConstArrayView<Buffer::Change> changes, RangeContainer& rang
}; };
for (auto& range : ranges) for (auto& range : ranges)
{ update_range(changes_tracker, range, advance_while_relevant);
auto& first = get_first(range);
auto& last = get_last(range);
advance_while_relevant(first);
first = changes_tracker.get_new_coord_tolerant(first);
advance_while_relevant(last);
last = changes_tracker.get_new_coord_tolerant(last);
}
} }
template<typename RangeContainer> template<typename RangeContainer>
@ -69,15 +76,7 @@ void update_backward(ConstArrayView<Buffer::Change> changes, RangeContainer& ran
}; };
for (auto& range : ranges) for (auto& range : ranges)
{ update_range(changes_tracker, range, advance_while_relevant);
auto& first = get_first(range);
auto& last = get_last(range);
advance_while_relevant(first);
first = changes_tracker.get_new_coord_tolerant(first);
advance_while_relevant(last);
last = changes_tracker.get_new_coord_tolerant(last);
}
} }
template<typename RangeContainer> template<typename RangeContainer>

View File

@ -89,7 +89,7 @@ void replace_range(DisplayBuffer& display_buffer,
BufferCoord begin, BufferCoord end, T func) BufferCoord begin, BufferCoord end, T func)
{ {
// tolerate begin > end as that can be triggered by wrong encodings // tolerate begin > end as that can be triggered by wrong encodings
if (begin >= end or end <= display_buffer.range().begin if (begin > end or end <= display_buffer.range().begin
or begin >= display_buffer.range().end) or begin >= display_buffer.range().end)
return; return;
@ -112,7 +112,9 @@ void replace_range(DisplayBuffer& display_buffer,
atom_it = ++line.split(atom_it, begin); atom_it = ++line.split(atom_it, begin);
beg_idx = atom_it - line.begin(); beg_idx = atom_it - line.begin();
} }
if (end <= atom_it->end()) if (end == atom_it->begin())
end_idx = atom_it - line.begin();
else if (end <= atom_it->end())
{ {
if (end < atom_it->end()) if (end < atom_it->end())
atom_it = line.split(atom_it, end); atom_it = line.split(atom_it, end);
@ -1448,8 +1450,15 @@ private:
String m_default_face; String m_default_face;
}; };
bool is_empty(const InclusiveBufferRange& range)
{
return range.last < BufferCoord{0,0};
}
String option_to_string(InclusiveBufferRange range) String option_to_string(InclusiveBufferRange range)
{ {
if (is_empty(range))
return format("{}.{}+0", range.first.line+1, range.first.column+1);
return format("{}.{},{}.{}", return format("{}.{},{}.{}",
range.first.line+1, range.first.column+1, range.first.line+1, range.first.column+1,
range.last.line+1, range.last.column+1); range.last.line+1, range.last.column+1);
@ -1468,12 +1477,17 @@ InclusiveBufferRange option_from_string(Meta::Type<InclusiveBufferRange>, String
const BufferCoord first{str_to_int({str.begin(), dot_beg}) - 1, const BufferCoord first{str_to_int({str.begin(), dot_beg}) - 1,
str_to_int({dot_beg+1, sep}) - 1}; str_to_int({dot_beg+1, sep}) - 1};
const bool len = (*sep == '+'); if (first.line < 0 or first.column < 0)
const BufferCoord last{len ? first.line : str_to_int({sep+1, dot_end}) - 1, throw runtime_error("coordinates elements should be >= 1");
len ? first.column + str_to_int({sep+1, str.end()}) - 1
: str_to_int({dot_end+1, str.end()}) - 1 };
if (first.line < 0 or first.column < 0 or last.line < 0 or last.column < 0) if (*sep == '+')
{
auto len = str_to_int({sep+1, str.end()});
return {first, len == 0 ? BufferCoord{-1,-1} : BufferCoord{first.line, first.column + len}};
}
const BufferCoord last{str_to_int({sep+1, dot_end}) - 1, str_to_int({dot_end+1, str.end()}) - 1};
if (last.line < 0 or last.column < 0)
throw runtime_error("coordinates elements should be >= 1"); throw runtime_error("coordinates elements should be >= 1");
return { std::min(first, last), std::max(first, last) }; return { std::min(first, last), std::max(first, last) };
@ -1522,14 +1536,13 @@ private:
auto& range_and_faces = context.context.options()[m_option_name].get_mutable<RangeAndStringList>(); auto& range_and_faces = context.context.options()[m_option_name].get_mutable<RangeAndStringList>();
update_ranges(buffer, range_and_faces.prefix, range_and_faces.list); update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);
for (auto& range : range_and_faces.list) for (auto& [range, face] : range_and_faces.list)
{ {
try try
{ {
auto& r = std::get<0>(range); if (buffer.is_valid(range.first) and (buffer.is_valid(range.last) and not buffer.is_end(range.last)))
if (buffer.is_valid(r.first) and (buffer.is_valid(r.last) and not buffer.is_end(r.last))) highlight_range(display_buffer, range.first, buffer.char_next(range.last), false,
highlight_range(display_buffer, r.first, buffer.char_next(r.last), false, apply_face(context.context.faces()[face]));
apply_face(context.context.faces()[std::get<1>(range)]));
} }
catch (runtime_error&) catch (runtime_error&)
{} {}
@ -1564,15 +1577,14 @@ private:
auto& range_and_faces = context.context.options()[m_option_name].get_mutable<RangeAndStringList>(); auto& range_and_faces = context.context.options()[m_option_name].get_mutable<RangeAndStringList>();
update_ranges(buffer, range_and_faces.prefix, range_and_faces.list); update_ranges(buffer, range_and_faces.prefix, range_and_faces.list);
for (auto& range : range_and_faces.list) for (auto& [range, spec] : range_and_faces.list)
{ {
try try
{ {
auto& r = std::get<0>(range); if (buffer.is_valid(range.first) and (buffer.is_valid(range.last) or is_empty(range)))
if (buffer.is_valid(r.first) and buffer.is_valid(r.last))
{ {
auto replacement = parse_display_line(std::get<1>(range), context.context.faces()); auto replacement = parse_display_line(spec, context.context.faces());
replace_range(display_buffer, r.first, buffer.char_next(r.last), replace_range(display_buffer, range.first, is_empty(range) ? range.first : buffer.char_next(range.last),
[&](DisplayLine& line, int beg_idx, int end_idx){ [&](DisplayLine& line, int beg_idx, int end_idx){
auto it = line.erase(line.begin() + beg_idx, line.begin() + end_idx); auto it = line.erase(line.begin() + beg_idx, line.begin() + end_idx);
for (auto& atom : replacement) for (auto& atom : replacement)