manen

Fancy Lua REPL
Log | Files | Refs | README | LICENSE

commit 13eb26bc302409871350027813e37ad16b8a66cd
parent e09605dbc225e231c98c17ce6e504742231aea56
Author: Sylvia Ivory <git@sivory.net>
Date:   Fri, 20 Jun 2025 19:35:56 -0700

Add syntax highlighting

Diffstat:
MCargo.lock | 136+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MCargo.toml | 3+++
Msrc/editor.rs | 6+++++-
Asrc/highlight.rs | 150+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 2++
5 files changed, 296 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -18,6 +18,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -195,6 +204,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" [[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] name = "errno" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -232,6 +247,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -268,6 +289,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -277,6 +308,12 @@ dependencies = [ ] [[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -352,7 +389,10 @@ dependencies = [ "color-eyre", "comfy-table", "mlua", + "nu-ansi-term", "reedline", + "tree-sitter-highlight", + "tree-sitter-lua", ] [[package]] @@ -533,6 +573,35 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] name = "rustc-demangle" version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -577,6 +646,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -603,6 +678,19 @@ dependencies = [ ] [[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -654,6 +742,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] name = "strip-ansi-escapes" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -763,6 +857,48 @@ dependencies = [ ] [[package]] +name = "tree-sitter" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cf18d43cbf0bfca51f657132cc616a5097edc4424d538bae6fa60142eaf9f0" +dependencies = [ + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-highlight" +version = "0.25.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea684ab5dd71e19f6c0add355be96f2b4eb58327cb305337415208681761aa" +dependencies = [ + "regex", + "streaming-iterator", + "thiserror", + "tree-sitter", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" + +[[package]] +name = "tree-sitter-lua" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb9adf0965fec58e7660cbb3a059dbb12ebeec9459e6dcbae3db004739641e" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -7,4 +7,7 @@ edition = "2024" color-eyre = "0.6.5" comfy-table = "7.1.4" mlua = { version = "0.10.5", features = ["lua54", "vendored", "anyhow"] } +nu-ansi-term = "0.50.1" reedline = "0.40.0" +tree-sitter-highlight = "0.25.6" +tree-sitter-lua = "0.2.0" diff --git a/src/editor.rs b/src/editor.rs @@ -1,6 +1,8 @@ use mlua::prelude::*; use reedline::{DefaultPrompt, DefaultPromptSegment, Reedline, Signal}; +use crate::highlight::LuaHighlighter; + pub enum TableFormat { Ascii, Lua, @@ -21,12 +23,14 @@ impl Editor { let lua = Lua::new(); let version: String = lua.globals().get("_VERSION")?; - let editor = Reedline::create(); let prompt = DefaultPrompt::new( DefaultPromptSegment::Basic(version), DefaultPromptSegment::Empty, ); + let editor = Reedline::create() + .with_highlighter(Box::new(LuaHighlighter::new())); + Ok(Self { prompt, editor, diff --git a/src/highlight.rs b/src/highlight.rs @@ -0,0 +1,150 @@ +use std::cell::RefCell; + +use nu_ansi_term::{Color, Style}; +use reedline::StyledText; +use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent}; + +const LUA_HIGHLIGHT_NAMES: &[&str] = &[ + "keyword", + "keyword.return", + "keyword.function", + "keyword.operator", + + "punctuation.delimiter", + "punctuation.bracket", + + "variable", + "variable.builtin", + + "constant", + "constant.builtin", + + "function", + "function.call", + "function.builtin", + "method", + "parameter", + + "string", + "string.escape", + "boolean", + "number", + + "label", + "repeat", + "conditional", + "operator", + "comment", +]; + +const fn style_fg(color: Color) -> Style { + Style { + foreground: Some(color), + background: None, + is_bold: false, + is_dimmed: false, + is_italic: false, + is_underline: false, + is_blink: false, + is_reverse: false, + is_hidden: false, + is_strikethrough: false, + prefix_with_reset: false, + } +} + +const STYLES: &[Style] = &[ + style_fg(Color::Purple), + style_fg(Color::Purple), + style_fg(Color::Purple), + style_fg(Color::Purple), + + style_fg(Color::LightGray), + style_fg(Color::LightRed), + + style_fg(Color::LightGray), + style_fg(Color::Red), + + style_fg(Color::Magenta), + style_fg(Color::Magenta), + + style_fg(Color::LightBlue), + style_fg(Color::LightBlue), + style_fg(Color::LightBlue), + style_fg(Color::LightBlue), + style_fg(Color::LightRed), + + style_fg(Color::Green), + style_fg(Color::Cyan), + style_fg(Color::Yellow), + style_fg(Color::Yellow), + + style_fg(Color::LightGray), + style_fg(Color::Purple), + style_fg(Color::Purple), + style_fg(Color::LightBlue), + style_fg(Color::DarkGray), +]; + +pub struct LuaHighlighter { + highlighter: RefCell<tree_sitter_highlight::Highlighter>, + config: HighlightConfiguration, +} + +impl LuaHighlighter { + pub fn new() -> Self { + let highlighter = tree_sitter_highlight::Highlighter::new(); + let mut config = HighlightConfiguration::new( + tree_sitter_lua::LANGUAGE.into(), + "lua", + tree_sitter_lua::HIGHLIGHTS_QUERY, + tree_sitter_lua::INJECTIONS_QUERY, + tree_sitter_lua::LOCALS_QUERY + ).unwrap(); + + config.configure(LUA_HIGHLIGHT_NAMES); + + Self { + highlighter: RefCell::new(highlighter), + config + } + } +} + +impl reedline::Highlighter for LuaHighlighter { + fn highlight(&self, line: &str, _cursor: usize) -> StyledText { + let mut binding = self.highlighter.borrow_mut(); + let highlights = binding.highlight( + &self.config, + line.as_bytes(), + None, + |_| None + ); + + let mut text = StyledText::new(); + + let highlights = if let Ok(highlights) = highlights { + highlights + } else { + text.push((Style::new(), line.to_string())); + + return text + }; + + let mut style = Style::new(); + + for event in highlights.flatten() { + match event { + HighlightEvent::Source {start, end} => { + text.push((style, line[start..end].to_string())) + }, + HighlightEvent::HighlightStart(s) => { + style = STYLES[s.0]; + }, + HighlightEvent::HighlightEnd => {} + } + } + + text + } +} diff --git a/src/main.rs b/src/main.rs @@ -1,5 +1,7 @@ use editor::Editor; + mod editor; +mod highlight; fn main() -> color_eyre::Result<()> { Editor::new()?.run();