diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index 39cdc7c448..44c1d6c498 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -42,6 +42,8 @@ set(SOURCES FontPicker.cpp FontPickerDialogGML.h Frame.cpp + GitCommitLexer.cpp + GitCommitSyntaxHighlighter.cpp GlyphMapWidget.cpp GMLAutocompleteProvider.cpp GMLFormatter.cpp diff --git a/Userland/Libraries/LibGUI/GitCommitLexer.cpp b/Userland/Libraries/LibGUI/GitCommitLexer.cpp new file mode 100644 index 0000000000..43a14af763 --- /dev/null +++ b/Userland/Libraries/LibGUI/GitCommitLexer.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2022, Brian Gianforcaro + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace GUI { + +GitCommitLexer::GitCommitLexer(StringView input) + : m_input(input) +{ +} + +char GitCommitLexer::peek(size_t offset) const +{ + if ((m_index + offset) >= m_input.length()) + return 0; + return m_input[m_index + offset]; +} + +char GitCommitLexer::consume() +{ + VERIFY(m_index < m_input.length()); + char ch = m_input[m_index++]; + if (ch == '\n') { + m_position.line++; + m_position.column = 0; + } else { + m_position.column++; + } + return ch; +} + +Vector GitCommitLexer::lex() +{ + Vector tokens; + + size_t token_start_index = 0; + GitCommitPosition token_start_position; + + auto begin_token = [&] { + token_start_index = m_index; + token_start_position = m_position; + }; + + auto commit_token = [&](auto type) { + GitCommitToken token; + token.m_view = m_input.substring_view(token_start_index, m_index - token_start_index); + token.m_type = type; + token.m_start = token_start_position; + token.m_end = m_position; + tokens.append(token); + }; + + while (m_index < m_input.length()) { + if (is_ascii_space(peek(0))) { + begin_token(); + while (is_ascii_space(peek())) + consume(); + continue; + } + + // Commit comments + if (peek(0) && peek(0) == '#') { + begin_token(); + while (peek() && peek() != '\n') + consume(); + commit_token(GitCommitToken::Type::Comment); + continue; + } + + consume(); + commit_token(GitCommitToken::Type::Unknown); + } + return tokens; +} + +} diff --git a/Userland/Libraries/LibGUI/GitCommitLexer.h b/Userland/Libraries/LibGUI/GitCommitLexer.h new file mode 100644 index 0000000000..52dd40badb --- /dev/null +++ b/Userland/Libraries/LibGUI/GitCommitLexer.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022, Brian Gianforcaro + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace GUI { + +#define FOR_EACH_TOKEN_TYPE \ + __TOKEN(Comment) \ + __TOKEN(Unknown) + +struct GitCommitPosition { + size_t line; + size_t column; +}; + +struct GitCommitToken { + enum class Type { +#define __TOKEN(x) x, + FOR_EACH_TOKEN_TYPE +#undef __TOKEN + }; + + char const* to_string() const + { + switch (m_type) { +#define __TOKEN(x) \ + case Type::x: \ + return #x; + FOR_EACH_TOKEN_TYPE +#undef __TOKEN + } + VERIFY_NOT_REACHED(); + } + + Type m_type { Type::Unknown }; + StringView m_view; + GitCommitPosition m_start; + GitCommitPosition m_end; +}; + +class GitCommitLexer { +public: + GitCommitLexer(StringView); + + Vector lex(); + +private: + char peek(size_t offset = 0) const; + char consume(); + + StringView m_input; + size_t m_index { 0 }; + GitCommitPosition m_position { 0, 0 }; +}; + +} diff --git a/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp new file mode 100644 index 0000000000..dbceb07550 --- /dev/null +++ b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022, Brian Gianforcaro + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace GUI { +static Syntax::TextStyle style_for_token_type(const Gfx::Palette& palette, GitCommitToken::Type type) +{ + switch (type) { + case GitCommitToken::Type::Comment: + return { palette.syntax_comment() }; + default: + return { palette.base_text() }; + } +} + +void GitCommitSyntaxHighlighter::rehighlight(Palette const& palette) +{ + auto text = m_client->get_text(); + GitCommitLexer lexer(text); + auto tokens = lexer.lex(); + + Vector spans; + for (auto& token : tokens) { + GUI::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 }); + auto style = style_for_token_type(palette, token.m_type); + span.attributes.color = style.color; + span.attributes.bold = style.bold; + span.is_skippable = false; + span.data = static_cast(token.m_type); + spans.append(span); + } + m_client->do_set_spans(move(spans)); + m_client->do_update(); +} + +Vector GitCommitSyntaxHighlighter::matching_token_pairs_impl() const +{ + static Vector empty; + return empty; +} + +bool GitCommitSyntaxHighlighter::token_types_equal(u64 token1, u64 token2) const +{ + return static_cast(token1) == static_cast(token2); +} + +GitCommitSyntaxHighlighter::~GitCommitSyntaxHighlighter() +{ +} + +} diff --git a/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.h b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.h new file mode 100644 index 0000000000..3e9cec60a7 --- /dev/null +++ b/Userland/Libraries/LibGUI/GitCommitSyntaxHighlighter.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Brian Gianforcaro + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace GUI { + +class GitCommitSyntaxHighlighter final : public Syntax::Highlighter { +public: + GitCommitSyntaxHighlighter() { } + virtual ~GitCommitSyntaxHighlighter() override; + + virtual Syntax::Language language() const override { return Syntax::Language::GitCommit; } + virtual void rehighlight(Palette const&) override; + +protected: + virtual Vector matching_token_pairs_impl() const override; + virtual bool token_types_equal(u64, u64) const override; +}; + +}