Serenity Operating System
at master 584 lines 21 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/QuickSort.h> 8#include <AK/ScopedValueRollback.h> 9#include <AK/TemporaryChange.h> 10#include <LibGUI/TextEditor.h> 11#include <LibGfx/Font/Font.h> 12#include <LibGfx/Palette.h> 13#include <Shell/NodeVisitor.h> 14#include <Shell/Parser.h> 15#include <Shell/SyntaxHighlighter.h> 16 17namespace Shell { 18 19enum class AugmentedTokenKind : u32 { 20 __TokenTypeCount = (u32)AST::Node::Kind::__Count, 21 OpenParen, 22 CloseParen, 23}; 24 25class HighlightVisitor : public AST::NodeVisitor { 26public: 27 HighlightVisitor(Vector<GUI::TextDocumentSpan>& spans, Gfx::Palette const& palette, const GUI::TextDocument& document) 28 : m_spans(spans) 29 , m_palette(palette) 30 , m_document(document) 31 { 32 } 33 34private: 35 AST::Position::Line offset_line(const AST::Position::Line& line, size_t offset) 36 { 37 // We need to look at the line(s) above. 38 AST::Position::Line new_line { line }; 39 while (new_line.line_column < offset) { 40 offset -= new_line.line_column; 41 --offset; 42 43 if (new_line.line_number == 0) 44 break; 45 --new_line.line_number; 46 47 auto& line = m_document.line(new_line.line_number); 48 new_line.line_column = line.length(); 49 } 50 if (offset > 0) 51 new_line.line_column -= offset; 52 53 return new_line; 54 } 55 void set_offset_range_end(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0) 56 { 57 auto new_line = offset_line(line, offset); 58 range.set_end({ new_line.line_number, new_line.line_column }); 59 } 60 void set_offset_range_start(GUI::TextRange& range, const AST::Position::Line& line, size_t offset = 0) 61 { 62 auto new_line = offset_line(line, offset); 63 range.set_start({ new_line.line_number, new_line.line_column }); 64 } 65 66 GUI::TextDocumentSpan& span_for_node(const AST::Node* node) 67 { 68 GUI::TextDocumentSpan span; 69 set_offset_range_start(span.range, node->position().start_line); 70 set_offset_range_end(span.range, node->position().end_line); 71 span.data = static_cast<u64>(node->kind()); 72 span.is_skippable = false; 73 m_spans.append(move(span)); 74 75 return m_spans.last(); 76 } 77 78 virtual void visit(const AST::PathRedirectionNode* node) override 79 { 80 if (node->path()->is_bareword()) { 81 auto& span = span_for_node(node->path()); 82 span.attributes.color = m_palette.link(); 83 span.attributes.underline = true; 84 } else { 85 NodeVisitor::visit(node); 86 } 87 } 88 virtual void visit(const AST::And* node) override 89 { 90 { 91 ScopedValueRollback first_in_command { m_is_first_in_command }; 92 node->left()->visit(*this); 93 } 94 { 95 ScopedValueRollback first_in_command { m_is_first_in_command }; 96 node->right()->visit(*this); 97 } 98 99 auto& span = span_for_node(node); 100 set_offset_range_start(span.range, node->and_position().start_line); 101 set_offset_range_end(span.range, node->and_position().end_line); 102 span.attributes.color = m_palette.syntax_punctuation(); 103 span.attributes.bold = true; 104 } 105 virtual void visit(const AST::ListConcatenate* node) override 106 { 107 NodeVisitor::visit(node); 108 } 109 virtual void visit(const AST::Background* node) override 110 { 111 NodeVisitor::visit(node); 112 113 auto& span = span_for_node(node); 114 set_offset_range_start(span.range, node->position().end_line, 1); 115 span.attributes.color = m_palette.syntax_punctuation(); 116 span.attributes.bold = true; 117 } 118 virtual void visit(const AST::BraceExpansion* node) override 119 { 120 NodeVisitor::visit(node); 121 } 122 virtual void visit(const AST::BarewordLiteral* node) override 123 { 124 NodeVisitor::visit(node); 125 126 auto& span = span_for_node(node); 127 if (m_is_first_in_command) { 128 span.attributes.color = m_palette.syntax_keyword(); 129 span.attributes.bold = true; 130 m_is_first_in_command = false; 131 } else if (node->text().starts_with('-')) { 132 span.attributes.color = m_palette.syntax_preprocessor_statement(); 133 } else { 134 span.attributes.color = m_palette.base_text(); 135 } 136 } 137 virtual void visit(const AST::CastToCommand* node) override 138 { 139 NodeVisitor::visit(node); 140 } 141 virtual void visit(const AST::CastToList* node) override 142 { 143 NodeVisitor::visit(node); 144 145 auto& start_span = span_for_node(node); 146 start_span.attributes.color = m_palette.syntax_punctuation(); 147 start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 2 }); 148 start_span.data = static_cast<u64>(AugmentedTokenKind::OpenParen); 149 150 auto& end_span = span_for_node(node); 151 end_span.attributes.color = m_palette.syntax_punctuation(); 152 set_offset_range_start(end_span.range, node->position().end_line, 1); 153 end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen); 154 } 155 virtual void visit(const AST::CloseFdRedirection* node) override 156 { 157 NodeVisitor::visit(node); 158 } 159 virtual void visit(const AST::CommandLiteral* node) override 160 { 161 NodeVisitor::visit(node); 162 } 163 virtual void visit(const AST::Comment* node) override 164 { 165 NodeVisitor::visit(node); 166 167 auto& span = span_for_node(node); 168 span.attributes.color = m_palette.syntax_comment(); 169 } 170 virtual void visit(const AST::ContinuationControl* node) override 171 { 172 NodeVisitor::visit(node); 173 174 auto& span = span_for_node(node); 175 span.attributes.color = m_palette.syntax_control_keyword(); 176 } 177 virtual void visit(const AST::DynamicEvaluate* node) override 178 { 179 NodeVisitor::visit(node); 180 181 auto& start_span = span_for_node(node); 182 start_span.attributes.color = m_palette.syntax_punctuation(); 183 start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 }); 184 } 185 virtual void visit(const AST::DoubleQuotedString* node) override 186 { 187 NodeVisitor::visit(node); 188 189 auto& start_span = span_for_node(node); 190 start_span.attributes.color = m_palette.syntax_string(); 191 start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 1 }); 192 start_span.is_skippable = true; 193 194 auto& end_span = span_for_node(node); 195 set_offset_range_start(end_span.range, node->position().end_line, 1); 196 end_span.attributes.color = m_palette.syntax_string(); 197 end_span.is_skippable = true; 198 199 if (m_is_first_in_command) { 200 start_span.attributes.bold = true; 201 end_span.attributes.bold = true; 202 } 203 m_is_first_in_command = false; 204 } 205 virtual void visit(const AST::Fd2FdRedirection* node) override 206 { 207 NodeVisitor::visit(node); 208 } 209 virtual void visit(const AST::FunctionDeclaration* node) override 210 { 211 NodeVisitor::visit(node); 212 213 // fn name 214 auto& name_span = span_for_node(node); 215 set_offset_range_start(name_span.range, node->name().position.start_line); 216 set_offset_range_end(name_span.range, node->name().position.end_line); 217 name_span.attributes.color = m_palette.syntax_identifier(); 218 219 // arguments 220 for (auto& arg : node->arguments()) { 221 auto& name_span = span_for_node(node); 222 set_offset_range_start(name_span.range, arg.position.start_line); 223 set_offset_range_end(name_span.range, arg.position.end_line); 224 name_span.attributes.color = m_palette.syntax_identifier(); 225 } 226 } 227 virtual void visit(const AST::ForLoop* node) override 228 { 229 // The iterated expression is an expression, not a command. 230 m_is_first_in_command = false; 231 NodeVisitor::visit(node); 232 233 // "for" 234 auto& for_span = span_for_node(node); 235 // FIXME: "fo\\\nr" is valid too 236 for_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 3 }); 237 for_span.attributes.color = m_palette.syntax_keyword(); 238 239 // "in" 240 if (auto maybe_position = node->in_keyword_position(); maybe_position.has_value()) { 241 auto& position = maybe_position.value(); 242 243 auto& in_span = span_for_node(node); 244 set_offset_range_start(in_span.range, position.start_line); 245 set_offset_range_end(in_span.range, position.end_line); 246 in_span.attributes.color = m_palette.syntax_keyword(); 247 } 248 249 // "index" 250 if (auto maybe_position = node->index_keyword_position(); maybe_position.has_value()) { 251 auto& position = maybe_position.value(); 252 253 auto& index_span = span_for_node(node); 254 set_offset_range_start(index_span.range, position.start_line); 255 set_offset_range_end(index_span.range, position.end_line); 256 index_span.attributes.color = m_palette.syntax_keyword(); 257 } 258 259 // variables 260 if (auto maybe_variable = node->variable(); maybe_variable.has_value()) { 261 auto& position = maybe_variable->position; 262 263 auto& variable_span = span_for_node(node); 264 set_offset_range_start(variable_span.range, position.start_line); 265 set_offset_range_end(variable_span.range, position.end_line); 266 variable_span.attributes.color = m_palette.syntax_identifier(); 267 } 268 269 if (auto maybe_variable = node->index_variable(); maybe_variable.has_value()) { 270 auto& position = maybe_variable->position; 271 272 auto& variable_span = span_for_node(node); 273 set_offset_range_start(variable_span.range, position.start_line); 274 set_offset_range_end(variable_span.range, position.end_line); 275 variable_span.attributes.color = m_palette.syntax_identifier(); 276 } 277 } 278 virtual void visit(const AST::Glob* node) override 279 { 280 NodeVisitor::visit(node); 281 282 auto& span = span_for_node(node); 283 span.attributes.color = m_palette.syntax_preprocessor_value(); 284 } 285 virtual void visit(const AST::Execute* node) override 286 { 287 TemporaryChange first { m_is_first_in_command, true }; 288 NodeVisitor::visit(node); 289 290 if (node->does_capture_stdout()) { 291 auto& start_span = span_for_node(node); 292 start_span.attributes.color = m_palette.syntax_punctuation(); 293 start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 2 }); 294 start_span.data = static_cast<u64>(AugmentedTokenKind::OpenParen); 295 296 auto& end_span = span_for_node(node); 297 end_span.attributes.color = m_palette.syntax_punctuation(); 298 set_offset_range_start(end_span.range, node->position().end_line, 1); 299 end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen); 300 } 301 } 302 virtual void visit(const AST::IfCond* node) override 303 { 304 m_is_first_in_command = false; 305 NodeVisitor::visit(node); 306 307 // "if" 308 auto& if_span = span_for_node(node); 309 // FIXME: "i\\\nf" is valid too 310 if_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 2 }); 311 if_span.attributes.color = m_palette.syntax_keyword(); 312 313 // "else" 314 if (auto maybe_position = node->else_position(); maybe_position.has_value()) { 315 auto& position = maybe_position.value(); 316 317 auto& else_span = span_for_node(node); 318 set_offset_range_start(else_span.range, position.start_line); 319 set_offset_range_end(else_span.range, position.end_line); 320 else_span.attributes.color = m_palette.syntax_keyword(); 321 } 322 } 323 324 virtual void visit(const AST::ImmediateExpression* node) override 325 { 326 TemporaryChange first { m_is_first_in_command, false }; 327 NodeVisitor::visit(node); 328 329 // ${ 330 auto& start_span = span_for_node(node); 331 start_span.attributes.color = m_palette.syntax_punctuation(); 332 start_span.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 2 }); 333 start_span.data = static_cast<u64>(AugmentedTokenKind::OpenParen); 334 335 // Function name 336 auto& name_span = span_for_node(node); 337 name_span.attributes.color = m_palette.syntax_preprocessor_statement(); // Closest thing we have to this 338 set_offset_range_start(name_span.range, node->function_position().start_line); 339 set_offset_range_end(name_span.range, node->function_position().end_line); 340 341 // } 342 auto& end_span = span_for_node(node); 343 end_span.attributes.color = m_palette.syntax_punctuation(); 344 set_offset_range_start(end_span.range, node->position().end_line, 1); 345 end_span.data = static_cast<u64>(AugmentedTokenKind::CloseParen); 346 } 347 348 virtual void visit(const AST::Join* node) override 349 { 350 NodeVisitor::visit(node); 351 } 352 virtual void visit(const AST::MatchExpr* node) override 353 { 354 // The matched expression is an expression, not a command. 355 m_is_first_in_command = false; 356 NodeVisitor::visit(node); 357 358 // "match" 359 auto& match_expr = span_for_node(node); 360 // FIXME: "mat\\\nch" is valid too 361 match_expr.range.set_end({ node->position().start_line.line_number, node->position().start_line.line_column + 5 }); 362 match_expr.attributes.color = m_palette.syntax_keyword(); 363 364 // "as" 365 if (auto maybe_position = node->as_position(); maybe_position.has_value()) { 366 auto& position = maybe_position.value(); 367 368 auto& as_span = span_for_node(node); 369 as_span.range.set_start({ position.start_line.line_number, position.start_line.line_column }); 370 as_span.range.set_end({ position.end_line.line_number, position.end_line.line_column + 1 }); 371 as_span.attributes.color = m_palette.syntax_keyword(); 372 } 373 } 374 virtual void visit(const AST::Or* node) override 375 { 376 { 377 ScopedValueRollback first_in_command { m_is_first_in_command }; 378 node->left()->visit(*this); 379 } 380 { 381 ScopedValueRollback first_in_command { m_is_first_in_command }; 382 node->right()->visit(*this); 383 } 384 385 auto& span = span_for_node(node); 386 set_offset_range_start(span.range, node->or_position().start_line); 387 set_offset_range_end(span.range, node->or_position().end_line); 388 span.attributes.color = m_palette.syntax_punctuation(); 389 span.attributes.bold = true; 390 } 391 virtual void visit(const AST::Pipe* node) override 392 { 393 NodeVisitor::visit(node); 394 } 395 virtual void visit(const AST::Range* node) override 396 { 397 NodeVisitor::visit(node); 398 399 auto& start_span = span_for_node(node->start()); 400 auto& start_position = node->start()->position(); 401 set_offset_range_start(start_span.range, start_position.start_line, 1); 402 start_span.range.set_end({ start_position.start_line.line_number, start_position.start_line.line_column + 1 }); 403 start_span.attributes.color = m_palette.syntax_punctuation(); 404 405 auto& end_span = span_for_node(node->start()); 406 auto& end_position = node->end()->position(); 407 set_offset_range_start(end_span.range, end_position.end_line, 1); 408 start_span.range.set_end({ end_position.start_line.line_number, end_position.start_line.line_column + 1 }); 409 410 end_span.attributes.color = m_palette.syntax_punctuation(); 411 } 412 virtual void visit(const AST::ReadRedirection* node) override 413 { 414 NodeVisitor::visit(node); 415 } 416 virtual void visit(const AST::ReadWriteRedirection* node) override 417 { 418 NodeVisitor::visit(node); 419 } 420 virtual void visit(const AST::Sequence* node) override 421 { 422 for (auto& entry : node->entries()) { 423 ScopedValueRollback first_in_command { m_is_first_in_command }; 424 entry->visit(*this); 425 } 426 427 for (auto& position : node->separator_positions()) { 428 if (position.start_offset == position.end_offset) 429 continue; 430 auto& span = span_for_node(node); 431 set_offset_range_start(span.range, position.start_line); 432 set_offset_range_end(span.range, position.end_line); 433 span.attributes.color = m_palette.syntax_punctuation(); 434 span.attributes.bold = true; 435 span.is_skippable = true; 436 } 437 } 438 virtual void visit(const AST::Subshell* node) override 439 { 440 NodeVisitor::visit(node); 441 } 442 virtual void visit(const AST::SimpleVariable* node) override 443 { 444 NodeVisitor::visit(node); 445 446 auto& span = span_for_node(node); 447 span.attributes.color = m_palette.syntax_identifier(); 448 } 449 virtual void visit(const AST::SpecialVariable* node) override 450 { 451 NodeVisitor::visit(node); 452 453 auto& span = span_for_node(node); 454 span.attributes.color = m_palette.syntax_identifier(); 455 } 456 virtual void visit(const AST::Juxtaposition* node) override 457 { 458 NodeVisitor::visit(node); 459 } 460 virtual void visit(const AST::StringLiteral* node) override 461 { 462 NodeVisitor::visit(node); 463 464 if (node->text().is_empty()) 465 return; 466 467 auto& span = span_for_node(node); 468 span.attributes.color = m_palette.syntax_string(); 469 if (m_is_first_in_command) 470 span.attributes.bold = true; 471 m_is_first_in_command = false; 472 } 473 virtual void visit(const AST::StringPartCompose* node) override 474 { 475 NodeVisitor::visit(node); 476 } 477 virtual void visit(const AST::SyntaxError* node) override 478 { 479 NodeVisitor::visit(node); 480 481 auto& span = span_for_node(node); 482 span.attributes.underline = true; 483 span.attributes.background_color = Color(Color::NamedColor::MidRed).lightened(1.3f).with_alpha(128); 484 span.attributes.color = m_palette.base_text(); 485 } 486 virtual void visit(const AST::Tilde* node) override 487 { 488 NodeVisitor::visit(node); 489 490 auto& span = span_for_node(node); 491 span.attributes.color = m_palette.link(); 492 } 493 virtual void visit(const AST::VariableDeclarations* node) override 494 { 495 TemporaryChange first_in_command { m_is_first_in_command, false }; 496 for (auto& decl : node->variables()) { 497 auto& name_span = span_for_node(decl.name); 498 name_span.attributes.color = m_palette.syntax_identifier(); 499 500 decl.value->visit(*this); 501 502 auto& start_span = span_for_node(decl.name); 503 start_span.range.set_start({ decl.name->position().end_line.line_number, decl.name->position().end_line.line_column }); 504 start_span.range.set_end({ decl.value->position().start_line.line_number, decl.value->position().start_line.line_column + 1 }); 505 start_span.attributes.color = m_palette.syntax_punctuation(); 506 start_span.data = static_cast<u64>(AugmentedTokenKind::OpenParen); 507 } 508 } 509 virtual void visit(const AST::WriteAppendRedirection* node) override 510 { 511 NodeVisitor::visit(node); 512 } 513 virtual void visit(const AST::WriteRedirection* node) override 514 { 515 NodeVisitor::visit(node); 516 } 517 518 Vector<GUI::TextDocumentSpan>& m_spans; 519 Gfx::Palette const& m_palette; 520 const GUI::TextDocument& m_document; 521 bool m_is_first_in_command { false }; 522}; 523 524bool SyntaxHighlighter::is_identifier(u64 token) const 525{ 526 if (!token) 527 return false; 528 529 auto kind = (size_t)token; 530 return kind == (size_t)AST::Node::Kind::BarewordLiteral 531 || kind == (size_t)AST::Node::Kind::StringLiteral 532 || kind == (size_t)AST::Node::Kind::Tilde; 533} 534 535bool SyntaxHighlighter::is_navigatable(u64) const 536{ 537 return false; 538} 539 540void SyntaxHighlighter::rehighlight(Palette const& palette) 541{ 542 auto text = m_client->get_text(); 543 544 Parser parser(text); 545 auto ast = parser.parse(); 546 547 Vector<GUI::TextDocumentSpan> spans; 548 HighlightVisitor visitor { spans, palette, m_client->get_document() }; 549 550 if (ast) 551 ast->visit(visitor); 552 553 quick_sort(spans, [](auto& a, auto& b) { return a.range.start() < b.range.start() && a.range.end() < b.range.end(); }); 554 555 if constexpr (SYNTAX_HIGHLIGHTING_DEBUG) { 556 for (auto& span : spans) { 557 dbgln("Kind {}, range {}.", span.data, span.range); 558 } 559 } 560 561 m_client->do_set_spans(move(spans)); 562 m_has_brace_buddies = false; 563 highlight_matching_token_pair(); 564 m_client->do_update(); 565} 566 567Vector<Syntax::Highlighter::MatchingTokenPair> SyntaxHighlighter::matching_token_pairs_impl() const 568{ 569 static Vector<MatchingTokenPair> pairs; 570 if (pairs.is_empty()) { 571 pairs.append({ 572 static_cast<u64>(AugmentedTokenKind::OpenParen), 573 static_cast<u64>(AugmentedTokenKind::CloseParen), 574 }); 575 } 576 return pairs; 577} 578 579bool SyntaxHighlighter::token_types_equal(u64 token0, u64 token1) const 580{ 581 return token0 == token1; 582} 583 584}