commit 13eb26bc302409871350027813e37ad16b8a66cd
parent e09605dbc225e231c98c17ce6e504742231aea56
Author: Sylvia Ivory <git@sivory.net>
Date: Fri, 20 Jun 2025 19:35:56 -0700
Add syntax highlighting
Diffstat:
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();