mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 01:17:35 +00:00
LibSyntax+Userland: Make LibSyntax not depend on LibGUI
This moves some stuff around to make LibGUI depend on LibSyntax instead of the other way around, as not every application that wishes to do syntax highlighting is necessarily a LibGUI (or even a GUI) application.
This commit is contained in:
parent
2495302991
commit
ba4db899d4
26 changed files with 773 additions and 683 deletions
|
@ -1,4 +1,5 @@
|
|||
set(SOURCES
|
||||
Document.cpp
|
||||
Highlighter.cpp
|
||||
Language.cpp
|
||||
)
|
||||
|
|
389
Userland/Libraries/LibSyntax/Document.cpp
Normal file
389
Userland/Libraries/LibSyntax/Document.cpp
Normal file
|
@ -0,0 +1,389 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/CharacterTypes.h>
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/DeprecatedString.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/Utf8View.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibSyntax/Document.h>
|
||||
|
||||
namespace Syntax {
|
||||
|
||||
size_t TextDocumentLine::first_non_whitespace_column() const
|
||||
{
|
||||
for (size_t i = 0; i < length(); ++i) {
|
||||
auto code_point = code_points()[i];
|
||||
if (!is_ascii_space(code_point))
|
||||
return i;
|
||||
}
|
||||
return length();
|
||||
}
|
||||
|
||||
Optional<size_t> TextDocumentLine::last_non_whitespace_column() const
|
||||
{
|
||||
for (ssize_t i = length() - 1; i >= 0; --i) {
|
||||
auto code_point = code_points()[i];
|
||||
if (!is_ascii_space(code_point))
|
||||
return i;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool TextDocumentLine::ends_in_whitespace() const
|
||||
{
|
||||
if (!length())
|
||||
return false;
|
||||
return is_ascii_space(code_points()[length() - 1]);
|
||||
}
|
||||
|
||||
bool TextDocumentLine::can_select() const
|
||||
{
|
||||
if (is_empty())
|
||||
return false;
|
||||
for (size_t i = 0; i < length(); ++i) {
|
||||
auto code_point = code_points()[i];
|
||||
if (code_point != '\n' && code_point != '\r' && code_point != '\f' && code_point != '\v')
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t TextDocumentLine::leading_spaces() const
|
||||
{
|
||||
size_t count = 0;
|
||||
for (; count < m_text.size(); ++count) {
|
||||
if (m_text[count] != ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
DeprecatedString TextDocumentLine::to_utf8() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
builder.append(view());
|
||||
return builder.to_deprecated_string();
|
||||
}
|
||||
|
||||
TextDocumentLine::TextDocumentLine(Document& document)
|
||||
{
|
||||
clear(document);
|
||||
}
|
||||
|
||||
TextDocumentLine::TextDocumentLine(Document& document, StringView text)
|
||||
{
|
||||
set_text(document, text);
|
||||
}
|
||||
|
||||
void TextDocumentLine::clear(Document& document)
|
||||
{
|
||||
m_text.clear();
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::set_text(Document& document, Vector<u32> const text)
|
||||
{
|
||||
m_text = move(text);
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
bool TextDocumentLine::set_text(Document& document, StringView text)
|
||||
{
|
||||
if (text.is_empty()) {
|
||||
clear(document);
|
||||
return true;
|
||||
}
|
||||
m_text.clear();
|
||||
Utf8View utf8_view(text);
|
||||
if (!utf8_view.validate()) {
|
||||
return false;
|
||||
}
|
||||
for (auto code_point : utf8_view)
|
||||
m_text.append(code_point);
|
||||
document.update_views({});
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextDocumentLine::append(Document& document, u32 const* code_points, size_t length)
|
||||
{
|
||||
if (length == 0)
|
||||
return;
|
||||
m_text.append(code_points, length);
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::append(Document& document, u32 code_point)
|
||||
{
|
||||
insert(document, length(), code_point);
|
||||
}
|
||||
|
||||
void TextDocumentLine::prepend(Document& document, u32 code_point)
|
||||
{
|
||||
insert(document, 0, code_point);
|
||||
}
|
||||
|
||||
void TextDocumentLine::insert(Document& document, size_t index, u32 code_point)
|
||||
{
|
||||
if (index == length()) {
|
||||
m_text.append(code_point);
|
||||
} else {
|
||||
m_text.insert(index, code_point);
|
||||
}
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::remove(Document& document, size_t index)
|
||||
{
|
||||
if (index == length()) {
|
||||
m_text.take_last();
|
||||
} else {
|
||||
m_text.remove(index);
|
||||
}
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::remove_range(Document& document, size_t start, size_t length)
|
||||
{
|
||||
VERIFY(length <= m_text.size());
|
||||
|
||||
Vector<u32> new_data;
|
||||
new_data.ensure_capacity(m_text.size() - length);
|
||||
for (size_t i = 0; i < start; ++i)
|
||||
new_data.append(m_text[i]);
|
||||
for (size_t i = (start + length); i < m_text.size(); ++i)
|
||||
new_data.append(m_text[i]);
|
||||
m_text = move(new_data);
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::keep_range(Document& document, size_t start_index, size_t length)
|
||||
{
|
||||
VERIFY(start_index + length < m_text.size());
|
||||
|
||||
Vector<u32> new_data;
|
||||
new_data.ensure_capacity(m_text.size());
|
||||
for (size_t i = start_index; i <= (start_index + length); i++)
|
||||
new_data.append(m_text[i]);
|
||||
|
||||
m_text = move(new_data);
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
void TextDocumentLine::truncate(Document& document, size_t length)
|
||||
{
|
||||
m_text.resize(length);
|
||||
document.update_views({});
|
||||
}
|
||||
|
||||
TextDocumentSpan const* Document::span_at(TextPosition const& position) const
|
||||
{
|
||||
for (auto& span : m_spans) {
|
||||
if (span.range.contains(position))
|
||||
return &span;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Document::set_spans(u32 span_collection_index, Vector<TextDocumentSpan> spans)
|
||||
{
|
||||
m_span_collections.set(span_collection_index, move(spans));
|
||||
merge_span_collections();
|
||||
}
|
||||
|
||||
struct SpanAndCollectionIndex {
|
||||
TextDocumentSpan span;
|
||||
u32 collection_index { 0 };
|
||||
};
|
||||
|
||||
void Document::merge_span_collections()
|
||||
{
|
||||
Vector<SpanAndCollectionIndex> sorted_spans;
|
||||
auto collection_indices = m_span_collections.keys();
|
||||
quick_sort(collection_indices);
|
||||
|
||||
for (auto collection_index : collection_indices) {
|
||||
auto spans = m_span_collections.get(collection_index).value();
|
||||
for (auto span : spans) {
|
||||
sorted_spans.append({ move(span), collection_index });
|
||||
}
|
||||
}
|
||||
|
||||
quick_sort(sorted_spans, [](SpanAndCollectionIndex const& a, SpanAndCollectionIndex const& b) {
|
||||
if (a.span.range.start() == b.span.range.start()) {
|
||||
return a.collection_index < b.collection_index;
|
||||
}
|
||||
return a.span.range.start() < b.span.range.start();
|
||||
});
|
||||
|
||||
// The end of the TextRanges of spans are non-inclusive, i.e span range = [X,y).
|
||||
// This transforms the span's range to be inclusive, i.e [X,Y].
|
||||
auto adjust_end = [](TextDocumentSpan span) -> TextDocumentSpan {
|
||||
span.range.set_end({ span.range.end().line(), span.range.end().column() == 0 ? 0 : span.range.end().column() - 1 });
|
||||
return span;
|
||||
};
|
||||
|
||||
Vector<SpanAndCollectionIndex> merged_spans;
|
||||
for (auto& span_and_collection_index : sorted_spans) {
|
||||
if (merged_spans.is_empty()) {
|
||||
merged_spans.append(span_and_collection_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const& span = span_and_collection_index.span;
|
||||
auto last_span_and_collection_index = merged_spans.last();
|
||||
auto const& last_span = last_span_and_collection_index.span;
|
||||
|
||||
if (adjust_end(span).range.start() > adjust_end(last_span).range.end()) {
|
||||
// Current span does not intersect with previous one, can simply append to merged list.
|
||||
merged_spans.append(span_and_collection_index);
|
||||
continue;
|
||||
}
|
||||
merged_spans.take_last();
|
||||
|
||||
if (span.range.start() > last_span.range.start()) {
|
||||
SpanAndCollectionIndex first_part = last_span_and_collection_index;
|
||||
first_part.span.range.set_end(span.range.start());
|
||||
merged_spans.append(move(first_part));
|
||||
}
|
||||
|
||||
SpanAndCollectionIndex merged_span;
|
||||
merged_span.collection_index = span_and_collection_index.collection_index;
|
||||
merged_span.span.range = { span.range.start(), min(span.range.end(), last_span.range.end()) };
|
||||
merged_span.span.is_skippable = span.is_skippable | last_span.is_skippable;
|
||||
merged_span.span.data = span.data ? span.data : last_span.data;
|
||||
merged_span.span.attributes.color = span_and_collection_index.collection_index > last_span_and_collection_index.collection_index ? span.attributes.color : last_span.attributes.color;
|
||||
merged_span.span.attributes.bold = span.attributes.bold | last_span.attributes.bold;
|
||||
merged_span.span.attributes.background_color = span.attributes.background_color.has_value() ? span.attributes.background_color.value() : last_span.attributes.background_color;
|
||||
merged_span.span.attributes.underline_color = span.attributes.underline_color.has_value() ? span.attributes.underline_color.value() : last_span.attributes.underline_color;
|
||||
merged_span.span.attributes.underline_style = span.attributes.underline_style.has_value() ? span.attributes.underline_style : last_span.attributes.underline_style;
|
||||
merged_spans.append(move(merged_span));
|
||||
|
||||
if (span.range.end() == last_span.range.end())
|
||||
continue;
|
||||
|
||||
if (span.range.end() > last_span.range.end()) {
|
||||
SpanAndCollectionIndex last_part = span_and_collection_index;
|
||||
last_part.span.range.set_start(last_span.range.end());
|
||||
merged_spans.append(move(last_part));
|
||||
continue;
|
||||
}
|
||||
|
||||
SpanAndCollectionIndex last_part = last_span_and_collection_index;
|
||||
last_part.span.range.set_start(span.range.end());
|
||||
merged_spans.append(move(last_part));
|
||||
}
|
||||
|
||||
m_spans.clear();
|
||||
TextDocumentSpan previous_span { .range = { TextPosition(0, 0), TextPosition(0, 0) }, .attributes = {} };
|
||||
for (auto span : merged_spans) {
|
||||
// Validate spans
|
||||
if (!span.span.range.is_valid()) {
|
||||
dbgln_if(TEXTEDITOR_DEBUG, "Invalid span {} => ignoring", span.span.range);
|
||||
continue;
|
||||
}
|
||||
if (span.span.range.end() < span.span.range.start()) {
|
||||
dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", span.span.range);
|
||||
continue;
|
||||
}
|
||||
if (span.span.range.end() < previous_span.range.start()) {
|
||||
dbgln_if(TEXTEDITOR_DEBUG, "Spans not sorted (Span {} ends before previous span {}) => ignoring", span.span.range, previous_span.range);
|
||||
continue;
|
||||
}
|
||||
if (span.span.range.start() < previous_span.range.end()) {
|
||||
dbgln_if(TEXTEDITOR_DEBUG, "Span {} overlaps previous span {} => ignoring", span.span.range, previous_span.range);
|
||||
continue;
|
||||
}
|
||||
|
||||
previous_span = span.span;
|
||||
m_spans.append(move(span.span));
|
||||
}
|
||||
}
|
||||
|
||||
void Document::set_folding_regions(Vector<TextDocumentFoldingRegion> folding_regions)
|
||||
{
|
||||
// Remove any regions that don't span at least 3 lines.
|
||||
// Currently, we can't do anything useful with them, and our implementation gets very confused by
|
||||
// single-line regions, so drop them.
|
||||
folding_regions.remove_all_matching([](TextDocumentFoldingRegion const& region) {
|
||||
return region.range.line_count() < 3;
|
||||
});
|
||||
|
||||
quick_sort(folding_regions, [](TextDocumentFoldingRegion const& a, TextDocumentFoldingRegion const& b) {
|
||||
return a.range.start() < b.range.start();
|
||||
});
|
||||
|
||||
for (auto& folding_region : folding_regions) {
|
||||
folding_region.line_ptr = &line(folding_region.range.start().line());
|
||||
|
||||
// Map the new folding region to an old one, to preserve which regions were folded.
|
||||
// FIXME: This is O(n*n).
|
||||
for (auto const& existing_folding_region : m_folding_regions) {
|
||||
// We treat two folding regions as the same if they start on the same TextDocumentLine,
|
||||
// and have the same line count. The actual line *numbers* might change, but the pointer
|
||||
// and count should not.
|
||||
if (existing_folding_region.line_ptr
|
||||
&& existing_folding_region.line_ptr == folding_region.line_ptr
|
||||
&& existing_folding_region.range.line_count() == folding_region.range.line_count()) {
|
||||
folding_region.is_folded = existing_folding_region.is_folded;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Remove any regions that partially overlap another region, since these are invalid.
|
||||
|
||||
m_folding_regions = move(folding_regions);
|
||||
|
||||
if constexpr (TEXTEDITOR_DEBUG) {
|
||||
dbgln("Document got {} fold regions:", m_folding_regions.size());
|
||||
for (auto const& item : m_folding_regions) {
|
||||
dbgln("- {} (ptr: {:p}, folded: {})", item.range, item.line_ptr, item.is_folded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Optional<TextDocumentFoldingRegion&> Document::folding_region_starting_on_line(size_t line)
|
||||
{
|
||||
return m_folding_regions.first_matching([line](auto& region) {
|
||||
return region.range.start().line() == line;
|
||||
});
|
||||
}
|
||||
|
||||
bool Document::line_is_visible(size_t line) const
|
||||
{
|
||||
// FIXME: line_is_visible() gets called a lot.
|
||||
// We could avoid a lot of repeated work if we saved this state on the TextDocumentLine.
|
||||
return !any_of(m_folding_regions, [line](auto& region) {
|
||||
return region.is_folded
|
||||
&& line > region.range.start().line()
|
||||
&& line < region.range.end().line();
|
||||
});
|
||||
}
|
||||
|
||||
Vector<TextDocumentFoldingRegion const&> Document::currently_folded_regions() const
|
||||
{
|
||||
Vector<TextDocumentFoldingRegion const&> folded_regions;
|
||||
|
||||
for (auto& region : m_folding_regions) {
|
||||
if (region.is_folded) {
|
||||
// Only add this region if it's not contained within a previous folded region.
|
||||
// Because regions are sorted by their start position, and regions cannot partially overlap,
|
||||
// we can just see if it starts inside the last region we appended.
|
||||
if (!folded_regions.is_empty() && folded_regions.last().range.contains(region.range.start()))
|
||||
continue;
|
||||
|
||||
folded_regions.append(region);
|
||||
}
|
||||
}
|
||||
|
||||
return folded_regions;
|
||||
}
|
||||
|
||||
}
|
101
Userland/Libraries/LibSyntax/Document.h
Normal file
101
Userland/Libraries/LibSyntax/Document.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/Utf32View.h>
|
||||
#include <LibGfx/TextAttributes.h>
|
||||
#include <LibSyntax/Forward.h>
|
||||
#include <LibSyntax/TextRange.h>
|
||||
|
||||
namespace Syntax {
|
||||
|
||||
struct TextDocumentSpan {
|
||||
TextRange range;
|
||||
Gfx::TextAttributes attributes;
|
||||
u64 data { 0 };
|
||||
bool is_skippable { false };
|
||||
};
|
||||
|
||||
struct TextDocumentFoldingRegion {
|
||||
TextRange range;
|
||||
bool is_folded { false };
|
||||
// This pointer is only used to identify that two TDFRs are the same.
|
||||
RawPtr<class TextDocumentLine> line_ptr;
|
||||
};
|
||||
|
||||
class TextDocumentLine {
|
||||
public:
|
||||
explicit TextDocumentLine(Document&);
|
||||
explicit TextDocumentLine(Document&, StringView);
|
||||
|
||||
DeprecatedString to_utf8() const;
|
||||
|
||||
Utf32View view() const { return { code_points(), length() }; }
|
||||
u32 const* code_points() const { return m_text.data(); }
|
||||
size_t length() const { return m_text.size(); }
|
||||
bool set_text(Document&, StringView);
|
||||
void set_text(Document&, Vector<u32>);
|
||||
void append(Document&, u32);
|
||||
void prepend(Document&, u32);
|
||||
void insert(Document&, size_t index, u32);
|
||||
void remove(Document&, size_t index);
|
||||
void append(Document&, u32 const*, size_t);
|
||||
void truncate(Document&, size_t length);
|
||||
void clear(Document&);
|
||||
void remove_range(Document&, size_t start, size_t length);
|
||||
void keep_range(Document&, size_t start_index, size_t end_index);
|
||||
|
||||
size_t first_non_whitespace_column() const;
|
||||
Optional<size_t> last_non_whitespace_column() const;
|
||||
bool ends_in_whitespace() const;
|
||||
bool can_select() const;
|
||||
bool is_empty() const { return length() == 0; }
|
||||
size_t leading_spaces() const;
|
||||
|
||||
private:
|
||||
// NOTE: This vector is null terminated.
|
||||
Vector<u32> m_text;
|
||||
};
|
||||
|
||||
class Document : public RefCounted<Document> {
|
||||
public:
|
||||
Document() = default;
|
||||
virtual ~Document() = default;
|
||||
|
||||
void set_spans(u32 span_collection_index, Vector<TextDocumentSpan> spans);
|
||||
bool has_spans() const { return !m_spans.is_empty(); }
|
||||
Vector<TextDocumentSpan>& spans() { return m_spans; }
|
||||
Vector<TextDocumentSpan> const& spans() const { return m_spans; }
|
||||
void set_span_at_index(size_t index, TextDocumentSpan span) { m_spans[index] = move(span); }
|
||||
TextDocumentSpan const* span_at(TextPosition const&) const;
|
||||
|
||||
void set_folding_regions(Vector<TextDocumentFoldingRegion>);
|
||||
bool has_folding_regions() const { return !m_folding_regions.is_empty(); }
|
||||
Vector<TextDocumentFoldingRegion>& folding_regions() { return m_folding_regions; }
|
||||
Vector<TextDocumentFoldingRegion> const& folding_regions() const { return m_folding_regions; }
|
||||
Optional<TextDocumentFoldingRegion&> folding_region_starting_on_line(size_t line);
|
||||
// Returns all folded FoldingRegions that are not contained inside another folded region.
|
||||
Vector<TextDocumentFoldingRegion const&> currently_folded_regions() const;
|
||||
// Returns true if any part of the line is currently visible. (Not inside a folded FoldingRegion.)
|
||||
bool line_is_visible(size_t line) const;
|
||||
|
||||
virtual TextDocumentLine const& line(size_t line_index) const = 0;
|
||||
virtual TextDocumentLine& line(size_t line_index) = 0;
|
||||
|
||||
virtual void update_views(Badge<TextDocumentLine>) = 0;
|
||||
|
||||
protected:
|
||||
HashMap<u32, Vector<TextDocumentSpan>> m_span_collections;
|
||||
Vector<TextDocumentSpan> m_spans;
|
||||
Vector<TextDocumentFoldingRegion> m_folding_regions;
|
||||
|
||||
private:
|
||||
void merge_span_collections();
|
||||
};
|
||||
|
||||
}
|
|
@ -8,7 +8,10 @@
|
|||
|
||||
namespace Syntax {
|
||||
|
||||
class Document;
|
||||
class Highlighter;
|
||||
class HighlighterClient;
|
||||
class TextPosition;
|
||||
class TextRange;
|
||||
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibGUI/TextDocument.h>
|
||||
#include <LibGfx/Palette.h>
|
||||
#include <LibSyntax/Document.h>
|
||||
#include <LibSyntax/HighlighterClient.h>
|
||||
#include <LibSyntax/Language.h>
|
||||
|
||||
|
@ -61,7 +61,7 @@ protected:
|
|||
|
||||
struct BuddySpan {
|
||||
int index { -1 };
|
||||
GUI::TextDocumentSpan span_backup;
|
||||
TextDocumentSpan span_backup;
|
||||
};
|
||||
|
||||
bool m_has_brace_buddies { false };
|
||||
|
@ -71,7 +71,7 @@ protected:
|
|||
|
||||
class ProxyHighlighterClient final : public Syntax::HighlighterClient {
|
||||
public:
|
||||
ProxyHighlighterClient(Syntax::HighlighterClient& client, GUI::TextPosition start, u64 nested_kind_start_value, StringView source)
|
||||
ProxyHighlighterClient(Syntax::HighlighterClient& client, TextPosition start, u64 nested_kind_start_value, StringView source)
|
||||
: m_document(client.get_document())
|
||||
, m_text(source)
|
||||
, m_start(start)
|
||||
|
@ -79,9 +79,9 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
Vector<GUI::TextDocumentSpan> corrected_spans() const
|
||||
Vector<TextDocumentSpan> corrected_spans() const
|
||||
{
|
||||
Vector<GUI::TextDocumentSpan> spans { m_spans };
|
||||
Vector<TextDocumentSpan> spans { m_spans };
|
||||
for (auto& entry : spans) {
|
||||
entry.range.start() = {
|
||||
entry.range.start().line() + m_start.line(),
|
||||
|
@ -98,9 +98,9 @@ public:
|
|||
return spans;
|
||||
}
|
||||
|
||||
Vector<GUI::TextDocumentFoldingRegion> corrected_folding_regions() const
|
||||
Vector<TextDocumentFoldingRegion> corrected_folding_regions() const
|
||||
{
|
||||
Vector<GUI::TextDocumentFoldingRegion> folding_regions { m_folding_regions };
|
||||
Vector<TextDocumentFoldingRegion> folding_regions { m_folding_regions };
|
||||
for (auto& entry : folding_regions) {
|
||||
entry.range.start() = {
|
||||
entry.range.start().line() + m_start.line(),
|
||||
|
@ -125,24 +125,24 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
virtual Vector<GUI::TextDocumentSpan> const& spans() const override { return m_spans; }
|
||||
virtual void set_span_at_index(size_t index, GUI::TextDocumentSpan span) override { m_spans.at(index) = move(span); }
|
||||
virtual Vector<TextDocumentSpan> const& spans() const override { return m_spans; }
|
||||
virtual void set_span_at_index(size_t index, TextDocumentSpan span) override { m_spans.at(index) = move(span); }
|
||||
|
||||
virtual Vector<GUI::TextDocumentFoldingRegion>& folding_regions() override { return m_folding_regions; }
|
||||
virtual Vector<GUI::TextDocumentFoldingRegion> const& folding_regions() const override { return m_folding_regions; }
|
||||
virtual Vector<TextDocumentFoldingRegion>& folding_regions() override { return m_folding_regions; }
|
||||
virtual Vector<TextDocumentFoldingRegion> const& folding_regions() const override { return m_folding_regions; }
|
||||
|
||||
virtual DeprecatedString highlighter_did_request_text() const override { return m_text; }
|
||||
virtual void highlighter_did_request_update() override { }
|
||||
virtual GUI::TextDocument& highlighter_did_request_document() override { return m_document; }
|
||||
virtual GUI::TextPosition highlighter_did_request_cursor() const override { return {}; }
|
||||
virtual void highlighter_did_set_spans(Vector<GUI::TextDocumentSpan> spans) override { m_spans = move(spans); }
|
||||
virtual void highlighter_did_set_folding_regions(Vector<GUI::TextDocumentFoldingRegion> folding_regions) override { m_folding_regions = folding_regions; }
|
||||
virtual Document& highlighter_did_request_document() override { return m_document; }
|
||||
virtual TextPosition highlighter_did_request_cursor() const override { return {}; }
|
||||
virtual void highlighter_did_set_spans(Vector<TextDocumentSpan> spans) override { m_spans = move(spans); }
|
||||
virtual void highlighter_did_set_folding_regions(Vector<TextDocumentFoldingRegion> folding_regions) override { m_folding_regions = folding_regions; }
|
||||
|
||||
Vector<GUI::TextDocumentSpan> m_spans;
|
||||
Vector<GUI::TextDocumentFoldingRegion> m_folding_regions;
|
||||
GUI::TextDocument& m_document;
|
||||
Vector<TextDocumentSpan> m_spans;
|
||||
Vector<TextDocumentFoldingRegion> m_folding_regions;
|
||||
Document& m_document;
|
||||
StringView m_text;
|
||||
GUI::TextPosition m_start;
|
||||
TextPosition m_start;
|
||||
u64 m_nested_kind_start_value { 0 };
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -8,8 +9,8 @@
|
|||
|
||||
#include <AK/DeprecatedString.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibGUI/TextDocument.h>
|
||||
#include <LibGUI/TextPosition.h>
|
||||
#include <LibSyntax/Document.h>
|
||||
#include <LibSyntax/TextPosition.h>
|
||||
|
||||
namespace Syntax {
|
||||
|
||||
|
@ -17,27 +18,27 @@ class HighlighterClient {
|
|||
public:
|
||||
virtual ~HighlighterClient() = default;
|
||||
|
||||
virtual Vector<GUI::TextDocumentSpan> const& spans() const = 0;
|
||||
virtual void set_span_at_index(size_t index, GUI::TextDocumentSpan span) = 0;
|
||||
virtual Vector<TextDocumentSpan> const& spans() const = 0;
|
||||
virtual void set_span_at_index(size_t index, TextDocumentSpan span) = 0;
|
||||
virtual void clear_spans() { do_set_spans({}); }
|
||||
|
||||
virtual Vector<GUI::TextDocumentFoldingRegion>& folding_regions() = 0;
|
||||
virtual Vector<GUI::TextDocumentFoldingRegion> const& folding_regions() const = 0;
|
||||
virtual Vector<TextDocumentFoldingRegion>& folding_regions() = 0;
|
||||
virtual Vector<TextDocumentFoldingRegion> const& folding_regions() const = 0;
|
||||
|
||||
virtual DeprecatedString highlighter_did_request_text() const = 0;
|
||||
virtual void highlighter_did_request_update() = 0;
|
||||
virtual GUI::TextDocument& highlighter_did_request_document() = 0;
|
||||
virtual GUI::TextPosition highlighter_did_request_cursor() const = 0;
|
||||
virtual void highlighter_did_set_spans(Vector<GUI::TextDocumentSpan>) = 0;
|
||||
virtual void highlighter_did_set_folding_regions(Vector<GUI::TextDocumentFoldingRegion>) = 0;
|
||||
virtual Document& highlighter_did_request_document() = 0;
|
||||
virtual TextPosition highlighter_did_request_cursor() const = 0;
|
||||
virtual void highlighter_did_set_spans(Vector<TextDocumentSpan>) = 0;
|
||||
virtual void highlighter_did_set_folding_regions(Vector<TextDocumentFoldingRegion>) = 0;
|
||||
|
||||
void do_set_spans(Vector<GUI::TextDocumentSpan> spans) { highlighter_did_set_spans(move(spans)); }
|
||||
void do_set_folding_regions(Vector<GUI::TextDocumentFoldingRegion> folding_regions) { highlighter_did_set_folding_regions(move(folding_regions)); }
|
||||
void do_set_spans(Vector<TextDocumentSpan> spans) { highlighter_did_set_spans(move(spans)); }
|
||||
void do_set_folding_regions(Vector<TextDocumentFoldingRegion> folding_regions) { highlighter_did_set_folding_regions(move(folding_regions)); }
|
||||
void do_update() { highlighter_did_request_update(); }
|
||||
|
||||
DeprecatedString get_text() const { return highlighter_did_request_text(); }
|
||||
GUI::TextDocument& get_document() { return highlighter_did_request_document(); }
|
||||
GUI::TextPosition get_cursor() const { return highlighter_did_request_cursor(); }
|
||||
Document& get_document() { return highlighter_did_request_document(); }
|
||||
TextPosition get_cursor() const { return highlighter_did_request_cursor(); }
|
||||
|
||||
static constexpr auto span_collection_index = 0;
|
||||
};
|
||||
|
|
53
Userland/Libraries/LibSyntax/TextPosition.h
Normal file
53
Userland/Libraries/LibSyntax/TextPosition.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Format.h>
|
||||
|
||||
namespace Syntax {
|
||||
|
||||
class TextPosition {
|
||||
public:
|
||||
TextPosition() = default;
|
||||
TextPosition(size_t line, size_t column)
|
||||
: m_line(line)
|
||||
, m_column(column)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_valid() const { return m_line != 0xffffffffu && m_column != 0xffffffffu; }
|
||||
|
||||
size_t line() const { return m_line; }
|
||||
size_t column() const { return m_column; }
|
||||
|
||||
void set_line(size_t line) { m_line = line; }
|
||||
void set_column(size_t column) { m_column = column; }
|
||||
|
||||
bool operator==(TextPosition const& other) const { return m_line == other.m_line && m_column == other.m_column; }
|
||||
bool operator!=(TextPosition const& other) const { return m_line != other.m_line || m_column != other.m_column; }
|
||||
bool operator<(TextPosition const& other) const { return m_line < other.m_line || (m_line == other.m_line && m_column < other.m_column); }
|
||||
bool operator>(TextPosition const& other) const { return *this != other && !(*this < other); }
|
||||
bool operator>=(TextPosition const& other) const { return *this > other || (*this == other); }
|
||||
|
||||
private:
|
||||
size_t m_line { 0xffffffff };
|
||||
size_t m_column { 0xffffffff };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Syntax::TextPosition> : AK::Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Syntax::TextPosition const& value)
|
||||
{
|
||||
if (value.is_valid())
|
||||
return Formatter<FormatString>::format(builder, "({},{})"sv, value.line(), value.column());
|
||||
|
||||
return builder.put_string("TextPosition(Invalid)"sv);
|
||||
}
|
||||
};
|
80
Userland/Libraries/LibSyntax/TextRange.h
Normal file
80
Userland/Libraries/LibSyntax/TextRange.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022-2023, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibSyntax/TextPosition.h>
|
||||
|
||||
namespace Syntax {
|
||||
|
||||
class TextRange {
|
||||
public:
|
||||
TextRange() = default;
|
||||
TextRange(TextPosition const& start, TextPosition const& end)
|
||||
: m_start(start)
|
||||
, m_end(end)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_valid() const { return m_start.is_valid() && m_end.is_valid() && m_start != m_end; }
|
||||
void clear()
|
||||
{
|
||||
m_start = {};
|
||||
m_end = {};
|
||||
}
|
||||
|
||||
TextPosition& start() { return m_start; }
|
||||
TextPosition& end() { return m_end; }
|
||||
TextPosition const& start() const { return m_start; }
|
||||
TextPosition const& end() const { return m_end; }
|
||||
|
||||
size_t line_count() const { return normalized_end().line() - normalized_start().line() + 1; }
|
||||
|
||||
TextRange normalized() const { return TextRange(normalized_start(), normalized_end()); }
|
||||
|
||||
void set_start(TextPosition const& position) { m_start = position; }
|
||||
void set_end(TextPosition const& position) { m_end = position; }
|
||||
|
||||
void set(TextPosition const& start, TextPosition const& end)
|
||||
{
|
||||
m_start = start;
|
||||
m_end = end;
|
||||
}
|
||||
|
||||
bool operator==(TextRange const& other) const
|
||||
{
|
||||
return m_start == other.m_start && m_end == other.m_end;
|
||||
}
|
||||
|
||||
bool contains(TextPosition const& position) const
|
||||
{
|
||||
if (position.line() <= m_start.line() && (position.line() != m_start.line() || position.column() < m_start.column()))
|
||||
return false;
|
||||
if (position.line() >= m_end.line() && (position.line() != m_end.line() || position.column() > m_end.column()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
TextPosition normalized_start() const { return m_start < m_end ? m_start : m_end; }
|
||||
TextPosition normalized_end() const { return m_start < m_end ? m_end : m_start; }
|
||||
|
||||
TextPosition m_start {};
|
||||
TextPosition m_end {};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<>
|
||||
struct AK::Formatter<Syntax::TextRange> : AK::Formatter<FormatString> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, Syntax::TextRange const& value)
|
||||
{
|
||||
if (value.is_valid())
|
||||
return Formatter<FormatString>::format(builder, "{}-{}"sv, value.start(), value.end());
|
||||
return builder.put_string("TextRange(Invalid)"sv);
|
||||
}
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue