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:
Ali Mohammad Pur 2023-08-29 12:43:41 +03:30 committed by Tim Flynn
parent 2495302991
commit ba4db899d4
26 changed files with 773 additions and 683 deletions

View file

@ -42,9 +42,9 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
auto text = m_client->get_text();
auto tokens = Lexer::lex(text).release_value_but_fixme_should_propagate_errors();
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
auto highlight_span = [&](Token::Type type, Position const& start, Position const& end) {
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ start.line, start.column });
span.range.set_end({ end.line, end.column });
if (!span.range.is_valid())

View file

@ -57,10 +57,10 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
Optional<Token> ending_paren {};
};
Vector<OpenBlock> open_blocks;
Vector<GUI::TextDocumentFoldingRegion> folding_regions;
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
Vector<Syntax::TextDocumentSpan> spans;
auto highlight_span = [&](Token::Type type, Position const& start, Position const& end) {
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ start.line, start.column });
span.range.set_end({ end.line, end.column });
if (!span.range.is_valid())
@ -96,7 +96,7 @@ void SyntaxHighlighter::rehighlight(Gfx::Palette const& palette)
open_blocks.shrink(found_index.value());
// Create a region.
GUI::TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
if (open_block.ending_paren.has_value()) {
region.range.set_start({ open_block.ending_paren->end.line, open_block.ending_paren->end.column });
} else {

View file

@ -146,4 +146,5 @@ set(GENERATED_SOURCES
)
serenity_lib(LibGUI gui)
target_link_libraries(LibGUI PRIVATE LibCore LibFileSystem LibGfx LibIPC LibThreading LibRegex LibSyntax LibConfig LibUnicode)
target_link_libraries(LibGUI PRIVATE LibCore LibFileSystem LibGfx LibIPC LibThreading LibRegex LibConfig LibUnicode)
target_link_libraries(LibGUI PUBLIC LibSyntax)

View file

@ -6,6 +6,8 @@
#pragma once
#include <LibSyntax/Forward.h>
namespace GUI {
class AbstractButton;
@ -77,11 +79,8 @@ class Statusbar;
class TabWidget;
class TableView;
class TextBox;
class TextPosition;
class UrlBox;
class TextDocument;
class TextDocumentLine;
struct TextDocumentSpan;
class TextDocumentUndoCommand;
class TextEditor;
class ThemeChangeEvent;

View file

@ -47,11 +47,11 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
Vector<Token> folding_region_start_tokens;
Vector<GUI::TextDocumentSpan> spans;
Vector<GUI::TextDocumentFoldingRegion> folding_regions;
Vector<Syntax::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
for (auto& token : tokens) {
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ token.m_start.line, token.m_start.column });
span.range.set_end({ token.m_end.line, token.m_end.column });
span.attributes = style_for_token_type(palette, token.m_type);
@ -65,7 +65,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
} else if (token.m_type == Token::Type::RightCurly) {
if (!folding_region_start_tokens.is_empty()) {
auto left_curly = folding_region_start_tokens.take_last();
GUI::TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
region.range.set_start({ left_curly.m_end.line, left_curly.m_end.column });
region.range.set_end({ token.m_start.line, token.m_start.column });
folding_regions.append(region);

View file

@ -26,9 +26,9 @@ void GitCommitSyntaxHighlighter::rehighlight(Palette const& palette)
GitCommitLexer lexer(text);
auto tokens = lexer.lex();
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
for (auto& token : tokens) {
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ token.m_start.line, token.m_start.column });
span.range.set_end({ token.m_end.line, token.m_end.column });
span.attributes = style_for_token_type(palette, token.m_type);

View file

@ -45,11 +45,11 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette)
Optional<IniToken> previous_section_token;
IniToken previous_token;
Vector<TextDocumentFoldingRegion> folding_regions;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
for (auto& token : tokens) {
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ token.m_start.line, token.m_start.column });
span.range.set_end({ token.m_end.line, token.m_end.column });
span.attributes = style_for_token_type(palette, token.m_type);
@ -61,7 +61,7 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette)
previous_section_token = token;
} else if (token.m_type == IniToken::Type::LeftBracket) {
if (previous_section_token.has_value()) {
TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
region.range.set_start({ previous_section_token->m_end.line, previous_section_token->m_end.column });
// If possible, leave a blank line between sections.
// `end_line - start_line > 1` means the whitespace contains at least 1 blank line,
@ -80,7 +80,7 @@ void IniSyntaxHighlighter::rehighlight(Palette const& palette)
previous_token = token;
}
if (previous_section_token.has_value()) {
TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
auto& end_token = tokens.last();
region.range.set_start({ previous_section_token->m_end.line, previous_section_token->m_end.column });
region.range.set_end({ end_token.m_end.line, end_token.m_end.column });

View file

@ -105,173 +105,6 @@ bool TextDocument::set_text(StringView text, AllowCallback allow_callback, IsNew
return true;
}
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(TextDocument& document)
{
clear(document);
}
TextDocumentLine::TextDocumentLine(TextDocument& document, StringView text)
{
set_text(document, text);
}
void TextDocumentLine::clear(TextDocument& document)
{
m_text.clear();
document.update_views({});
}
void TextDocumentLine::set_text(TextDocument& document, Vector<u32> const text)
{
m_text = move(text);
document.update_views({});
}
bool TextDocumentLine::set_text(TextDocument& 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(TextDocument& document, u32 const* code_points, size_t length)
{
if (length == 0)
return;
m_text.append(code_points, length);
document.update_views({});
}
void TextDocumentLine::append(TextDocument& document, u32 code_point)
{
insert(document, length(), code_point);
}
void TextDocumentLine::prepend(TextDocument& document, u32 code_point)
{
insert(document, 0, code_point);
}
void TextDocumentLine::insert(TextDocument& 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(TextDocument& document, size_t index)
{
if (index == length()) {
m_text.take_last();
} else {
m_text.remove(index);
}
document.update_views({});
}
void TextDocumentLine::remove_range(TextDocument& 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(TextDocument& 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(TextDocument& document, size_t length)
{
m_text.resize(length);
document.update_views({});
}
void TextDocument::append_line(NonnullOwnPtr<TextDocumentLine> line)
{
lines().append(move(line));
@ -1303,213 +1136,9 @@ TextRange TextDocument::range_for_entire_line(size_t line_index) const
return { { line_index, 0 }, { line_index, line(line_index).length() } };
}
TextDocumentSpan const* TextDocument::span_at(TextPosition const& position) const
{
for (auto& span : m_spans) {
if (span.range.contains(position))
return &span;
}
return nullptr;
}
void TextDocument::set_unmodified()
{
m_undo_stack.set_current_unmodified();
}
void TextDocument::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 TextDocument::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 = [](GUI::TextDocumentSpan span) -> GUI::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 TextDocument::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("TextDocument 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&> TextDocument::folding_region_starting_on_line(size_t line)
{
return m_folding_regions.first_matching([line](auto& region) {
return region.range.start().line() == line;
});
}
bool TextDocument::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&> TextDocument::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;
}
}

View file

@ -17,32 +17,24 @@
#include <LibCore/Forward.h>
#include <LibGUI/Command.h>
#include <LibGUI/Forward.h>
#include <LibGUI/TextPosition.h>
#include <LibGUI/TextRange.h>
#include <LibGUI/UndoStack.h>
#include <LibGUI/Widget.h>
#include <LibGfx/Color.h>
#include <LibGfx/TextAttributes.h>
#include <LibRegex/Regex.h>
#include <LibSyntax/Document.h>
namespace GUI {
using TextDocumentSpan = Syntax::TextDocumentSpan;
using TextDocumentFoldingRegion = Syntax::TextDocumentFoldingRegion;
using TextDocumentLine = Syntax::TextDocumentLine;
constexpr Duration COMMAND_COMMIT_TIME = Duration::from_milliseconds(400);
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 TextDocument : public RefCounted<TextDocument> {
class TextDocument : public Syntax::Document {
public:
enum class SearchShouldWrap {
No = 0,
@ -69,10 +61,8 @@ public:
virtual ~TextDocument() = default;
size_t line_count() const { return m_lines.size(); }
TextDocumentLine const& line(size_t line_index) const { return *m_lines[line_index]; }
TextDocumentLine& line(size_t line_index) { return *m_lines[line_index]; }
void set_spans(u32 span_collection_index, Vector<TextDocumentSpan> spans);
virtual TextDocumentLine const& line(size_t line_index) const override { return *m_lines[line_index]; }
virtual TextDocumentLine& line(size_t line_index) override { return *m_lines[line_index]; }
enum class IsNewDocument {
No,
@ -83,23 +73,6 @@ public:
Vector<NonnullOwnPtr<TextDocumentLine>> const& lines() const { return m_lines; }
Vector<NonnullOwnPtr<TextDocumentLine>>& lines() { return m_lines; }
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;
void append_line(NonnullOwnPtr<TextDocumentLine>);
NonnullOwnPtr<TextDocumentLine> take_line(size_t line_index);
void remove_line(size_t line_index);
@ -109,7 +82,7 @@ public:
void register_client(Client&);
void unregister_client(Client&);
void update_views(Badge<TextDocumentLine>);
virtual void update_views(Badge<TextDocumentLine>) override;
DeprecatedString text() const;
DeprecatedString text_in_range(TextRange const&) const;
@ -162,12 +135,7 @@ protected:
explicit TextDocument(Client* client);
private:
void merge_span_collections();
Vector<NonnullOwnPtr<TextDocumentLine>> m_lines;
HashMap<u32, Vector<TextDocumentSpan>> m_span_collections;
Vector<TextDocumentSpan> m_spans;
Vector<TextDocumentFoldingRegion> m_folding_regions;
HashTable<Client*> m_clients;
bool m_client_notifications_enabled { true };
@ -182,40 +150,6 @@ private:
DeprecatedString m_regex_needle;
};
class TextDocumentLine {
public:
explicit TextDocumentLine(TextDocument&);
explicit TextDocumentLine(TextDocument&, 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(TextDocument&, StringView);
void set_text(TextDocument&, Vector<u32>);
void append(TextDocument&, u32);
void prepend(TextDocument&, u32);
void insert(TextDocument&, size_t index, u32);
void remove(TextDocument&, size_t index);
void append(TextDocument&, u32 const*, size_t);
void truncate(TextDocument&, size_t length);
void clear(TextDocument&);
void remove_range(TextDocument&, size_t start, size_t length);
void keep_range(TextDocument&, 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 TextDocumentUndoCommand : public Command {
public:
TextDocumentUndoCommand(TextDocument&);

View file

@ -17,10 +17,10 @@
#include <LibGUI/Clipboard.h>
#include <LibGUI/Forward.h>
#include <LibGUI/TextDocument.h>
#include <LibGUI/TextRange.h>
#include <LibGfx/TextAlignment.h>
#include <LibSyntax/Forward.h>
#include <LibSyntax/HighlighterClient.h>
#include <LibSyntax/TextRange.h>
namespace GUI {
@ -93,7 +93,7 @@ public:
bool is_editable() const { return m_mode == Editable; }
bool is_readonly() const { return m_mode == ReadOnly; }
bool is_displayonly() const { return m_mode == DisplayOnly; }
void set_mode(const Mode);
void set_mode(Mode const);
void set_editing_cursor();
@ -294,7 +294,7 @@ protected:
virtual void highlighter_did_set_folding_regions(Vector<TextDocumentFoldingRegion> folding_regions) final;
private:
friend class TextDocumentLine;
friend TextDocumentLine;
// ^TextDocument::Client
virtual void document_did_append_line() override;

View file

@ -7,46 +7,10 @@
#pragma once
#include <AK/Format.h>
#include <LibSyntax/TextPosition.h>
namespace GUI {
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 };
};
using TextPosition = Syntax::TextPosition;
}
template<>
struct AK::Formatter<GUI::TextPosition> : AK::Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, GUI::TextPosition const& value)
{
if (value.is_valid())
return Formatter<FormatString>::format(builder, "({},{})"sv, value.line(), value.column());
return builder.put_string("GUI::TextPosition(Invalid)"sv);
}
};

View file

@ -1,80 +1,15 @@
/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, the SerenityOS developers.
* Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/TextPosition.h>
#include <LibSyntax/TextRange.h>
namespace GUI {
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 {};
};
using TextRange = Syntax::TextRange;
}
template<>
struct AK::Formatter<GUI::TextRange> : AK::Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, GUI::TextRange const& value)
{
if (value.is_valid())
return Formatter<FormatString>::format(builder, "{}-{}"sv, value.start(), value.end());
return builder.put_string("GUI::TextRange(Invalid)"sv);
}
};

View file

@ -54,10 +54,10 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
Lexer lexer(text);
Vector<GUI::TextDocumentSpan> spans;
Vector<GUI::TextDocumentFoldingRegion> folding_regions;
GUI::TextPosition position { 0, 0 };
GUI::TextPosition start { 0, 0 };
Vector<Syntax::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
Syntax::TextPosition position { 0, 0 };
Syntax::TextPosition start { 0, 0 };
auto advance_position = [&position](char ch) {
if (ch == '\n') {
@ -75,7 +75,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
for (size_t i = 0; i < str.length(); ++i)
advance_position(str[i]);
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start(start);
span.range.set_end({ position.line(), position.column() });
auto type = is_trivia ? TokenType::Invalid : token.type();
@ -94,7 +94,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
struct TokenData {
Token token;
GUI::TextRange range;
Syntax::TextRange range;
};
Vector<TokenData> folding_region_start_tokens;
@ -115,7 +115,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
} else if (token.type() == TokenType::CurlyClose) {
if (!folding_region_start_tokens.is_empty()) {
auto curly_open = folding_region_start_tokens.take_last();
GUI::TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
region.range.set_start(curly_open.range.end());
region.range.set_end(token_start_position);
folding_regions.append(region);

View file

@ -33,13 +33,13 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
{
auto text = m_client->get_text();
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
auto append_header = [&](GUI::TextRange const& range) {
auto append_header = [&](Syntax::TextRange const& range) {
Gfx::TextAttributes attributes;
attributes.color = palette.base_text();
attributes.bold = true;
GUI::TextDocumentSpan span {
Syntax::TextDocumentSpan span {
.range = range,
.attributes = attributes,
.data = static_cast<u32>(Token::Header),
@ -48,10 +48,10 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
spans.append(span);
};
auto append_code_block = [&](GUI::TextRange const& range) {
auto append_code_block = [&](Syntax::TextRange const& range) {
Gfx::TextAttributes attributes;
attributes.color = palette.syntax_string();
GUI::TextDocumentSpan span {
Syntax::TextDocumentSpan span {
.range = range,
.attributes = attributes,
.data = static_cast<u32>(Token::Code),

View file

@ -46,12 +46,12 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
Lexer lexer(text);
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
auto append_token = [&](Token const& token) {
if (token.value().is_empty())
return;
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
span.range.set_start({ token.start_position().line - 1, token.start_position().column - 1 });
span.range.set_end({ token.end_position().line - 1, token.end_position().column - 1 });
span.attributes = style_for_token_type(palette, token.type());

View file

@ -1,4 +1,5 @@
set(SOURCES
Document.cpp
Highlighter.cpp
Language.cpp
)

View 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;
}
}

View 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();
};
}

View file

@ -8,7 +8,10 @@
namespace Syntax {
class Document;
class Highlighter;
class HighlighterClient;
class TextPosition;
class TextRange;
}

View file

@ -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 };
};

View file

@ -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;
};

View 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);
}
};

View 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);
}
};

View file

@ -26,8 +26,8 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
auto text = m_client->get_text();
Vector<Parser::Token> folding_region_start_tokens;
Vector<GUI::TextDocumentFoldingRegion> folding_regions;
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
Vector<Syntax::TextDocumentSpan> spans;
auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, CSS::Parser::Token::Type type) {
if (start_line > end_line || (start_line == end_line && start_column >= end_column)) {
@ -36,7 +36,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
}
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color);
spans.empend(
GUI::TextRange {
Syntax::TextRange {
{ start_line, start_column },
{ end_line, end_column },
},
@ -55,7 +55,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
} else if (token.is(Parser::Token::Type::CloseCurly)) {
if (!folding_region_start_tokens.is_empty()) {
auto start_token = folding_region_start_tokens.take_last();
GUI::TextDocumentFoldingRegion folding_region;
Syntax::TextDocumentFoldingRegion folding_region;
folding_region.range.set_start({ start_token.end_position().line, start_token.end_position().column });
folding_region.range.set_end({ token.start_position().line, token.start_position().column });
folding_regions.append(move(folding_region));

View file

@ -42,8 +42,8 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
clear_nested_token_pairs();
// FIXME: Add folding regions for start and end tags.
Vector<GUI::TextDocumentFoldingRegion> folding_regions;
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
Vector<Syntax::TextDocumentSpan> spans;
auto highlight = [&](auto start_line, auto start_column, auto end_line, auto end_column, Gfx::TextAttributes attributes, AugmentedTokenKind kind) {
if (start_line > end_line || (start_line == end_line && start_column >= end_column)) {
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) discarding ({}-{}) to ({}-{}) because it has zero or negative length", start_line, start_column, end_line, end_column);
@ -51,7 +51,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
}
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) highlighting ({}-{}) to ({}-{}) with color {}", start_line, start_column, end_line, end_column, attributes.color);
spans.empend(
GUI::TextRange {
Syntax::TextRange {
{ start_line, start_column },
{ end_line, end_column },
},
@ -67,7 +67,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
CSS,
} state { State::HTML };
StringBuilder substring_builder;
GUI::TextPosition substring_start_position;
Syntax::TextPosition substring_start_position;
for (;;) {
auto token = tokenizer.next_token();
@ -141,7 +141,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
{ palette.syntax_comment(), {} },
AugmentedTokenKind::Comment);
GUI::TextDocumentFoldingRegion region;
Syntax::TextDocumentFoldingRegion region;
region.range.set_start({ token->start_position().line, token->start_position().column + comment_prefix()->length() });
region.range.set_end({ token->end_position().line, token->end_position().column - comment_suffix()->length() });
folding_regions.append(move(region));

View file

@ -7,9 +7,9 @@
#include <AK/QuickSort.h>
#include <AK/ScopedValueRollback.h>
#include <AK/TemporaryChange.h>
#include <LibGUI/TextEditor.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Palette.h>
#include <LibSyntax/Document.h>
#include <Shell/NodeVisitor.h>
#include <Shell/Parser.h>
#include <Shell/SyntaxHighlighter.h>
@ -24,7 +24,7 @@ enum class AugmentedTokenKind : u32 {
class HighlightVisitor : public AST::NodeVisitor {
public:
HighlightVisitor(Vector<GUI::TextDocumentSpan>& spans, Gfx::Palette const& palette, const GUI::TextDocument& document)
HighlightVisitor(Vector<Syntax::TextDocumentSpan>& spans, Gfx::Palette const& palette, Syntax::Document const& document)
: m_spans(spans)
, m_palette(palette)
, m_document(document)
@ -32,7 +32,7 @@ public:
}
private:
AST::Position::Line offset_line(const AST::Position::Line& line, size_t offset)
AST::Position::Line offset_line(AST::Position::Line const& line, size_t offset)
{
// We need to look at the line(s) above.
AST::Position::Line new_line { line };
@ -52,20 +52,20 @@ private:
return new_line;
}
void set_offset_range_end(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0)
void set_offset_range_end(Syntax::TextRange& range, AST::Position::Line const& line, size_t offset = 0)
{
auto new_line = offset_line(line, offset);
range.set_end({ new_line.line_number, new_line.line_column });
}
void set_offset_range_start(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0)
void set_offset_range_start(Syntax::TextRange& range, AST::Position::Line const& line, size_t offset = 0)
{
auto new_line = offset_line(line, offset);
range.set_start({ new_line.line_number, new_line.line_column });
}
GUI::TextDocumentSpan& span_for_node(const AST::Node* node)
Syntax::TextDocumentSpan& span_for_node(AST::Node const* node)
{
GUI::TextDocumentSpan span;
Syntax::TextDocumentSpan span;
set_offset_range_start(span.range, node->position().start_line);
set_offset_range_end(span.range, node->position().end_line);
span.data = static_cast<u64>(node->kind());
@ -75,7 +75,7 @@ private:
return m_spans.last();
}
virtual void visit(const AST::PathRedirectionNode* node) override
virtual void visit(AST::PathRedirectionNode const* node) override
{
if (node->path()->is_bareword()) {
auto& span = span_for_node(node->path());
@ -85,7 +85,7 @@ private:
NodeVisitor::visit(node);
}
}
virtual void visit(const AST::And* node) override
virtual void visit(AST::And const* node) override
{
{
ScopedValueRollback first_in_command { m_is_first_in_command };
@ -102,11 +102,11 @@ private:
span.attributes.color = m_palette.syntax_punctuation();
span.attributes.bold = true;
}
virtual void visit(const AST::ListConcatenate* node) override
virtual void visit(AST::ListConcatenate const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::Background* node) override
virtual void visit(AST::Background const* node) override
{
NodeVisitor::visit(node);
@ -115,11 +115,11 @@ private:
span.attributes.color = m_palette.syntax_punctuation();
span.attributes.bold = true;
}
virtual void visit(const AST::BraceExpansion* node) override
virtual void visit(AST::BraceExpansion const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::BarewordLiteral* node) override
virtual void visit(AST::BarewordLiteral const* node) override
{
NodeVisitor::visit(node);
@ -134,11 +134,11 @@ private:
span.attributes.color = m_palette.base_text();
}
}
virtual void visit(const AST::CastToCommand* node) override
virtual void visit(AST::CastToCommand const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::CastToList* node) override
virtual void visit(AST::CastToList const* node) override
{
NodeVisitor::visit(node);
@ -152,29 +152,29 @@ private:
set_offset_range_start(end_span.range, node->position().end_line, 1);
end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen);
}
virtual void visit(const AST::CloseFdRedirection* node) override
virtual void visit(AST::CloseFdRedirection const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::CommandLiteral* node) override
virtual void visit(AST::CommandLiteral const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::Comment* node) override
virtual void visit(AST::Comment const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.syntax_comment();
}
virtual void visit(const AST::ContinuationControl* node) override
virtual void visit(AST::ContinuationControl const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.syntax_control_keyword();
}
virtual void visit(const AST::DynamicEvaluate* node) override
virtual void visit(AST::DynamicEvaluate const* node) override
{
NodeVisitor::visit(node);
@ -182,7 +182,7 @@ private:
start_span.attributes.color = m_palette.syntax_punctuation();
start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 });
}
virtual void visit(const AST::DoubleQuotedString* node) override
virtual void visit(AST::DoubleQuotedString const* node) override
{
NodeVisitor::visit(node);
@ -202,11 +202,11 @@ private:
}
m_is_first_in_command = false;
}
virtual void visit(const AST::Fd2FdRedirection* node) override
virtual void visit(AST::Fd2FdRedirection const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::FunctionDeclaration* node) override
virtual void visit(AST::FunctionDeclaration const* node) override
{
NodeVisitor::visit(node);
@ -224,7 +224,7 @@ private:
name_span.attributes.color = m_palette.syntax_identifier();
}
}
virtual void visit(const AST::ForLoop* node) override
virtual void visit(AST::ForLoop const* node) override
{
// The iterated expression is an expression, not a command.
m_is_first_in_command = false;
@ -275,14 +275,14 @@ private:
variable_span.attributes.color = m_palette.syntax_identifier();
}
}
virtual void visit(const AST::Glob* node) override
virtual void visit(AST::Glob const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.syntax_preprocessor_value();
}
virtual void visit(const AST::Execute* node) override
virtual void visit(AST::Execute const* node) override
{
TemporaryChange first { m_is_first_in_command, true };
NodeVisitor::visit(node);
@ -299,7 +299,7 @@ private:
end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen);
}
}
virtual void visit(const AST::IfCond* node) override
virtual void visit(AST::IfCond const* node) override
{
m_is_first_in_command = false;
NodeVisitor::visit(node);
@ -321,7 +321,7 @@ private:
}
}
virtual void visit(const AST::ImmediateExpression* node) override
virtual void visit(AST::ImmediateExpression const* node) override
{
TemporaryChange first { m_is_first_in_command, false };
NodeVisitor::visit(node);
@ -345,11 +345,11 @@ private:
end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen);
}
virtual void visit(const AST::Join* node) override
virtual void visit(AST::Join const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::MatchExpr* node) override
virtual void visit(AST::MatchExpr const* node) override
{
// The matched expression is an expression, not a command.
m_is_first_in_command = false;
@ -371,7 +371,7 @@ private:
as_span.attributes.color = m_palette.syntax_keyword();
}
}
virtual void visit(const AST::Or* node) override
virtual void visit(AST::Or const* node) override
{
{
ScopedValueRollback first_in_command { m_is_first_in_command };
@ -388,11 +388,11 @@ private:
span.attributes.color = m_palette.syntax_punctuation();
span.attributes.bold = true;
}
virtual void visit(const AST::Pipe* node) override
virtual void visit(AST::Pipe const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::Range* node) override
virtual void visit(AST::Range const* node) override
{
NodeVisitor::visit(node);
@ -409,15 +409,15 @@ private:
end_span.attributes.color = m_palette.syntax_punctuation();
}
virtual void visit(const AST::ReadRedirection* node) override
virtual void visit(AST::ReadRedirection const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::ReadWriteRedirection* node) override
virtual void visit(AST::ReadWriteRedirection const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::Sequence* node) override
virtual void visit(AST::Sequence const* node) override
{
for (auto& entry : node->entries()) {
ScopedValueRollback first_in_command { m_is_first_in_command };
@ -435,29 +435,29 @@ private:
span.is_skippable = true;
}
}
virtual void visit(const AST::Subshell* node) override
virtual void visit(AST::Subshell const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::SimpleVariable* node) override
virtual void visit(AST::SimpleVariable const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.syntax_identifier();
}
virtual void visit(const AST::SpecialVariable* node) override
virtual void visit(AST::SpecialVariable const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.syntax_identifier();
}
virtual void visit(const AST::Juxtaposition* node) override
virtual void visit(AST::Juxtaposition const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::StringLiteral* node) override
virtual void visit(AST::StringLiteral const* node) override
{
NodeVisitor::visit(node);
@ -470,11 +470,11 @@ private:
span.attributes.bold = true;
m_is_first_in_command = false;
}
virtual void visit(const AST::StringPartCompose* node) override
virtual void visit(AST::StringPartCompose const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::SyntaxError* node) override
virtual void visit(AST::SyntaxError const* node) override
{
NodeVisitor::visit(node);
@ -483,14 +483,14 @@ private:
span.attributes.background_color = Color(Color::NamedColor::MidRed).lightened(1.3f).with_alpha(128);
span.attributes.color = m_palette.base_text();
}
virtual void visit(const AST::Tilde* node) override
virtual void visit(AST::Tilde const* node) override
{
NodeVisitor::visit(node);
auto& span = span_for_node(node);
span.attributes.color = m_palette.link();
}
virtual void visit(const AST::VariableDeclarations* node) override
virtual void visit(AST::VariableDeclarations const* node) override
{
TemporaryChange first_in_command { m_is_first_in_command, false };
for (auto& decl : node->variables()) {
@ -506,18 +506,18 @@ private:
start_span.data = static_cast<u64>(AugmentedTokenKind::OpenParen);
}
}
virtual void visit(const AST::WriteAppendRedirection* node) override
virtual void visit(AST::WriteAppendRedirection const* node) override
{
NodeVisitor::visit(node);
}
virtual void visit(const AST::WriteRedirection* node) override
virtual void visit(AST::WriteRedirection const* node) override
{
NodeVisitor::visit(node);
}
Vector<GUI::TextDocumentSpan>& m_spans;
Vector<Syntax::TextDocumentSpan>& m_spans;
Gfx::Palette const& m_palette;
const GUI::TextDocument& m_document;
Syntax::Document const& m_document;
bool m_is_first_in_command { false };
};
@ -544,7 +544,7 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
Parser parser(text);
auto ast = parser.parse();
Vector<GUI::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentSpan> spans;
HighlightVisitor visitor { spans, palette, m_client->get_document() };
if (ast)