LibSyntax+LibJS+LibWeb+LibWebView: Remove unused syntax highlighter code

A future patch will port LibSyntax away from UTF-32. The less code that
needs to be ported, the easier that will be.
This commit is contained in:
Timothy Flynn 2026-06-05 06:39:55 -04:00 committed by Shannon Booth
parent e6c68e9e91
commit 87cdeda93c
19 changed files with 69 additions and 1002 deletions

View file

@ -6,7 +6,6 @@
*/
#include <AK/Debug.h>
#include <AK/NeverDestroyed.h>
#include <AK/Utf16String.h>
#include <LibGfx/Palette.h>
#include <LibJS/RustFFI.h>
@ -40,27 +39,11 @@ static Gfx::TextAttributes style_for_token_category(Gfx::Palette const& palette,
}
}
bool SyntaxHighlighter::is_identifier(u64 token) const
{
return token_type_from_packed(token) == TokenType::Identifier;
}
bool SyntaxHighlighter::is_navigatable([[maybe_unused]] u64 token) const
{
return false;
}
struct RehighlightState {
Gfx::Palette const& palette;
Vector<Syntax::TextDocumentSpan>& spans;
Vector<Syntax::TextDocumentFoldingRegion>& folding_regions;
u16 const* source;
Syntax::TextPosition position { 0, 0 };
struct FoldStart {
Syntax::TextRange range;
};
Vector<FoldStart> folding_region_starts;
};
static void advance_position(Syntax::TextPosition& position, u16 const* source, u32 start, u32 len)
@ -106,19 +89,6 @@ static void on_token(void* ctx, FFI::FFIToken const* ffi_token)
span.data = pack_token_data(token_type, category);
state.spans.append(span);
}
// Track folding regions for {} blocks
if (token_type == TokenType::CurlyOpen) {
state.folding_region_starts.append({ .range = { token_start, state.position } });
} else if (token_type == TokenType::CurlyClose) {
if (!state.folding_region_starts.is_empty()) {
auto curly_open = state.folding_region_starts.take_last();
Syntax::TextDocumentFoldingRegion region;
region.range.set_start(curly_open.range.end());
region.range.set_end(token_start);
state.folding_regions.append(region);
}
}
}
void SyntaxHighlighter::rehighlight(Palette const& palette)
@ -130,43 +100,18 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
auto source_len = source_code->length_in_code_units();
Vector<Syntax::TextDocumentSpan> spans;
Vector<Syntax::TextDocumentFoldingRegion> folding_regions;
RehighlightState state {
.palette = palette,
.spans = spans,
.folding_regions = folding_regions,
.source = source_data,
.position = { 0, 0 },
.folding_region_starts = {},
};
FFI::rust_tokenize(source_data, source_len, &state,
[](void* ctx, FFI::FFIToken const* token) { on_token(ctx, token); });
m_client->do_set_spans(move(spans));
m_client->do_set_folding_regions(move(folding_regions));
m_has_brace_buddies = false;
highlight_matching_token_pair();
m_client->do_update();
}
Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
{
static NeverDestroyed<Vector<Syntax::Highlighter::MatchingTokenPair>> pairs;
if (pairs->is_empty()) {
pairs->append({ pack_token_data(TokenType::CurlyOpen, TokenCategory::Punctuation), pack_token_data(TokenType::CurlyClose, TokenCategory::Punctuation) });
pairs->append({ pack_token_data(TokenType::ParenOpen, TokenCategory::Punctuation), pack_token_data(TokenType::ParenClose, TokenCategory::Punctuation) });
pairs->append({ pack_token_data(TokenType::BracketOpen, TokenCategory::Punctuation), pack_token_data(TokenType::BracketClose, TokenCategory::Punctuation) });
}
return *pairs;
}
bool SyntaxHighlighter::token_types_equal(u64 token1, u64 token2) const
{
return static_cast<TokenType>(token1) == static_cast<TokenType>(token2);
}
}

View file

@ -16,17 +16,8 @@ public:
SyntaxHighlighter() = default;
virtual ~SyntaxHighlighter() override = default;
virtual bool is_identifier(u64) const override;
virtual bool is_navigatable(u64) const override;
virtual Syntax::Language language() const override { return Syntax::Language::JavaScript; }
virtual Optional<StringView> comment_prefix() const override { return "//"sv; }
virtual Optional<StringView> comment_suffix() const override { return {}; }
virtual void rehighlight(Palette const&) override;
protected:
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const override;
virtual bool token_types_equal(u64, u64) const override;
};
}

View file

@ -1,7 +1,6 @@
set(SOURCES
Document.cpp
Highlighter.cpp
Language.cpp
)
ladybird_lib(LibSyntax syntax)

View file

@ -4,74 +4,13 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/ByteString.h>
#include <AK/CharacterTypes.h>
#include <AK/Debug.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;
}
ByteString TextDocumentLine::to_utf8() const
{
StringBuilder builder;
builder.append(view());
return builder.to_byte_string();
}
TextDocumentLine::TextDocumentLine(Document& document)
{
clear(document);
@ -88,12 +27,6 @@ void TextDocumentLine::clear(Document& document)
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()) {
@ -111,115 +44,10 @@ bool TextDocumentLine::set_text(Document& document, StringView text)
return true;
}
void TextDocumentLine::append(Document& document, u32 const* code_points, size_t length)
void Document::set_spans(Vector<TextDocumentSpan> spans)
{
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();
quick_sort(spans, [](TextDocumentSpan const& a, TextDocumentSpan const& b) {
return a.range.start() < b.range.start();
});
// The end of the TextRanges of spans are non-inclusive, i.e span range = [X,y).
@ -229,54 +57,51 @@ void Document::merge_span_collections()
return span;
};
Vector<SpanAndCollectionIndex> merged_spans;
for (auto& span_and_collection_index : sorted_spans) {
Vector<TextDocumentSpan> merged_spans;
for (auto& span : spans) {
if (merged_spans.is_empty()) {
merged_spans.append(span_and_collection_index);
merged_spans.append(span);
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;
auto last_span = merged_spans.last();
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);
merged_spans.append(span);
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());
auto first_part = last_span;
first_part.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;
TextDocumentSpan merged_span;
merged_span.range = { span.range.start(), min(span.range.end(), last_span.range.end()) };
merged_span.is_skippable = span.is_skippable | last_span.is_skippable;
merged_span.data = span.data ? span.data : last_span.data;
merged_span.attributes.color = last_span.attributes.color;
merged_span.attributes.bold = span.attributes.bold | last_span.attributes.bold;
merged_span.attributes.background_color = span.attributes.background_color.has_value() ? span.attributes.background_color.value() : last_span.attributes.background_color;
merged_span.attributes.underline_color = span.attributes.underline_color.has_value() ? span.attributes.underline_color.value() : last_span.attributes.underline_color;
merged_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());
auto last_part = span;
last_part.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());
auto last_part = last_span;
last_part.range.set_start(span.range.end());
merged_spans.append(move(last_part));
}
@ -284,106 +109,26 @@ void Document::merge_span_collections()
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);
if (!span.range.is_valid()) {
dbgln_if(TEXTEDITOR_DEBUG, "Invalid span {} => ignoring", span.range);
continue;
}
if (span.span.range.end() < span.span.range.start()) {
dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", span.span.range);
if (span.range.end() < span.range.start()) {
dbgln_if(TEXTEDITOR_DEBUG, "Span {} has negative length => ignoring", 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);
if (span.range.end() < previous_span.range.start()) {
dbgln_if(TEXTEDITOR_DEBUG, "Spans not sorted (Span {} ends before previous span {}) => ignoring", 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);
if (span.range.start() < previous_span.range.end()) {
dbgln_if(TEXTEDITOR_DEBUG, "Span {} overlaps previous span {} => ignoring", span.range, previous_span.range);
continue;
}
previous_span = span.span;
m_spans.append(move(span.span));
previous_span = span;
m_spans.append(move(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

@ -6,9 +6,9 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/RefCounted.h>
#include <AK/Utf32View.h>
#include <AK/Vector.h>
#include <LibGfx/TextAttributes.h>
#include <LibSyntax/Forward.h>
#include <LibSyntax/TextRange.h>
@ -22,41 +22,17 @@ struct TextDocumentSpan {
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);
ByteString to_utf8() const;
Utf32View view() const LIFETIME_BOUND { return { code_points(), length() }; }
u32 const* code_points() const { return m_text.data(); }
bool is_empty() const { return length() == 0; }
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.
@ -68,22 +44,8 @@ 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(); }
void set_spans(Vector<TextDocumentSpan> spans);
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;
@ -91,12 +53,7 @@ public:
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

@ -4,99 +4,10 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Color.h>
#include <LibSyntax/Highlighter.h>
namespace Syntax {
void Highlighter::highlight_matching_token_pair()
{
auto& document = m_client->get_document();
enum class Direction {
Forward,
Backward,
};
auto find_span_of_type = [&](auto i, u64 type, u64 not_type, Direction direction) -> Optional<size_t> {
size_t nesting_level = 0;
bool forward = direction == Direction::Forward;
if (forward) {
++i;
if (i >= document.spans().size())
return {};
} else {
if (i == 0)
return {};
--i;
}
for (;;) {
auto& span = document.spans().at(i);
auto span_token_type = span.data;
if (token_types_equal(span_token_type, not_type)) {
++nesting_level;
} else if (token_types_equal(span_token_type, type)) {
if (nesting_level-- <= 0)
return i;
}
if (forward) {
++i;
if (i >= document.spans().size())
return {};
} else {
if (i == 0)
return {};
--i;
}
}
return {};
};
auto make_buddies = [&](int index0, int index1) {
auto& buddy0 = document.spans()[index0];
auto& buddy1 = document.spans()[index1];
m_has_brace_buddies = true;
m_brace_buddies[0].index = index0;
m_brace_buddies[1].index = index1;
m_brace_buddies[0].span_backup = buddy0;
m_brace_buddies[1].span_backup = buddy1;
buddy0.attributes.background_color = Color::DarkCyan;
buddy1.attributes.background_color = Color::DarkCyan;
buddy0.attributes.color = Color::White;
buddy1.attributes.color = Color::White;
m_client->do_update();
};
auto pairs = matching_token_pairs();
for (size_t i = 0; i < document.spans().size(); ++i) {
auto& span = const_cast<TextDocumentSpan&>(document.spans().at(i));
auto token_type = span.data;
for (auto& pair : pairs) {
if (token_types_equal(token_type, pair.open) && span.range.start() == m_client->get_cursor()) {
auto buddy = find_span_of_type(i, pair.close, pair.open, Direction::Forward);
if (buddy.has_value())
make_buddies(i, buddy.value());
return;
}
}
for (auto& pair : pairs) {
if (token_types_equal(token_type, pair.close) && span.range.end() == m_client->get_cursor()) {
auto buddy = find_span_of_type(i, pair.open, pair.close, Direction::Backward);
if (buddy.has_value())
make_buddies(i, buddy.value());
return;
}
}
}
}
void Highlighter::attach(HighlighterClient& client)
{
VERIFY(!m_client);
@ -108,33 +19,4 @@ void Highlighter::detach()
m_client = nullptr;
}
void Highlighter::cursor_did_change()
{
auto& document = m_client->get_document();
if (m_has_brace_buddies) {
if (m_brace_buddies[0].index >= 0 && m_brace_buddies[0].index < static_cast<int>(document.spans().size()))
document.set_span_at_index(m_brace_buddies[0].index, m_brace_buddies[0].span_backup);
if (m_brace_buddies[1].index >= 0 && m_brace_buddies[1].index < static_cast<int>(document.spans().size()))
document.set_span_at_index(m_brace_buddies[1].index, m_brace_buddies[1].span_backup);
m_has_brace_buddies = false;
m_client->do_update();
}
highlight_matching_token_pair();
}
Vector<Highlighter::MatchingTokenPair> Highlighter::matching_token_pairs() const
{
auto own_pairs = matching_token_pairs_impl();
own_pairs.ensure_capacity(own_pairs.size() + m_nested_token_pairs.size());
for (auto& nested_pair : m_nested_token_pairs)
own_pairs.append(nested_pair);
return own_pairs;
}
void Highlighter::register_nested_token_pairs(Vector<MatchingTokenPair> pairs)
{
for (auto& pair : pairs)
m_nested_token_pairs.set(pair);
}
}

View file

@ -7,7 +7,6 @@
#pragma once
#include <AK/Noncopyable.h>
#include <AK/WeakPtr.h>
#include <LibGfx/Palette.h>
#include <LibSyntax/Document.h>
#include <LibSyntax/HighlighterClient.h>
@ -23,57 +22,25 @@ public:
virtual ~Highlighter() = default;
virtual Language language() const = 0;
virtual Optional<StringView> comment_prefix() const = 0;
virtual Optional<StringView> comment_suffix() const = 0;
virtual void rehighlight(Palette const&) = 0;
virtual void highlight_matching_token_pair();
virtual bool is_identifier(u64) const { return false; }
virtual bool is_navigatable(u64) const { return false; }
void attach(HighlighterClient&);
void detach();
void cursor_did_change();
struct MatchingTokenPair {
u64 open;
u64 close;
};
Vector<MatchingTokenPair> matching_token_pairs() const;
template<typename T>
bool fast_is() const = delete;
// FIXME: When other syntax highlighters start using a language server, we should add a common base class here.
virtual bool is_cpp_semantic_highlighter() const { return false; }
protected:
Highlighter() = default;
// FIXME: This should be WeakPtr somehow
HighlighterClient* m_client { nullptr };
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const = 0;
virtual bool token_types_equal(u64, u64) const = 0;
void register_nested_token_pairs(Vector<MatchingTokenPair>);
void clear_nested_token_pairs() { m_nested_token_pairs.clear(); }
size_t first_free_token_kind_serial_value() const { return m_nested_token_pairs.size(); }
struct BuddySpan {
int index { -1 };
TextDocumentSpan span_backup;
};
bool m_has_brace_buddies { false };
BuddySpan m_brace_buddies[2];
HashTable<MatchingTokenPair> m_nested_token_pairs;
};
class ProxyHighlighterClient final : public Syntax::HighlighterClient {
public:
ProxyHighlighterClient(Syntax::HighlighterClient& client, TextPosition start, u64 nested_kind_start_value, StringView source)
: m_document(client.get_document())
, m_text(source)
ProxyHighlighterClient(TextPosition start, u64 nested_kind_start_value, StringView source)
: m_text(source)
, m_start(start)
, m_nested_kind_start_value(nested_kind_start_value)
{
@ -98,64 +65,14 @@ public:
return spans;
}
Vector<TextDocumentFoldingRegion> corrected_folding_regions() const
{
Vector<TextDocumentFoldingRegion> folding_regions { m_folding_regions };
for (auto& entry : folding_regions) {
entry.range.start() = {
entry.range.start().line() + m_start.line(),
entry.range.start().line() == 0 ? entry.range.start().column() + m_start.column() : entry.range.start().column(),
};
entry.range.end() = {
entry.range.end().line() + m_start.line(),
entry.range.end().line() == 0 ? entry.range.end().column() + m_start.column() : entry.range.end().column(),
};
}
return folding_regions;
}
Vector<Syntax::Highlighter::MatchingTokenPair> corrected_token_pairs(Vector<Syntax::Highlighter::MatchingTokenPair> pairs) const
{
for (auto& pair : pairs) {
pair.close += m_nested_kind_start_value;
pair.open += m_nested_kind_start_value;
}
return pairs;
}
private:
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<TextDocumentFoldingRegion>& folding_regions() override { return m_folding_regions; }
virtual Vector<TextDocumentFoldingRegion> const& folding_regions() const override { return m_folding_regions; }
virtual ByteString highlighter_did_request_text() const override { return m_text; }
virtual void highlighter_did_request_update() override { }
virtual Document& highlighter_did_request_document() override { return m_document; }
virtual TextPosition highlighter_did_request_cursor() const override { return {}; }
virtual StringView highlighter_did_request_text() const override { return m_text; }
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<TextDocumentSpan> m_spans;
Vector<TextDocumentFoldingRegion> m_folding_regions;
Document& m_document;
StringView m_text;
TextPosition m_start;
u64 m_nested_kind_start_value { 0 };
};
}
template<>
struct AK::Traits<Syntax::Highlighter::MatchingTokenPair> : public AK::DefaultTraits<Syntax::Highlighter::MatchingTokenPair> {
static unsigned hash(Syntax::Highlighter::MatchingTokenPair const& pair)
{
return pair_int_hash(u64_hash(pair.open), u64_hash(pair.close));
}
static bool equals(Syntax::Highlighter::MatchingTokenPair const& a, Syntax::Highlighter::MatchingTokenPair const& b)
{
return a.open == b.open && a.close == b.close;
}
};

View file

@ -7,10 +7,9 @@
#pragma once
#include <AK/ByteString.h>
#include <AK/StringView.h>
#include <AK/Vector.h>
#include <LibSyntax/Document.h>
#include <LibSyntax/TextPosition.h>
namespace Syntax {
@ -18,29 +17,12 @@ class HighlighterClient {
public:
virtual ~HighlighterClient() = default;
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<TextDocumentFoldingRegion>& folding_regions() = 0;
virtual Vector<TextDocumentFoldingRegion> const& folding_regions() const = 0;
virtual ByteString highlighter_did_request_text() const = 0;
virtual void highlighter_did_request_update() = 0;
virtual Document& highlighter_did_request_document() = 0;
virtual TextPosition highlighter_did_request_cursor() const = 0;
virtual StringView highlighter_did_request_text() 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<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(); }
ByteString get_text() const { return highlighter_did_request_text(); }
Document& get_document() { return highlighter_did_request_document(); }
TextPosition get_cursor() const { return highlighter_did_request_cursor(); }
static constexpr auto span_collection_index = 0;
StringView get_text() const { return highlighter_did_request_text(); }
};
}

View file

@ -1,140 +0,0 @@
/*
* Copyright (c) 2020-2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "Language.h"
#include <AK/LexicalPath.h>
#include <LibSyntax/Highlighter.h>
namespace Syntax {
StringView language_to_string(Language language)
{
switch (language) {
case Language::CMake:
return "CMake"sv;
case Language::CMakeCache:
return "CMakeCache"sv;
case Language::Cpp:
return "C++"sv;
case Language::CSS:
return "CSS"sv;
case Language::GitCommit:
return "Git"sv;
case Language::GML:
return "GML"sv;
case Language::HTML:
return "HTML"sv;
case Language::INI:
return "INI"sv;
case Language::JavaScript:
return "JavaScript"sv;
case Language::Markdown:
return "Markdown"sv;
case Language::PlainText:
return "Plain Text"sv;
case Language::Shell:
return "Shell"sv;
}
VERIFY_NOT_REACHED();
}
StringView common_language_extension(Language language)
{
switch (language) {
case Language::CMake:
return "cmake"sv;
case Language::CMakeCache:
return {};
case Language::Cpp:
return "cpp"sv;
case Language::CSS:
return "css"sv;
case Language::GitCommit:
return {};
case Language::GML:
return "gml"sv;
case Language::HTML:
return "html"sv;
case Language::INI:
return "ini"sv;
case Language::JavaScript:
return "js"sv;
case Language::Markdown:
return "md"sv;
case Language::PlainText:
return "txt"sv;
case Language::Shell:
return "sh"sv;
}
VERIFY_NOT_REACHED();
}
Optional<Language> language_from_name(StringView name)
{
if (name.equals_ignoring_ascii_case("CMake"sv))
return Language::CMake;
if (name.equals_ignoring_ascii_case("CMakeCache"sv))
return Language::CMakeCache;
if (name.equals_ignoring_ascii_case("Cpp"sv))
return Language::Cpp;
if (name.equals_ignoring_ascii_case("CSS"sv))
return Language::CSS;
if (name.equals_ignoring_ascii_case("GitCommit"sv))
return Language::GitCommit;
if (name.equals_ignoring_ascii_case("GML"sv))
return Language::GML;
if (name.equals_ignoring_ascii_case("HTML"sv))
return Language::HTML;
if (name.equals_ignoring_ascii_case("INI"sv))
return Language::INI;
if (name.equals_ignoring_ascii_case("JavaScript"sv))
return Language::JavaScript;
if (name.equals_ignoring_ascii_case("Markdown"sv))
return Language::Markdown;
if (name.equals_ignoring_ascii_case("PlainText"sv))
return Language::PlainText;
if (name.equals_ignoring_ascii_case("Shell"sv))
return Language::Shell;
return {};
}
Optional<Language> language_from_filename(LexicalPath const& file)
{
if (file.title() == "COMMIT_EDITMSG"sv)
return Language::GitCommit;
auto extension = file.extension();
VERIFY(!extension.starts_with('.'));
if (extension == "cmake"sv || (extension == "txt"sv && file.title() == "CMakeLists"sv))
return Language::CMake;
if (extension == "txt"sv && file.title() == "CMakeCache"sv)
return Language::CMakeCache;
if (extension.is_one_of("c"sv, "cc"sv, "cxx"sv, "cpp"sv, "c++", "h"sv, "hh"sv, "hxx"sv, "hpp"sv, "h++"sv))
return Language::Cpp;
if (extension == "css"sv)
return Language::CSS;
if (extension == "gml"sv)
return Language::GML;
if (extension.is_one_of("html"sv, "htm"sv))
return Language::HTML;
if (extension.is_one_of("ini"sv, "af"sv))
return Language::INI;
if (extension.is_one_of("js"sv, "mjs"sv, "json"sv))
return Language::JavaScript;
if (extension == "md"sv)
return Language::Markdown;
if (extension.is_one_of("sh"sv, "bash"sv))
return Language::Shell;
// Check "txt" after the CMake related files that use "txt" as their extension.
if (extension == "txt"sv)
return Language::PlainText;
return {};
}
}

View file

@ -6,29 +6,14 @@
#pragma once
#include <AK/Forward.h>
#include <AK/StringView.h>
#include <AK/Types.h>
namespace Syntax {
enum class Language {
CMake,
CMakeCache,
Cpp,
enum class Language : u8 {
CSS,
GitCommit,
GML,
HTML,
INI,
JavaScript,
Markdown,
PlainText,
Shell,
};
StringView language_to_string(Language);
StringView common_language_extension(Language);
Optional<Language> language_from_name(StringView);
Optional<Language> language_from_filename(LexicalPath const&);
}

View file

@ -21,50 +21,18 @@ public:
}
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 {};
TextPosition m_start;
TextPosition m_end;
};
}

View file

@ -5,29 +5,16 @@
*/
#include <AK/Debug.h>
#include <AK/NeverDestroyed.h>
#include <LibWeb/CSS/Parser/Tokenizer.h>
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
namespace Web::CSS {
bool SyntaxHighlighter::is_identifier(u64 token) const
{
return static_cast<CSS::Parser::Token::Type>(token) == CSS::Parser::Token::Type::Ident;
}
bool SyntaxHighlighter::is_navigatable(u64) const
{
return false;
}
void SyntaxHighlighter::rehighlight(Palette const& palette)
{
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(CSS::SyntaxHighlighter) starting rehighlight");
auto text = m_client->get_text();
Vector<Parser::Token> folding_region_start_tokens;
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) {
@ -51,18 +38,6 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
if (token.is(Parser::Token::Type::EndOfFile))
break;
if (token.is(Parser::Token::Type::OpenCurly)) {
folding_region_start_tokens.append(token);
} else if (token.is(Parser::Token::Type::CloseCurly)) {
if (!folding_region_start_tokens.is_empty()) {
auto start_token = folding_region_start_tokens.take_last();
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));
}
}
switch (token.type()) {
case Parser::Token::Type::Ident:
highlight(token.start_position().line, token.start_position().column, token.end_position().line, token.end_position().column, { palette.syntax_identifier(), {} }, token.type());
@ -149,27 +124,6 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
}
m_client->do_set_spans(move(spans));
m_client->do_set_folding_regions(move(folding_regions));
m_has_brace_buddies = false;
highlight_matching_token_pair();
m_client->do_update();
}
Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
{
static NeverDestroyed<Vector<Syntax::Highlighter::MatchingTokenPair>> pairs;
if (pairs->is_empty()) {
pairs->append({ static_cast<u64>(CSS::Parser::Token::Type::OpenCurly), static_cast<u64>(CSS::Parser::Token::Type::CloseCurly) });
pairs->append({ static_cast<u64>(CSS::Parser::Token::Type::OpenParen), static_cast<u64>(CSS::Parser::Token::Type::CloseParen) });
pairs->append({ static_cast<u64>(CSS::Parser::Token::Type::OpenSquare), static_cast<u64>(CSS::Parser::Token::Type::CloseSquare) });
pairs->append({ static_cast<u64>(CSS::Parser::Token::Type::CDO), static_cast<u64>(CSS::Parser::Token::Type::CDC) });
}
return *pairs;
}
bool SyntaxHighlighter::token_types_equal(u64 token0, u64 token1) const
{
return token0 == token1;
}
}

View file

@ -16,17 +16,8 @@ public:
SyntaxHighlighter() = default;
virtual ~SyntaxHighlighter() override = default;
virtual bool is_identifier(u64) const override;
virtual bool is_navigatable(u64) const override;
virtual Syntax::Language language() const override { return Syntax::Language::CSS; }
virtual Optional<StringView> comment_prefix() const override { return "/*"sv; }
virtual Optional<StringView> comment_suffix() const override { return "*/"sv; }
virtual void rehighlight(Palette const&) override;
protected:
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const override;
virtual bool token_types_equal(u64, u64) const override;
};
}

View file

@ -6,7 +6,6 @@
*/
#include <AK/Debug.h>
#include <AK/NeverDestroyed.h>
#include <LibJS/SyntaxHighlighter.h>
#include <LibJS/Token.h>
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
@ -15,24 +14,11 @@
namespace Web::HTML {
bool SyntaxHighlighter::is_identifier(u64) const
{
return false;
}
bool SyntaxHighlighter::is_navigatable(u64) const
{
return false;
}
void SyntaxHighlighter::rehighlight(Palette const& palette)
{
dbgln_if(SYNTAX_HIGHLIGHTING_DEBUG, "(HTML::SyntaxHighlighter) starting rehighlight");
auto text = m_client->get_text();
clear_nested_token_pairs();
// FIXME: Add folding regions for start and end tags.
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)) {
@ -80,9 +66,8 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
} else if (token->is_end_tag()) {
if (token->tag_name().is_one_of("script"sv, "style"sv)) {
if (state == State::Javascript) {
VERIFY(static_cast<u64>(AugmentedTokenKind::__Count) + first_free_token_kind_serial_value() < JS_TOKEN_START_VALUE);
VERIFY(static_cast<u64>(AugmentedTokenKind::__Count) < JS_TOKEN_START_VALUE);
Syntax::ProxyHighlighterClient proxy_client {
*m_client,
substring_start_position,
JS_TOKEN_START_VALUE,
substring_builder.string_view()
@ -92,16 +77,13 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
highlighter.attach(proxy_client);
highlighter.rehighlight(palette);
highlighter.detach();
register_nested_token_pairs(proxy_client.corrected_token_pairs(highlighter.matching_token_pairs()));
}
spans.extend(proxy_client.corrected_spans());
folding_regions.extend(proxy_client.corrected_folding_regions());
substring_builder.clear();
} else if (state == State::CSS) {
VERIFY(static_cast<u64>(AugmentedTokenKind::__Count) + first_free_token_kind_serial_value() + static_cast<u64>(JS::TokenType::_COUNT_OF_TOKENS) < CSS_TOKEN_START_VALUE);
VERIFY(static_cast<u64>(AugmentedTokenKind::__Count) + static_cast<u64>(JS::TokenType::_COUNT_OF_TOKENS) < CSS_TOKEN_START_VALUE);
Syntax::ProxyHighlighterClient proxy_client {
*m_client,
substring_start_position,
CSS_TOKEN_START_VALUE,
substring_builder.string_view()
@ -111,11 +93,9 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
highlighter.attach(proxy_client);
highlighter.rehighlight(palette);
highlighter.detach();
register_nested_token_pairs(proxy_client.corrected_token_pairs(highlighter.matching_token_pairs()));
}
spans.extend(proxy_client.corrected_spans());
folding_regions.extend(proxy_client.corrected_folding_regions());
substring_builder.clear();
}
state = State::HTML;
@ -134,11 +114,6 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
token->end_position().column,
{ palette.syntax_comment(), {} },
AugmentedTokenKind::Comment);
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));
} else if (token->is_start_tag() || token->is_end_tag()) {
highlight(
token->start_position().line,
@ -184,24 +159,6 @@ void SyntaxHighlighter::rehighlight(Palette const& palette)
}
m_client->do_set_spans(move(spans));
m_client->do_set_folding_regions(move(folding_regions));
m_has_brace_buddies = false;
highlight_matching_token_pair();
m_client->do_update();
}
Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
{
static NeverDestroyed<Vector<MatchingTokenPair>> pairs;
if (pairs->is_empty()) {
pairs->append({ static_cast<u64>(AugmentedTokenKind::OpenTag), static_cast<u64>(AugmentedTokenKind::CloseTag) });
}
return *pairs;
}
bool SyntaxHighlighter::token_types_equal(u64 token0, u64 token1) const
{
return token0 == token1;
}
}

View file

@ -26,20 +26,11 @@ public:
SyntaxHighlighter() = default;
virtual ~SyntaxHighlighter() override = default;
virtual bool is_identifier(u64) const override;
virtual bool is_navigatable(u64) const override;
virtual Syntax::Language language() const override { return Syntax::Language::HTML; }
virtual Optional<StringView> comment_prefix() const override { return "<!--"sv; }
virtual Optional<StringView> comment_suffix() const override { return "-->"sv; }
virtual void rehighlight(Palette const&) override;
static constexpr u64 JS_TOKEN_START_VALUE = 1000;
static constexpr u64 CSS_TOKEN_START_VALUE = 2000;
protected:
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const override;
virtual bool token_types_equal(u64, u64) const override;
};
}

View file

@ -97,60 +97,20 @@ SourceHighlighterClient::SourceHighlighterClient(String const& source, Syntax::L
}
}
Vector<Syntax::TextDocumentSpan> const& SourceHighlighterClient::spans() const
{
return document().spans();
}
void SourceHighlighterClient::set_span_at_index(size_t index, Syntax::TextDocumentSpan span)
{
document().set_span_at_index(index, span);
}
Vector<Syntax::TextDocumentFoldingRegion>& SourceHighlighterClient::folding_regions()
{
return document().folding_regions();
}
Vector<Syntax::TextDocumentFoldingRegion> const& SourceHighlighterClient::folding_regions() const
{
return document().folding_regions();
}
ByteString SourceHighlighterClient::highlighter_did_request_text() const
StringView SourceHighlighterClient::highlighter_did_request_text() const
{
return document().text();
}
void SourceHighlighterClient::highlighter_did_request_update()
{
// No-op
}
Syntax::Document& SourceHighlighterClient::highlighter_did_request_document()
{
return document();
}
Syntax::TextPosition SourceHighlighterClient::highlighter_did_request_cursor() const
{
return {};
}
void SourceHighlighterClient::highlighter_did_set_spans(Vector<Syntax::TextDocumentSpan> spans)
{
document().set_spans(span_collection_index, move(spans));
document().set_spans(move(spans));
}
void SourceHighlighterClient::highlighter_did_set_folding_regions(Vector<Syntax::TextDocumentFoldingRegion> folding_regions)
{
document().set_folding_regions(move(folding_regions));
}
String highlight_source(Optional<URL::URL> const& url, URL::URL const& base_url, String const& source, Syntax::Language language, HighlightOutputMode mode)
String highlight_source(Optional<URL::URL> const& url, URL::URL const& base_url, String const& source, Syntax::Language language)
{
SourceHighlighterClient highlighter_client { source, language };
return highlighter_client.to_html_string(url, base_url, mode);
return highlighter_client.to_html_string(url, base_url);
}
StringView SourceHighlighterClient::class_for_token(u64 token_type) const
@ -268,7 +228,7 @@ StringView SourceHighlighterClient::class_for_token(u64 token_type) const
}
}
String SourceHighlighterClient::to_html_string(Optional<URL::URL> const& url, URL::URL const& base_url, HighlightOutputMode mode) const
String SourceHighlighterClient::to_html_string(Optional<URL::URL> const& url, URL::URL const& base_url) const
{
StringBuilder builder;
@ -295,24 +255,22 @@ String SourceHighlighterClient::to_html_string(Optional<URL::URL> const& url, UR
builder.append("</span>"sv);
};
if (mode == HighlightOutputMode::FullDocument) {
builder.append(R"~~~(
builder.append(R"~~~(
<!DOCTYPE html>
<html>
<head>
<meta name="color-scheme" content="dark light">)~~~"sv);
if (url.has_value())
builder.appendff("<title>View Source - {}</title>", escape_html_entities(url->serialize_for_display()));
else
builder.append("<title>View Source</title>"sv);
if (url.has_value())
builder.appendff("<title>View Source - {}</title>", escape_html_entities(url->serialize_for_display()));
else
builder.append("<title>View Source</title>"sv);
builder.appendff("<style type=\"text/css\">{}</style>", HTML_HIGHLIGHTER_STYLE);
builder.append(R"~~~(
builder.appendff("<style type=\"text/css\">{}</style>", HTML_HIGHLIGHTER_STYLE);
builder.append(R"~~~(
</head>
<body>)~~~"sv);
}
builder.append("<pre class=\"html\">"sv);
<body>
<pre class=\"html\">)~~~"sv);
static constexpr auto href = to_array<u32>({ 'h', 'r', 'e', 'f' });
static constexpr auto src = to_array<u32>({ 's', 'r', 'c' });
@ -409,13 +367,11 @@ String SourceHighlighterClient::to_html_string(Optional<URL::URL> const& url, UR
builder.append("</div>"sv);
}
builder.append("</pre>"sv);
if (mode == HighlightOutputMode::FullDocument) {
builder.append(R"~~~(
builder.append(R"~~~(
</pre>
</body>
</html>
)~~~"sv);
}
return builder.to_string_without_validation();
}

View file

@ -18,11 +18,6 @@
namespace WebView {
enum class HighlightOutputMode {
FullDocument, // Include HTML header, title, style sheet, etc
SourceOnly, // Just the highlighted source
};
class WEBVIEW_API SourceDocument final : public Syntax::Document {
public:
static NonnullRefPtr<SourceDocument> create(String const& source)
@ -53,20 +48,12 @@ public:
SourceHighlighterClient(String const& source, Syntax::Language);
virtual ~SourceHighlighterClient() = default;
String to_html_string(Optional<URL::URL> const&, URL::URL const& base_url, HighlightOutputMode) const;
String to_html_string(Optional<URL::URL> const&, URL::URL const& base_url) const;
private:
// ^ Syntax::HighlighterClient
virtual Vector<Syntax::TextDocumentSpan> const& spans() const override;
virtual void set_span_at_index(size_t index, Syntax::TextDocumentSpan span) override;
virtual Vector<Syntax::TextDocumentFoldingRegion>& folding_regions() override;
virtual Vector<Syntax::TextDocumentFoldingRegion> const& folding_regions() const override;
virtual ByteString highlighter_did_request_text() const override;
virtual void highlighter_did_request_update() override;
virtual Syntax::Document& highlighter_did_request_document() override;
virtual Syntax::TextPosition highlighter_did_request_cursor() const override;
virtual StringView highlighter_did_request_text() const override;
virtual void highlighter_did_set_spans(Vector<Syntax::TextDocumentSpan>) override;
virtual void highlighter_did_set_folding_regions(Vector<Syntax::TextDocumentFoldingRegion>) override;
StringView class_for_token(u64 token_type) const;
@ -76,7 +63,7 @@ private:
OwnPtr<Syntax::Highlighter> m_highlighter;
};
WEBVIEW_API String highlight_source(Optional<URL::URL> const&, URL::URL const& base_url, String const& source, Syntax::Language, HighlightOutputMode);
WEBVIEW_API String highlight_source(Optional<URL::URL> const&, URL::URL const& base_url, String const& source, Syntax::Language);
constexpr inline StringView HTML_HIGHLIGHTER_STYLE = R"~~~(
@media (prefers-color-scheme: dark) {

View file

@ -624,7 +624,7 @@ void WebContentClient::did_request_media_context_menu(u64 page_id, Gfx::IntPoint
void WebContentClient::did_get_source(u64, URL::URL url, URL::URL base_url, String source)
{
if (auto view = Application::the().open_blank_new_tab(Web::HTML::ActivateTab::Yes); view.has_value()) {
auto html = highlight_source(url, base_url, source, Syntax::Language::HTML, WebView::HighlightOutputMode::FullDocument);
auto html = highlight_source(url, base_url, source, Syntax::Language::HTML);
view->load_html(html);
}
}

View file

@ -13,6 +13,6 @@ TEST_CASE(highlight_script_with_braces)
// Regression test for https://github.com/LadybirdBrowser/ladybird/issues/8529
auto source = "<script>\nfunction foo() {\n return 1;\n}\n</script>"_string;
URL::URL base_url {};
auto result = WebView::highlight_source({}, base_url, source, Syntax::Language::HTML, WebView::HighlightOutputMode::SourceOnly);
auto result = WebView::highlight_source({}, base_url, source, Syntax::Language::HTML);
EXPECT(!result.is_empty());
}