1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 04:17:35 +00:00

LibMarkdown+LibSyntax: Add a Markdown syntax highlighter

It currently supports only headers and code blocks.
This commit is contained in:
Maciej 2023-04-27 12:52:01 +02:00 committed by Sam Atkins
parent 416d6ab6c8
commit cf52542fcf
8 changed files with 156 additions and 4 deletions

View file

@ -9,9 +9,10 @@ set(SOURCES
LineIterator.cpp
List.cpp
Paragraph.cpp
SyntaxHighlighter.cpp
Table.cpp
Text.cpp
)
serenity_lib(LibMarkdown markdown)
target_link_libraries(LibMarkdown PRIVATE LibJS LibRegex)
target_link_libraries(LibMarkdown PRIVATE LibJS LibRegex LibSyntax)

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2023, Maciej <sppmacd@pm.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibMarkdown/SyntaxHighlighter.h>
namespace Markdown {
Syntax::Language SyntaxHighlighter::language() const
{
return Syntax::Language::Markdown;
}
Optional<StringView> SyntaxHighlighter::comment_prefix() const
{
return {};
}
Optional<StringView> SyntaxHighlighter::comment_suffix() const
{
return {};
}
enum class Token {
Default,
Header,
Code
};
void SyntaxHighlighter::rehighlight(Palette const& palette)
{
auto text = m_client->get_text();
Vector<GUI::TextDocumentSpan> spans;
auto append_header = [&](GUI::TextRange const& range) {
Gfx::TextAttributes attributes;
attributes.color = palette.base_text();
attributes.bold = true;
GUI::TextDocumentSpan span {
.range = range,
.attributes = attributes,
.data = static_cast<u32>(Token::Header),
.is_skippable = false
};
spans.append(span);
};
auto append_code_block = [&](GUI::TextRange const& range) {
Gfx::TextAttributes attributes;
attributes.color = palette.syntax_string();
GUI::TextDocumentSpan span {
.range = range,
.attributes = attributes,
.data = static_cast<u32>(Token::Code),
.is_skippable = false
};
spans.append(span);
};
// Headers, code blocks
{
size_t line_index = 0;
Optional<size_t> code_block_start;
for (auto const& line : StringView(text).lines()) {
if (line.starts_with("```"sv)) {
if (code_block_start.has_value()) {
append_code_block({ { *code_block_start, 0 }, { line_index, line.length() } });
code_block_start = {};
} else {
code_block_start = line_index;
}
}
if (!code_block_start.has_value()) {
auto trimmed = line.trim_whitespace(TrimMode::Left);
size_t indent = line.length() - trimmed.length();
if (indent < 4 && trimmed.starts_with("#"sv)) {
append_header({ { line_index, 0 }, { line_index, line.length() } });
}
}
line_index++;
}
}
// TODO: Highlight text nodes (em, strong, link, image)
m_client->do_set_spans(spans);
}
Vector<SyntaxHighlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const
{
return {};
}
bool SyntaxHighlighter::token_types_equal(u64 lhs, u64 rhs) const
{
return static_cast<Token>(lhs) == static_cast<Token>(rhs);
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2023, Maciej <sppmacd@pm.me>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibSyntax/Highlighter.h>
namespace Markdown {
class SyntaxHighlighter : public Syntax::Highlighter {
virtual Syntax::Language language() const override;
virtual Optional<StringView> comment_prefix() const override;
virtual Optional<StringView> comment_suffix() const override;
virtual void rehighlight(Palette const&) override;
virtual Vector<MatchingTokenPair> matching_token_pairs_impl() const override;
virtual bool token_types_equal(u64, u64) const override;
};
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2022, the SerenityOS developers.
* Copyright (c) 2020-2023, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -32,6 +32,8 @@ StringView language_to_string(Language language)
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:
@ -63,6 +65,8 @@ StringView common_language_extension(Language language)
return "ini"sv;
case Language::JavaScript:
return "js"sv;
case Language::Markdown:
return "md"sv;
case Language::PlainText:
return "txt"sv;
case Language::Shell:
@ -93,6 +97,8 @@ Optional<Language> language_from_name(StringView name)
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("SQL"sv))
@ -126,6 +132,8 @@ Optional<Language> language_from_filename(LexicalPath const& file)
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;
if (extension == "sql"sv)

View file

@ -21,6 +21,7 @@ enum class Language {
HTML,
INI,
JavaScript,
Markdown,
PlainText,
Shell,
SQL,