Skip to content

Commit 01aebe9

Browse files
timfennisclaude
andcommitted
🎨 Overhaul syntax highlighting colors and add parser-based function detection
Co-Authored-By: Claude Opus 4.6 <[email protected]>
1 parent 05bf359 commit 01aebe9

5 files changed

Lines changed: 181 additions & 10 deletions

File tree

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎ndc_bin/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ itertools.workspace = true
1515
strsim.workspace = true
1616
codespan-reporting = "0.11.1"
1717
ndc_lexer.workspace = true
18+
ndc_parser.workspace = true
1819
ndc_interpreter.workspace = true
1920
ndc_stdlib.workspace = true
2021
ndc_core.workspace = true

‎ndc_bin/src/highlighter.rs‎

Lines changed: 177 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
11
use itertools::Itertools;
2-
use ndc_lexer::{Lexer, Token};
2+
use ndc_lexer::{Lexer, Token, TokenLocation};
3+
use ndc_parser::{Expression, ExpressionLocation, ForBody, ForIteration};
4+
use std::collections::HashSet;
35
use yansi::{Paint, Painted};
46

57
pub(crate) struct AndycppHighlighter;
68

79
impl AndycppHighlighter {
8-
pub fn highlight_line(line: &str) -> Vec<Painted<&str>> {
10+
/// Parser-enhanced highlighting that correctly identifies function names
11+
/// even in method-call syntax like `foo.len`.
12+
pub fn highlight_parsed(line: &str) -> Vec<Painted<&str>> {
13+
let mut function_spans = HashSet::new();
14+
15+
let expressions = Lexer::new(line)
16+
.collect::<Result<Vec<TokenLocation>, _>>()
17+
.ok()
18+
.and_then(|tokens| ndc_parser::Parser::from_tokens(tokens).parse().ok());
19+
20+
if let Some(expressions) = expressions {
21+
for expr in &expressions {
22+
collect_function_spans(expr, &mut function_spans);
23+
}
24+
}
25+
26+
Self::highlight_tokens(line, &function_spans)
27+
}
28+
29+
fn highlight_tokens<'a>(
30+
line: &'a str,
31+
function_spans: &HashSet<usize>,
32+
) -> Vec<Painted<&'a str>> {
933
let Ok(tokens) = Lexer::new(line).collect::<Result<Vec<_>, _>>() else {
1034
return vec![line.red()];
1135
};
@@ -22,26 +46,67 @@ impl AndycppHighlighter {
2246
}
2347

2448
let mut out = Vec::new();
25-
for (range, token) in ranges.into_iter().zip(tokens.into_iter()) {
49+
let pairs: Vec<_> = ranges.into_iter().zip(tokens).collect();
50+
for (i, (range, token)) in pairs.iter().enumerate() {
2651
let substring = &line[range.start..(range.start + range.len())];
52+
let next_token = pairs.get(i + 1).map(|(_, t)| &t.token);
2753

2854
let colored = match &token.token {
29-
Token::String(_) => substring.rgb(70, 200, 128),
55+
// Strings — green
56+
Token::String(_) => substring.rgb(152, 195, 121),
57+
// Numeric literals and booleans — orange
3058
Token::BigInt(_)
3159
| Token::Int64(_)
3260
| Token::Float64(_)
3361
| Token::Complex(_)
62+
| Token::Infinity
3463
| Token::True
35-
| Token::False => substring.rgb(253, 151, 31),
64+
| Token::False => substring.rgb(209, 154, 102),
65+
// Keywords — coral red
66+
Token::Let
67+
| Token::Fn
68+
| Token::If
69+
| Token::Else
70+
| Token::Return
71+
| Token::Break
72+
| Token::Continue
73+
| Token::For
74+
| Token::In
75+
| Token::While
76+
| Token::Pure
77+
| Token::LogicAnd
78+
| Token::LogicOr
79+
| Token::LogicNot => substring.rgb(224, 108, 117),
80+
// Function identifiers — yellow/gold
81+
// Detected by parser (dot-calls, etc.) or by token heuristics as fallback
82+
Token::Identifier(_) if function_spans.contains(&token.span.offset()) => {
83+
substring.rgb(229, 192, 123)
84+
}
85+
Token::Identifier(_) if matches!(next_token, Some(Token::LeftParentheses)) => {
86+
substring.rgb(229, 192, 123)
87+
}
88+
Token::Identifier(_) if i > 0 && matches!(pairs[i - 1].1.token, Token::Fn) => {
89+
substring.rgb(229, 192, 123)
90+
}
91+
// Variable identifiers — blue
92+
Token::Identifier(_) => substring.rgb(97, 175, 239),
93+
// Arrows, fat arrows, and assignment — cyan
94+
Token::RightArrow | Token::FatArrow | Token::EqualsSign | Token::OpAssign(_) => {
95+
substring.rgb(86, 182, 194)
96+
}
97+
// Brackets and delimiters — light gray (neutral)
3698
Token::LeftSquareBracket
3799
| Token::RightSquareBracket
38100
| Token::LeftCurlyBracket
39101
| Token::RightCurlyBracket
40102
| Token::LeftParentheses
41103
| Token::RightParentheses
42-
| Token::MapOpen => substring.rgb(229, 181, 103),
43-
Token::Identifier(_) => substring.rgb(51, 177, 255),
44-
_ => substring.rgb(140, 182, 255).bold(),
104+
| Token::MapOpen
105+
| Token::Semicolon
106+
| Token::Comma
107+
| Token::Colon => substring.rgb(171, 178, 191),
108+
// Operators — purple
109+
_ => substring.rgb(198, 120, 221),
45110
};
46111

47112
out.push(colored);
@@ -50,3 +115,107 @@ impl AndycppHighlighter {
50115
out
51116
}
52117
}
118+
119+
/// Walk the parsed AST and collect the byte offsets of identifiers used as function names.
120+
fn collect_function_spans(expr: &ExpressionLocation, spans: &mut HashSet<usize>) {
121+
match &expr.expression {
122+
Expression::Call {
123+
function,
124+
arguments,
125+
} => {
126+
if let Expression::Identifier { .. } = &function.expression {
127+
spans.insert(function.span.offset());
128+
}
129+
collect_function_spans(function, spans);
130+
for arg in arguments {
131+
collect_function_spans(arg, spans);
132+
}
133+
}
134+
Expression::FunctionDeclaration { body, .. } => {
135+
collect_function_spans(body, spans);
136+
}
137+
Expression::VariableDeclaration { value, .. }
138+
| Expression::Assignment { r_value: value, .. }
139+
| Expression::OpAssignment { r_value: value, .. }
140+
| Expression::Return { value } => {
141+
collect_function_spans(value, spans);
142+
}
143+
Expression::Statement(inner) | Expression::Grouping(inner) => {
144+
collect_function_spans(inner, spans);
145+
}
146+
Expression::Block { statements } => {
147+
for s in statements {
148+
collect_function_spans(s, spans);
149+
}
150+
}
151+
Expression::If {
152+
condition,
153+
on_true,
154+
on_false,
155+
} => {
156+
collect_function_spans(condition, spans);
157+
collect_function_spans(on_true, spans);
158+
if let Some(f) = on_false {
159+
collect_function_spans(f, spans);
160+
}
161+
}
162+
Expression::While {
163+
expression,
164+
loop_body,
165+
} => {
166+
collect_function_spans(expression, spans);
167+
collect_function_spans(loop_body, spans);
168+
}
169+
Expression::For { iterations, body } => {
170+
for iteration in iterations {
171+
match iteration {
172+
ForIteration::Iteration { sequence, .. } => {
173+
collect_function_spans(sequence, spans);
174+
}
175+
ForIteration::Guard(expr) => collect_function_spans(expr, spans),
176+
}
177+
}
178+
match body.as_ref() {
179+
ForBody::Block(e) | ForBody::List { expr: e, .. } => {
180+
collect_function_spans(e, spans);
181+
}
182+
ForBody::Map {
183+
key,
184+
value,
185+
default,
186+
..
187+
} => {
188+
collect_function_spans(key, spans);
189+
if let Some(v) = value {
190+
collect_function_spans(v, spans);
191+
}
192+
if let Some(d) = default {
193+
collect_function_spans(d, spans);
194+
}
195+
}
196+
}
197+
}
198+
Expression::Logical { left, right, .. } => {
199+
collect_function_spans(left, spans);
200+
collect_function_spans(right, spans);
201+
}
202+
Expression::Tuple { values } | Expression::List { values } => {
203+
for v in values {
204+
collect_function_spans(v, spans);
205+
}
206+
}
207+
Expression::Map { values, default } => {
208+
for (k, v) in values {
209+
collect_function_spans(k, spans);
210+
if let Some(v) = v {
211+
collect_function_spans(v, spans);
212+
}
213+
}
214+
if let Some(d) = default {
215+
collect_function_spans(d, spans);
216+
}
217+
}
218+
// Literals, identifiers (non-call), etc. — nothing to collect
219+
_ => {}
220+
}
221+
}

‎ndc_bin/src/main.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ fn main() -> anyhow::Result<()> {
143143
Action::HighlightFile(path) => {
144144
let string = fs::read_to_string(path)?;
145145

146-
let out = AndycppHighlighter::highlight_line(&string);
146+
let out = AndycppHighlighter::highlight_parsed(&string);
147147
for styled in out {
148148
print!("{}", styled);
149149
}

‎ndc_bin/src/repl.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct RustylineHelper {}
1515

1616
impl rustyline::highlight::Highlighter for RustylineHelper {
1717
fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
18-
let out = AndycppHighlighter::highlight_line(line);
18+
let out = AndycppHighlighter::highlight_parsed(line);
1919

2020
Cow::Owned(out.into_iter().join(""))
2121
}

0 commit comments

Comments
 (0)