Serenity Operating System
1/*
2 * Copyright (c) 2022, Itamar S. <itamar8910@gmail.com>
3 * Copyright (c) 2022, the SerenityOS developers.
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "SemanticSyntaxHighlighter.h"
9#include "Lexer.h"
10#include <LibDiff/Generator.h>
11#include <LibGUI/AutocompleteProvider.h>
12#include <LibGfx/Palette.h>
13
14namespace Cpp {
15
16void SemanticSyntaxHighlighter::rehighlight(Palette const& palette)
17{
18 Vector<CodeComprehension::TokenInfo> new_tokens_info;
19 auto text = m_client->get_text();
20 {
21 Threading::MutexLocker locker(m_lock);
22 Cpp::Lexer lexer(text);
23 lexer.set_ignore_whitespace(true);
24 auto current_tokens = lexer.lex();
25
26 // Identify folding regions
27 Vector<Token> folding_region_start_tokens;
28 Vector<GUI::TextDocumentFoldingRegion> folding_regions;
29 for (auto& token : current_tokens) {
30 if (token.type() == Token::Type::LeftCurly) {
31 folding_region_start_tokens.append(token);
32 } else if (token.type() == Token::Type::RightCurly) {
33 if (!folding_region_start_tokens.is_empty()) {
34 auto start_token = folding_region_start_tokens.take_last();
35 GUI::TextDocumentFoldingRegion folding_region;
36 folding_region.range.set_start({ start_token.end().line, start_token.end().column });
37 folding_region.range.set_end({ token.start().line, token.start().column });
38 folding_regions.append(move(folding_region));
39 }
40 }
41 }
42 m_client->do_set_folding_regions(move(folding_regions));
43
44 StringBuilder current_tokens_as_lines;
45 StringBuilder previous_tokens_as_lines;
46
47 for (auto& token : current_tokens)
48 current_tokens_as_lines.appendff("{}\n", token.type_as_deprecated_string());
49
50 for (Cpp::Token const& token : m_saved_tokens)
51 previous_tokens_as_lines.appendff("{}\n", token.type_as_deprecated_string());
52
53 auto previous = previous_tokens_as_lines.to_deprecated_string();
54 auto current = current_tokens_as_lines.to_deprecated_string();
55
56 // FIXME: Computing the diff on the entire document's tokens is quite inefficient.
57 // An improvement over this could be only including the tokens that are in edited text ranges in the diff.
58 auto diff_hunks = Diff::from_text(previous.view(), current.view());
59 for (auto& token : current_tokens) {
60 new_tokens_info.append(CodeComprehension::TokenInfo { CodeComprehension::TokenInfo::SemanticType::Unknown,
61 token.start().line, token.start().column, token.end().line, token.end().column });
62 }
63 size_t previous_token_index = 0;
64 size_t current_token_index = 0;
65 for (auto const& hunk : diff_hunks) {
66 for (; previous_token_index < hunk.original_start_line; ++previous_token_index) {
67 new_tokens_info[current_token_index].type = m_tokens_info[previous_token_index].type;
68 ++current_token_index;
69 }
70 for (size_t i = 0; i < hunk.added_lines.size(); ++i) {
71 ++current_token_index;
72 }
73 for (size_t i = 0; i < hunk.removed_lines.size(); ++i) {
74 ++previous_token_index;
75 }
76 }
77 while (current_token_index < new_tokens_info.size() && previous_token_index < m_tokens_info.size()) {
78 new_tokens_info[current_token_index].type = m_tokens_info[previous_token_index].type;
79
80 ++previous_token_index;
81 ++current_token_index;
82 }
83 }
84
85 update_spans(new_tokens_info, palette);
86}
87
88static Syntax::TextStyle style_for_token_type(Gfx::Palette const& palette, CodeComprehension::TokenInfo::SemanticType type)
89{
90 switch (type) {
91 case CodeComprehension::TokenInfo::SemanticType::Unknown:
92 return { palette.base_text(), false };
93 case CodeComprehension::TokenInfo::SemanticType::Keyword:
94 return { palette.syntax_keyword(), true };
95 case CodeComprehension::TokenInfo::SemanticType::Type:
96 return { palette.syntax_type(), true };
97 case CodeComprehension::TokenInfo::SemanticType::Identifier:
98 return { palette.syntax_identifier(), false };
99 case CodeComprehension::TokenInfo::SemanticType::String:
100 return { palette.syntax_string(), false };
101 case CodeComprehension::TokenInfo::SemanticType::Number:
102 return { palette.syntax_number(), false };
103 case CodeComprehension::TokenInfo::SemanticType::IncludePath:
104 return { palette.syntax_preprocessor_value(), false };
105 case CodeComprehension::TokenInfo::SemanticType::PreprocessorStatement:
106 return { palette.syntax_preprocessor_statement(), false };
107 case CodeComprehension::TokenInfo::SemanticType::Comment:
108 return { palette.syntax_comment(), false };
109 case CodeComprehension::TokenInfo::SemanticType::Function:
110 return { palette.syntax_function(), false };
111 case CodeComprehension::TokenInfo::SemanticType::Variable:
112 return { palette.syntax_variable(), false };
113 case CodeComprehension::TokenInfo::SemanticType::CustomType:
114 return { palette.syntax_custom_type(), false };
115 case CodeComprehension::TokenInfo::SemanticType::Namespace:
116 return { palette.syntax_namespace(), false };
117 case CodeComprehension::TokenInfo::SemanticType::Member:
118 return { palette.syntax_member(), false };
119 case CodeComprehension::TokenInfo::SemanticType::Parameter:
120 return { palette.syntax_parameter(), false };
121 case CodeComprehension::TokenInfo::SemanticType::PreprocessorMacro:
122 return { palette.syntax_preprocessor_value(), false };
123 default:
124 VERIFY_NOT_REACHED();
125 return { palette.base_text(), false };
126 }
127}
128void SemanticSyntaxHighlighter::update_spans(Vector<CodeComprehension::TokenInfo> const& tokens_info, Gfx::Palette const& palette)
129{
130 Vector<GUI::TextDocumentSpan> spans;
131 for (auto& token : tokens_info) {
132 // FIXME: The +1 for the token end column is a quick hack due to not wanting to modify the lexer (which is also used by the parser). Maybe there's a better way to do this.
133 GUI::TextDocumentSpan span;
134 span.range.set_start({ token.start_line, token.start_column });
135 span.range.set_end({ token.end_line, token.end_column + 1 });
136 auto style = style_for_token_type(palette, token.type);
137 span.attributes.color = style.color;
138 span.attributes.bold = style.bold;
139 span.is_skippable = token.type == CodeComprehension::TokenInfo::SemanticType::Whitespace;
140 span.data = static_cast<u64>(token.type);
141 spans.append(span);
142 }
143 m_client->do_set_spans(move(spans));
144
145 m_has_brace_buddies = false;
146 highlight_matching_token_pair();
147
148 m_client->do_update();
149}
150
151void SemanticSyntaxHighlighter::update_tokens_info(Vector<CodeComprehension::TokenInfo> tokens_info)
152{
153 {
154 Threading::MutexLocker locker(m_lock);
155 m_tokens_info = move(tokens_info);
156
157 m_saved_tokens_text = m_client->get_text();
158 Cpp::Lexer lexer(m_saved_tokens_text);
159 lexer.set_ignore_whitespace(true);
160 m_saved_tokens = lexer.lex();
161 }
162}
163
164bool SemanticSyntaxHighlighter::is_identifier(u64 token_type) const
165{
166 auto type = static_cast<CodeComprehension::TokenInfo::SemanticType>(token_type);
167
168 return type == CodeComprehension::TokenInfo::SemanticType::Identifier
169 || type == CodeComprehension::TokenInfo::SemanticType::Function
170 || type == CodeComprehension::TokenInfo::SemanticType::Variable
171 || type == CodeComprehension::TokenInfo::SemanticType::CustomType
172 || type == CodeComprehension::TokenInfo::SemanticType::Namespace
173 || type == CodeComprehension::TokenInfo::SemanticType::Member
174 || type == CodeComprehension::TokenInfo::SemanticType::Parameter
175 || type == CodeComprehension::TokenInfo::SemanticType::PreprocessorMacro;
176}
177
178bool SemanticSyntaxHighlighter::is_navigatable(u64 token_type) const
179{
180 return static_cast<CodeComprehension::TokenInfo::SemanticType>(token_type) == CodeComprehension::TokenInfo::SemanticType::IncludePath;
181}
182
183}