mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 07:44:59 +00:00
LibMarkdown+LibSyntax: Add a Markdown syntax highlighter
It currently supports only headers and code blocks.
This commit is contained in:
parent
416d6ab6c8
commit
cf52542fcf
8 changed files with 156 additions and 4 deletions
|
@ -40,6 +40,7 @@
|
||||||
#include <LibGfx/Painter.h>
|
#include <LibGfx/Painter.h>
|
||||||
#include <LibJS/SyntaxHighlighter.h>
|
#include <LibJS/SyntaxHighlighter.h>
|
||||||
#include <LibMarkdown/Document.h>
|
#include <LibMarkdown/Document.h>
|
||||||
|
#include <LibMarkdown/SyntaxHighlighter.h>
|
||||||
#include <LibSQL/AST/SyntaxHighlighter.h>
|
#include <LibSQL/AST/SyntaxHighlighter.h>
|
||||||
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
|
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
|
||||||
#include <LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.h>
|
#include <LibWeb/HTML/SyntaxHighlighter/SyntaxHighlighter.h>
|
||||||
|
@ -672,6 +673,13 @@ ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
syntax_actions.add_action(*m_ini_highlight);
|
syntax_actions.add_action(*m_ini_highlight);
|
||||||
TRY(syntax_menu->try_add_action(*m_ini_highlight));
|
TRY(syntax_menu->try_add_action(*m_ini_highlight));
|
||||||
|
|
||||||
|
m_markdown_highlight = GUI::Action::create_checkable("Ma&rkdown", [&](auto&) {
|
||||||
|
m_editor->set_syntax_highlighter(make<Markdown::SyntaxHighlighter>());
|
||||||
|
m_editor->update();
|
||||||
|
});
|
||||||
|
syntax_actions.add_action(*m_markdown_highlight);
|
||||||
|
TRY(syntax_menu->try_add_action(*m_markdown_highlight));
|
||||||
|
|
||||||
m_shell_highlight = GUI::Action::create_checkable("Sh&ell File", [&](auto&) {
|
m_shell_highlight = GUI::Action::create_checkable("Sh&ell File", [&](auto&) {
|
||||||
m_editor->set_syntax_highlighter(make<Shell::SyntaxHighlighter>());
|
m_editor->set_syntax_highlighter(make<Shell::SyntaxHighlighter>());
|
||||||
m_editor->update();
|
m_editor->update();
|
||||||
|
@ -718,6 +726,7 @@ ErrorOr<void> MainWidget::initialize_menubar(GUI::Window& window)
|
||||||
TRY(m_syntax_statusbar_menu->try_add_action(*m_html_highlight));
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_html_highlight));
|
||||||
TRY(m_syntax_statusbar_menu->try_add_action(*m_ini_highlight));
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_ini_highlight));
|
||||||
TRY(m_syntax_statusbar_menu->try_add_action(*m_js_highlight));
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_js_highlight));
|
||||||
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_markdown_highlight));
|
||||||
TRY(m_syntax_statusbar_menu->try_add_action(*m_shell_highlight));
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_shell_highlight));
|
||||||
TRY(m_syntax_statusbar_menu->try_add_action(*m_sql_highlight));
|
TRY(m_syntax_statusbar_menu->try_add_action(*m_sql_highlight));
|
||||||
|
|
||||||
|
@ -752,6 +761,8 @@ void MainWidget::set_path(StringView path)
|
||||||
m_gml_highlight->activate();
|
m_gml_highlight->activate();
|
||||||
} else if (m_extension == "ini" || m_extension == "af") {
|
} else if (m_extension == "ini" || m_extension == "af") {
|
||||||
m_ini_highlight->activate();
|
m_ini_highlight->activate();
|
||||||
|
} else if (m_extension == "md") {
|
||||||
|
m_markdown_highlight->activate();
|
||||||
} else if (m_extension == "sh" || m_extension == "bash") {
|
} else if (m_extension == "sh" || m_extension == "bash") {
|
||||||
m_shell_highlight->activate();
|
m_shell_highlight->activate();
|
||||||
} else if (m_extension == "sql") {
|
} else if (m_extension == "sql") {
|
||||||
|
|
|
@ -136,6 +136,7 @@ private:
|
||||||
RefPtr<GUI::Action> m_git_highlight;
|
RefPtr<GUI::Action> m_git_highlight;
|
||||||
RefPtr<GUI::Action> m_gml_highlight;
|
RefPtr<GUI::Action> m_gml_highlight;
|
||||||
RefPtr<GUI::Action> m_ini_highlight;
|
RefPtr<GUI::Action> m_ini_highlight;
|
||||||
|
RefPtr<GUI::Action> m_markdown_highlight;
|
||||||
RefPtr<GUI::Action> m_shell_highlight;
|
RefPtr<GUI::Action> m_shell_highlight;
|
||||||
RefPtr<GUI::Action> m_sql_highlight;
|
RefPtr<GUI::Action> m_sql_highlight;
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
#include <LibGUI/Window.h>
|
#include <LibGUI/Window.h>
|
||||||
#include <LibJS/SyntaxHighlighter.h>
|
#include <LibJS/SyntaxHighlighter.h>
|
||||||
#include <LibMarkdown/Document.h>
|
#include <LibMarkdown/Document.h>
|
||||||
|
#include <LibMarkdown/SyntaxHighlighter.h>
|
||||||
#include <LibSQL/AST/SyntaxHighlighter.h>
|
#include <LibSQL/AST/SyntaxHighlighter.h>
|
||||||
#include <LibSyntax/Language.h>
|
#include <LibSyntax/Language.h>
|
||||||
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
|
#include <LibWeb/CSS/SyntaxHighlighter/SyntaxHighlighter.h>
|
||||||
|
@ -653,11 +654,14 @@ void Editor::set_syntax_highlighter_for(CodeDocument const& document)
|
||||||
case Syntax::Language::HTML:
|
case Syntax::Language::HTML:
|
||||||
set_syntax_highlighter(make<Web::HTML::SyntaxHighlighter>());
|
set_syntax_highlighter(make<Web::HTML::SyntaxHighlighter>());
|
||||||
break;
|
break;
|
||||||
|
case Syntax::Language::INI:
|
||||||
|
set_syntax_highlighter(make<GUI::IniSyntaxHighlighter>());
|
||||||
|
break;
|
||||||
case Syntax::Language::JavaScript:
|
case Syntax::Language::JavaScript:
|
||||||
set_syntax_highlighter(make<JS::SyntaxHighlighter>());
|
set_syntax_highlighter(make<JS::SyntaxHighlighter>());
|
||||||
break;
|
break;
|
||||||
case Syntax::Language::INI:
|
case Syntax::Language::Markdown:
|
||||||
set_syntax_highlighter(make<GUI::IniSyntaxHighlighter>());
|
set_syntax_highlighter(make<Markdown::SyntaxHighlighter>());
|
||||||
break;
|
break;
|
||||||
case Syntax::Language::Shell:
|
case Syntax::Language::Shell:
|
||||||
set_syntax_highlighter(make<Shell::SyntaxHighlighter>());
|
set_syntax_highlighter(make<Shell::SyntaxHighlighter>());
|
||||||
|
|
|
@ -9,9 +9,10 @@ set(SOURCES
|
||||||
LineIterator.cpp
|
LineIterator.cpp
|
||||||
List.cpp
|
List.cpp
|
||||||
Paragraph.cpp
|
Paragraph.cpp
|
||||||
|
SyntaxHighlighter.cpp
|
||||||
Table.cpp
|
Table.cpp
|
||||||
Text.cpp
|
Text.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
serenity_lib(LibMarkdown markdown)
|
serenity_lib(LibMarkdown markdown)
|
||||||
target_link_libraries(LibMarkdown PRIVATE LibJS LibRegex)
|
target_link_libraries(LibMarkdown PRIVATE LibJS LibRegex LibSyntax)
|
||||||
|
|
103
Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp
Normal file
103
Userland/Libraries/LibMarkdown/SyntaxHighlighter.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
Userland/Libraries/LibMarkdown/SyntaxHighlighter.h
Normal file
23
Userland/Libraries/LibMarkdown/SyntaxHighlighter.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2020-2022, the SerenityOS developers.
|
* Copyright (c) 2020-2023, the SerenityOS developers.
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
@ -32,6 +32,8 @@ StringView language_to_string(Language language)
|
||||||
return "INI"sv;
|
return "INI"sv;
|
||||||
case Language::JavaScript:
|
case Language::JavaScript:
|
||||||
return "JavaScript"sv;
|
return "JavaScript"sv;
|
||||||
|
case Language::Markdown:
|
||||||
|
return "Markdown"sv;
|
||||||
case Language::PlainText:
|
case Language::PlainText:
|
||||||
return "Plain Text"sv;
|
return "Plain Text"sv;
|
||||||
case Language::Shell:
|
case Language::Shell:
|
||||||
|
@ -63,6 +65,8 @@ StringView common_language_extension(Language language)
|
||||||
return "ini"sv;
|
return "ini"sv;
|
||||||
case Language::JavaScript:
|
case Language::JavaScript:
|
||||||
return "js"sv;
|
return "js"sv;
|
||||||
|
case Language::Markdown:
|
||||||
|
return "md"sv;
|
||||||
case Language::PlainText:
|
case Language::PlainText:
|
||||||
return "txt"sv;
|
return "txt"sv;
|
||||||
case Language::Shell:
|
case Language::Shell:
|
||||||
|
@ -93,6 +97,8 @@ Optional<Language> language_from_name(StringView name)
|
||||||
return Language::INI;
|
return Language::INI;
|
||||||
if (name.equals_ignoring_ascii_case("JavaScript"sv))
|
if (name.equals_ignoring_ascii_case("JavaScript"sv))
|
||||||
return Language::JavaScript;
|
return Language::JavaScript;
|
||||||
|
if (name.equals_ignoring_ascii_case("Markdown"sv))
|
||||||
|
return Language::Markdown;
|
||||||
if (name.equals_ignoring_ascii_case("PlainText"sv))
|
if (name.equals_ignoring_ascii_case("PlainText"sv))
|
||||||
return Language::PlainText;
|
return Language::PlainText;
|
||||||
if (name.equals_ignoring_ascii_case("SQL"sv))
|
if (name.equals_ignoring_ascii_case("SQL"sv))
|
||||||
|
@ -126,6 +132,8 @@ Optional<Language> language_from_filename(LexicalPath const& file)
|
||||||
return Language::INI;
|
return Language::INI;
|
||||||
if (extension.is_one_of("js"sv, "mjs"sv, "json"sv))
|
if (extension.is_one_of("js"sv, "mjs"sv, "json"sv))
|
||||||
return Language::JavaScript;
|
return Language::JavaScript;
|
||||||
|
if (extension == "md"sv)
|
||||||
|
return Language::Markdown;
|
||||||
if (extension.is_one_of("sh"sv, "bash"sv))
|
if (extension.is_one_of("sh"sv, "bash"sv))
|
||||||
return Language::Shell;
|
return Language::Shell;
|
||||||
if (extension == "sql"sv)
|
if (extension == "sql"sv)
|
||||||
|
|
|
@ -21,6 +21,7 @@ enum class Language {
|
||||||
HTML,
|
HTML,
|
||||||
INI,
|
INI,
|
||||||
JavaScript,
|
JavaScript,
|
||||||
|
Markdown,
|
||||||
PlainText,
|
PlainText,
|
||||||
Shell,
|
Shell,
|
||||||
SQL,
|
SQL,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue