feat: give column data types a dedicated syntax-highlight colour

Both Node::Ident and Word carried a highlight_override field, and
both were dead — the walker driver discarded the Ident's and
walk_word hardcoded Keyword. So column types (int, serial, …)
rendered identically to table/column names.

Wire both overrides through, and add a dedicated HighlightClass::Type
with its own theme colour (tok_type), distinct from keyword-purple
and identifier-teal. The three type Ident slots opt in, so canonical
types and the advanced-mode single-word SQL aliases (float, varchar,
…) render as types; the two-word `double precision` alias opts in via
a new Word::type_keyword constructor. ADR-0022 Amendment 4.
This commit is contained in:
claude@clouddev1
2026-05-29 22:07:18 +00:00
parent 46a31284c5
commit d20f765325
10 changed files with 195 additions and 13 deletions
+70
View File
@@ -297,6 +297,76 @@ mod tests {
assert_eq!(runs[1].2, HighlightClass::Keyword);
}
#[test]
fn dsl_type_keyword_classified_as_type() {
// Issue #8 / ADR-0022 Amendment 4: the column type in a DSL
// `create table` (`serial`) is a Type, distinct from the
// identifiers `T` / `id` which stay Identifier.
let runs = run("create table T with pk id(serial)");
// `serial` occupies bytes 26..32 in the input.
let serial = runs
.iter()
.find(|(s, e, _)| &"create table T with pk id(serial)"[*s..*e] == "serial")
.expect("serial run present");
assert_eq!(serial.2, HighlightClass::Type);
// The invented identifiers are NOT typed.
for name in ["T", "id"] {
let r = runs
.iter()
.find(|(s, e, _)| &"create table T with pk id(serial)"[*s..*e] == name)
.unwrap_or_else(|| panic!("{name} run present"));
assert_eq!(r.2, HighlightClass::Identifier, "{name} stays Identifier");
}
}
#[test]
fn advanced_type_keywords_classified_as_type() {
// Issue #8: the exact example from the ticket. In advanced
// mode the SQL type keywords `int` / `serial` are Type;
// `Orders` / `count` / `id` stay Identifier.
let input = "create table Orders (count int, id serial)";
let runs: Vec<(usize, usize, HighlightClass)> =
highlight_runs_in_mode(input, crate::mode::Mode::Advanced)
.into_iter()
.map(|c| (c.start, c.end, c.class))
.collect();
let class_of = |needle: &str| {
runs.iter()
.find(|(s, e, _)| &input[*s..*e] == needle)
.unwrap_or_else(|| panic!("{needle} run present"))
.2
};
assert_eq!(class_of("int"), HighlightClass::Type);
assert_eq!(class_of("serial"), HighlightClass::Type);
assert_eq!(class_of("Orders"), HighlightClass::Identifier);
assert_eq!(class_of("count"), HighlightClass::Identifier);
assert_eq!(class_of("id"), HighlightClass::Identifier);
}
#[test]
fn advanced_double_precision_classified_as_type() {
// Issue #8 follow-up: the two-word `double precision` SQL
// alias (ADR-0035 §3) is matched as keyword tokens, but
// `Word::type_keyword` colours it as a type so it matches
// its single-word synonyms `float` / `real`. Both words
// carry the Type class.
let input = "create table T (x double precision)";
let runs: Vec<(usize, usize, HighlightClass)> =
highlight_runs_in_mode(input, crate::mode::Mode::Advanced)
.into_iter()
.map(|c| (c.start, c.end, c.class))
.collect();
let class_of = |needle: &str| {
runs.iter()
.find(|(s, e, _)| &input[*s..*e] == needle)
.unwrap_or_else(|| panic!("{needle} run present"))
.2
};
assert_eq!(class_of("double"), HighlightClass::Type);
assert_eq!(class_of("precision"), HighlightClass::Type);
assert_eq!(class_of("x"), HighlightClass::Identifier);
}
#[test]
fn full_command_walks_with_each_class() {
// `update T set Name='hi' --all-rows` — walker covers it