From a276bf60dab0a7b15812a360a5f561159a48a93f Mon Sep 17 00:00:00 2001 From: Syed Daanish Date: Thu, 3 Sep 2026 16:46:13 +0100 Subject: [PATCH] Add command syntax highlighting. --- include/internal/io/tokens.h | 5 +- include/internal/parser/parser.h | 6 +- include/internal/ui/command.h | 25 ------ src/internal/buffer/shard.cc | 2 - src/internal/functions/extended.cc | 21 +++++ src/internal/parser/address.cc | 136 ++++++++++++++++++++++++++++- src/internal/parser/operation.cc | 66 +++++++++++++- src/internal/parser/parser.cc | 23 ++++- src/internal/syntax/parser.cc | 2 +- src/internal/theme/theme.cc | 82 +++++++++-------- src/internal/ui/command/command.cc | 18 +++- src/internal/vase/shard.cc | 2 + 12 files changed, 304 insertions(+), 84 deletions(-) diff --git a/include/internal/io/tokens.h b/include/internal/io/tokens.h index 62f17e1..3adfd1f 100644 --- a/include/internal/io/tokens.h +++ b/include/internal/io/tokens.h @@ -5,8 +5,8 @@ namespace bed::internal::io { struct Token { - uint32_t start; - uint32_t end; + uint64_t start; + uint64_t end; enum Kind : uint8_t { TempCurrent, BufferName, @@ -24,7 +24,6 @@ struct Token { Shell, Ruby, File, - Regex, Replacement, Suffix, Color1, diff --git a/include/internal/parser/parser.h b/include/internal/parser/parser.h index aee2cb3..a2a9372 100644 --- a/include/internal/parser/parser.h +++ b/include/internal/parser/parser.h @@ -85,12 +85,12 @@ struct Parser { std::string_view cmd; uint16_t i; Command *command; - std::vector *tokens; + std::vector *tokens; CompletionContext *completion; explicit Parser( std::string_view cmd, BEd &bed, Command *command, - std::vector *tokens, CompletionContext *completion + std::vector *tokens, CompletionContext *completion ); char peek(uint16_t = 0); // == \0 if at eof. @@ -107,7 +107,7 @@ struct Parser { void operation(); void parse(); - + static std::vector get_highlight(std::string_view cmd, BEd &bed); static Command get_command(std::string_view, BEd &); static std::vector get_addresses(std::string_view cmd, BEd &bed); }; diff --git a/include/internal/ui/command.h b/include/internal/ui/command.h index 704dac8..db8b2b9 100644 --- a/include/internal/ui/command.h +++ b/include/internal/ui/command.h @@ -4,31 +4,6 @@ #include "pch.h" namespace bed::internal::ui { -struct Token { - enum struct Type : uint8_t { - TempCurrent, // @ - AddressSeperator, // ; , - Address, // . $ % [ ] ^ ~ - Offset, // +N -N + - - AddressRegex, // /re/ ?re? - AddressSymbol, // >s> - Number, // 10 - Mark, // 'm - RubyFunction, // (func:arg) - RubyArg, - Function, - Any, - Shell, - Ruby, - File, - Regex, - Replacement, - Suffix - } type; - uint16_t start; - uint16_t end; -}; - struct CommandIO { std::string cmd; uint16_t cursor; diff --git a/src/internal/buffer/shard.cc b/src/internal/buffer/shard.cc index 8187ff5..37e5d56 100644 --- a/src/internal/buffer/shard.cc +++ b/src/internal/buffer/shard.cc @@ -29,8 +29,6 @@ uint64_t ShardBuffer::find_prev(std::string_view pattern, uint64_t start) { uint64_t ShardBuffer::next_closing(uint64_t start) { if (parse.lang) { uint64_t closing = syntax::next_closing(parse, start - 1); - if (closing == UINT64_MAX) - return lines(); return closing + 1; } else { start += 10; diff --git a/src/internal/functions/extended.cc b/src/internal/functions/extended.cc index 0102b1b..e76ba9d 100644 --- a/src/internal/functions/extended.cc +++ b/src/internal/functions/extended.cc @@ -170,6 +170,27 @@ void Function::register_extented(BEd &ctx) { ) {}, } ); + ctx.functions.insert( + "`", + Function{ + .address_kind = Function::AddressKind::None, + .argument_kind = Function::ArgumentKind::Ruby, + .input_mode = Function::InputMode::None, + .desc = "Execute some ruby code", + .default_address = "", + .accept_zero = false, + .pre_text_mode = nullptr, + .handle = []( + BEd &, + const buffer::Address &, + vase::Shard *, + const Argument &, + std::vector * + ) { + // TODO: connect up to mruby. + }, + } + ); ctx.functions.insert( "echo", Function{ diff --git a/src/internal/parser/address.cc b/src/internal/parser/address.cc index 424b097..ed69381 100644 --- a/src/internal/parser/address.cc +++ b/src/internal/parser/address.cc @@ -6,34 +6,74 @@ void Parser::locator(AddressPromise &addr) { addr.base = AddressPromise::None{}; switch (peek()) { case '.': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Current{}; break; case '$': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Last{}; break; case '%': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::LastRange{}; break; case '[': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Block{Direction::Backward}; break; case ']': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Block{Direction::Forward}; break; case '^': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Diagnostic{Direction::Backward}; break; case '~': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Diagnostic{Direction::Forward}; break; case '\'': + tokens->push_back( + {.start = i, + .end = i + (uint64_t)2, + .type = io::Token::Mark} + ); advance(); if (('a' <= peek() && peek() <= 'z') || ('A' <= peek() && peek() <= 'Z')) @@ -43,6 +83,11 @@ void Parser::locator(AddressPromise &addr) { advance(); break; case '{': { + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); uint16_t j = 0; std::string func; @@ -64,8 +109,24 @@ void Parser::locator(AddressPromise &addr) { arg = peek_str(j); else func = peek_str(j); + tokens->push_back( + {.start = i, + .end = i + (uint64_t)func.size(), + .type = io::Token::RubyFunction} + ); + if (arg.size()) + tokens->push_back( + {.start = i + (uint64_t)func.size() + 1, + .end = i + (uint64_t)func.size() + 1 + (uint64_t)arg.size(), + .type = io::Token::RubyFunction} + ); addr.base = AddressPromise::Scripted{std::move(func), std::move(arg)}; advance(j + 1); + tokens->push_back( + {.start = i, + .end = i - (uint64_t)1, + .type = io::Token::AddressSymbol} + ); } break; case '/': { advance(); @@ -87,9 +148,19 @@ void Parser::locator(AddressPromise &addr) { Direction::Forward, std::string(peek_str(j)) ); + tokens->push_back( + {.start = (uint64_t)i - 1, + .end = (uint64_t)i + j + 1, + .type = io::Token::Regexp} + ); advance(j + 1); } break; case '?': { + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); uint16_t j = 0; while (true) { @@ -109,6 +180,11 @@ void Parser::locator(AddressPromise &addr) { Direction::Backward, std::string(peek_str(j)) ); + tokens->push_back( + {.start = (uint64_t)i - 1, + .end = (uint64_t)i + j + 1, + .type = io::Token::Regexp} + ); advance(j + 1); } break; case '<': { @@ -132,6 +208,11 @@ void Parser::locator(AddressPromise &addr) { }; break; } + tokens->push_back( + {.start = (uint64_t)i - 1, + .end = (uint64_t)i + j + 1, + .type = io::Token::AddressSymbol} + ); advance(j + 1); } break; case '>': { @@ -149,9 +230,19 @@ void Parser::locator(AddressPromise &addr) { }; break; } + tokens->push_back( + {.start = (uint64_t)i - 1, + .end = (uint64_t)i + j + 1, + .type = io::Token::AddressSymbol} + ); advance(j + 1); } break; case '+': { + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Current{}; uint16_t j = 0; @@ -162,10 +253,20 @@ void Parser::locator(AddressPromise &addr) { } if (j == 0) num = 1; + tokens->push_back( + {.start = (uint64_t)i, + .end = (uint64_t)i + j, + .type = io::Token::Number} + ); advance(j); addr.offset += num; } break; case '-': { + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); addr.base = AddressPromise::Current{}; uint16_t j = 0; @@ -176,17 +277,28 @@ void Parser::locator(AddressPromise &addr) { } if (j == 0) num = 1; + tokens->push_back( + {.start = (uint64_t)i, + .end = (uint64_t)i + j, + .type = io::Token::Number} + ); advance(j); addr.offset -= num; } break; default: if ('0' <= peek() && peek() <= '9') { + uint16_t start = i; uint64_t num = 0; while ('0' <= peek() && peek() <= '9') { num = num * 10 + (peek() - '0'); advance(); } addr.base = AddressPromise::Number{num}; + tokens->push_back( + {.start = (uint64_t)start, + .end = (uint64_t)i, + .type = io::Token::Number} + ); } } } @@ -196,14 +308,25 @@ int64_t Parser::offset() { while (peek() == '+' || peek() == '-' || ('0' <= peek() && peek() <= '9')) { bool positive = peek() != '-'; - if (peek() == '+' || peek() == '-') + if (peek() == '+' || peek() == '-') { + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSymbol} + ); advance(); + } uint16_t j = 0; int64_t num = 0; while ('0' <= peek(j) && peek(j) <= '9') num = num * 10 + (peek(j++) - '0'); if (j == 0) num = 1; + tokens->push_back( + {.start = (uint64_t)i, + .end = (uint64_t)i + j, + .type = io::Token::Number} + ); advance(j); offset += positive ? num : -num; skip_ws(); @@ -214,6 +337,7 @@ int64_t Parser::offset() { void Parser::address(AddressPromise &addr) { if (peek() == ':') { + uint16_t start = i; advance(); uint16_t j = 0; while (peek(j) != ':' && peek(j) != '\0') @@ -222,6 +346,11 @@ void Parser::address(AddressPromise &addr) { advance(j); if (peek() == ':') advance(); + tokens->push_back( + {.start = start, + .end = i, + .type = io::Token::BufferName} + ); } skip_ws(); if (peek() == '\0') @@ -238,6 +367,11 @@ void Parser::addresses(std::vector &addresses) { address(*addr); while (peek() == ',' || peek() == ';') { addr->jumping = peek() == ';'; + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::AddressSeperator} + ); advance(); skip_ws(); addresses.push_back({}); diff --git a/src/internal/parser/operation.cc b/src/internal/parser/operation.cc index 8270c70..40facfa 100644 --- a/src/internal/parser/operation.cc +++ b/src/internal/parser/operation.cc @@ -11,6 +11,11 @@ void Parser::operation() { if (len == 0) throw ed_error("Function not found."); functions::Function *function = bed.functions.get_ptr(peek_str(len)); + tokens->push_back( + {.start = i, + .end = i + len, + .type = io::Token::Function} + ); advance(len); command->function = function; char suffix = '\0'; @@ -27,10 +32,20 @@ void Parser::operation() { command->argument = peek(); else throw ed_error("Valid mark needed."); + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::Mark} + ); advance(); break; case functions::Function::ArgumentKind::Any: command->argument = std::string(peek_str()); + tokens->push_back( + {.start = i, + .end = i + peek_str().size(), + .type = io::Token::Any} + ); advance(peek_str().size()); break; case functions::Function::ArgumentKind::Global: { @@ -108,8 +123,18 @@ void Parser::operation() { skip_ws(); switch (peek()) { case '!': + tokens->push_back( + {.start = i, + .end = (uint64_t)i + 1, + .type = io::Token::Error} + ); advance(); command->argument = functions::Function::ShellArg(std::string(peek_str())); + tokens->push_back( + {.start = i, + .end = i + peek_str().size(), + .type = io::Token::Shell} + ); advance(peek_str().size()); break; case '\0': @@ -117,6 +142,11 @@ void Parser::operation() { break; default: command->argument = std::filesystem::path(peek_str()); + tokens->push_back( + {.start = i, + .end = i + peek_str().size(), + .type = io::Token::File} + ); advance(peek_str().size()); break; } @@ -133,6 +163,7 @@ void Parser::operation() { char delim = peek(); if (delim == '\0') throw ed_error("regex expected"); + uint16_t start = i; advance(); uint16_t j = 0; while (true) { @@ -172,8 +203,18 @@ void Parser::operation() { advance(j); options = "p"; } + tokens->push_back( + {.start = start, + .end = i, + .type = io::Token::Regexp} + ); if (peek() != '\0') { options = std::string(peek_str()); + tokens->push_back( + {.start = i, + .end = i + peek_str().size(), + .type = io::Token::Suffix} + ); advance(peek_str().size()); } std::erase_if(options, [&](char c) { @@ -185,17 +226,38 @@ void Parser::operation() { }); command->argument = functions::Function::RegexArg(expression, replacement, options); } break; - case functions::Function::ArgumentKind::Ruby: + case functions::Function::ArgumentKind::Ruby: { command->argument = functions::Function::RubyArg(std::string(peek_str())); + auto *ruby_parser = bed.languages["ruby"]; + void *state = ruby_parser->none_state(); + std::vector events; + std::vector tokens_; + ruby_parser->parse(&state, peek_str(), false, &tokens_, &events); + for (auto &token : tokens_) { + token.start += i; + token.end += i; + tokens->push_back(token); + } + ruby_parser->destroy(state); advance(peek_str().size()); - break; + } break; case functions::Function::ArgumentKind::Shell: command->argument = functions::Function::ShellArg(std::string(peek_str())); + tokens->push_back( + {.start = i, + .end = i + peek_str().size(), + .type = io::Token::Shell} + ); advance(peek_str().size()); break; } if (!suffix) { suffix = peek(); + tokens->push_back( + {.start = i, + .end = i + (uint64_t)1, + .type = io::Token::Suffix} + ); advance(); } if (suffix) { diff --git a/src/internal/parser/parser.cc b/src/internal/parser/parser.cc index 3e6fee0..21ae3d2 100644 --- a/src/internal/parser/parser.cc +++ b/src/internal/parser/parser.cc @@ -22,6 +22,11 @@ void Parser::skip_ws() { void Parser::parse() { skip_ws(); if (peek() == '@') { + tokens->push_back( + {.start = 0, + .end = 1, + .type = io::Token::TempCurrent} + ); advance(); command->temp_address = true; } else { @@ -37,14 +42,26 @@ void Parser::parse() { Parser::Parser( std::string_view cmd, BEd &bed, Command *command, - std::vector *tokens, CompletionContext *completion + std::vector *tokens, CompletionContext *completion ) : bed(bed), cmd(cmd), command(command), tokens(tokens), completion(completion) { i = 0; } +std::vector Parser::get_highlight(std::string_view cmd, BEd &bed) { + Command c; + std::vector tokens; + CompletionContext completion; + try { + Parser p(cmd, bed, &c, &tokens, &completion); + p.parse(); + } catch (const ed_error &e) { + } + return tokens; +} + Command Parser::get_command(std::string_view cmd, BEd &bed) { Command c; - std::vector tokens; + std::vector tokens; CompletionContext completion; Parser p(cmd, bed, &c, &tokens, &completion); p.parse(); @@ -53,7 +70,7 @@ Command Parser::get_command(std::string_view cmd, BEd &bed) { std::vector Parser::get_addresses(std::string_view cmd, BEd &bed) { std::vector result; - std::vector tokens; + std::vector tokens; CompletionContext completion; Parser p(cmd, bed, nullptr, &tokens, &completion); p.addresses(result); diff --git a/src/internal/syntax/parser.cc b/src/internal/syntax/parser.cc index a5cb9ab..43caea3 100644 --- a/src/internal/syntax/parser.cc +++ b/src/internal/syntax/parser.cc @@ -50,7 +50,7 @@ uint64_t next_closing(const ParserSnapshot &snap, uint64_t line) { c.next(); first_leaf = false; } - return (snap.root->lines() - line > 10 ? line + 10 : snap.root->lines()); + return (snap.root->lines() - line > 10 ? line + 10 : snap.root->lines() - 1); } uint64_t prev_opening(const ParserSnapshot &snap, uint64_t line) { diff --git a/src/internal/theme/theme.cc b/src/internal/theme/theme.cc index a6e0541..41d9927 100644 --- a/src/internal/theme/theme.cc +++ b/src/internal/theme/theme.cc @@ -15,53 +15,54 @@ io::Highlight Theme::get(const io::Token::Kind &token) const { Theme Theme::default_theme() { Theme theme; + theme.hl[io::Token::TempCurrent] = { - .fg = 0x7DCFFF, + .fg = 0x7AA2F7, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::BufferName] = { - .fg = 0xAAD94C, + .fg = 0xBB9AF7, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::AddressSeperator] = { - .fg = 0xFF8F40, + .fg = 0xA9B1D6, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::Address] = { - .fg = 0xD2A6FF, + .fg = 0xE0AF68, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::Offset] = { - .fg = 0xEF5168, + .fg = 0xFFAF70, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::AddressRegex] = { - .fg = 0x7DCFFF, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::AddressSymbol] = { - .fg = 0xAAD94C, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::Mark] = { - .fg = 0xFF8F40, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::RubyFunction] = { .fg = 0xD2A6FF, .bg = 0x000000, .flags = io::Highlight::None, }; + theme.hl[io::Token::AddressSymbol] = { + .fg = 0xFF9E64, + .bg = 0x000000, + .flags = io::Highlight::None, + }; + theme.hl[io::Token::Mark] = { + .fg = 0xFF757F, + .bg = 0x000000, + .flags = io::Highlight::None, + }; + theme.hl[io::Token::RubyFunction] = { + .fg = 0x7DCFFF, + .bg = 0x000000, + .flags = io::Highlight::None, + }; theme.hl[io::Token::RubyArg] = { - .fg = 0xEF5168, + .fg = 0xA9B1D6, .bg = 0x000000, .flags = io::Highlight::None, }; @@ -71,37 +72,32 @@ Theme Theme::default_theme() { .flags = io::Highlight::None, }; theme.hl[io::Token::Shell] = { - .fg = 0xAAD94C, + .fg = 0x89DDFF, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::Ruby] = { - .fg = 0xFF8F40, + .fg = 0x95E6CB, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::File] = { - .fg = 0xD2A6FF, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::Regex] = { - .fg = 0xEF5168, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::Replacement] = { - .fg = 0x7DCFFF, - .bg = 0x000000, - .flags = io::Highlight::None, - }; - theme.hl[io::Token::Suffix] = { .fg = 0xAAD94C, .bg = 0x000000, .flags = io::Highlight::None, }; + theme.hl[io::Token::Replacement] = { + .fg = 0x9ECE6A, + .bg = 0x000000, + .flags = io::Highlight::None, + }; + theme.hl[io::Token::Suffix] = { + .fg = 0xF07178, + .bg = 0x000000, + .flags = io::Highlight::None, + }; theme.hl[io::Token::Color1] = { - .fg = 0x7DCFFF, + .fg = 0x7AA2F7, .bg = 0x000000, .flags = io::Highlight::None, }; @@ -111,17 +107,17 @@ Theme Theme::default_theme() { .flags = io::Highlight::None, }; theme.hl[io::Token::Color3] = { - .fg = 0xFF8F40, + .fg = 0xFF9E64, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::Color4] = { - .fg = 0xD2A6FF, + .fg = 0xBB9AF7, .bg = 0x000000, .flags = io::Highlight::None, }; theme.hl[io::Token::Color5] = { - .fg = 0xEF5168, + .fg = 0xFF757F, .bg = 0x000000, .flags = io::Highlight::None, }; @@ -162,7 +158,7 @@ Theme Theme::default_theme() { .flags = io::Highlight::None, }; theme.hl[io::Token::Regexp] = { - .fg = 0xD2A6FF, + .fg = 0x7DCFFF, .bg = 0x000000, .flags = io::Highlight::None, }; diff --git a/src/internal/ui/command/command.cc b/src/internal/ui/command/command.cc index bc90624..a81cfcf 100644 --- a/src/internal/ui/command/command.cc +++ b/src/internal/ui/command/command.cc @@ -1,5 +1,6 @@ #include "internal/ui/command.h" #include "bed.h" +#include "internal/parser/parser.h" namespace bed::internal::ui { /*template @@ -162,7 +163,22 @@ void CommandIO::redraw() { bed.io.write("\x1b[2K", 4); bed.io.move_cursor(start, 1); bed.io.write(prompt); - bed.io.write(cmd); + const auto tokens = parser::Parser::get_highlight(cmd, bed); + uint32_t pos = 0; + for (const auto &token : tokens) { + const uint32_t tstart = token.start; + const uint32_t tend = token.end; + if (tstart > cmd.size()) + break; + if (pos < tstart) + bed.io.write(cmd.data() + pos, tstart - pos); + bed.io.apply(token.type); + bed.io.write(cmd.data() + tstart, tend - tstart); + bed.io.reset(); + pos = tend; + } + if (pos < cmd.size()) + bed.io.write(cmd.data() + pos, cmd.size() - pos); bed.io.move_cursor(start, prompt.size() + cursor + 1); } } // namespace bed::internal::ui diff --git a/src/internal/vase/shard.cc b/src/internal/vase/shard.cc index e0c95d0..25a1357 100644 --- a/src/internal/vase/shard.cc +++ b/src/internal/vase/shard.cc @@ -289,6 +289,8 @@ Shard *Shard::from_command(const char *cmd, bool posix_ending) { last = nullptr; if (!pieces.empty()) last = (Petal *)pieces.back(); + else + return nullptr; } if (last && ending[0] == '\r') last->length--;