From 65d149ab40a30f39a078a5699accdf505aaf7c06 Mon Sep 17 00:00:00 2001 From: "Ignat S." Date: Mon, 5 Jan 2026 14:11:17 +0300 Subject: [PATCH 1/4] Add Odin support (#182) --- Cargo.lock | 11 +++++++ Cargo.toml | 1 + crates/codebook/Cargo.toml | 1 + crates/codebook/src/queries.rs | 10 ++++++- crates/codebook/src/queries/odin.scm | 44 ++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 crates/codebook/src/queries/odin.scm diff --git a/Cargo.lock b/Cargo.lock index b55c00e..8bedfb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -401,6 +401,7 @@ dependencies = [ "tree-sitter-java", "tree-sitter-javascript", "tree-sitter-lua", + "tree-sitter-odin-codebook", "tree-sitter-php", "tree-sitter-python", "tree-sitter-r", @@ -2941,6 +2942,16 @@ dependencies = [ "tree-sitter-language", ] +[[package]] +name = "tree-sitter-odin-codebook" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2059a437d3a5a9974518bda883f5442ca52cc248c0db98e049dc3b3c7f4dfddf" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "tree-sitter-php" version = "0.24.2" diff --git a/Cargo.toml b/Cargo.toml index 78f5739..79c4ff0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ tree-sitter-html = "<0.25.0" tree-sitter-java = "<0.25.0" tree-sitter-javascript = "<0.26.0" tree-sitter-lua = "<0.25.0" +tree-sitter-odin-codebook = "1.4.0" tree-sitter-php = "<0.25.0" tree-sitter-python = "<0.26.0" tree-sitter-r = "1.1.0" diff --git a/crates/codebook/Cargo.toml b/crates/codebook/Cargo.toml index 67eb449..c0071c3 100644 --- a/crates/codebook/Cargo.toml +++ b/crates/codebook/Cargo.toml @@ -41,6 +41,7 @@ tree-sitter-java.workspace = true tree-sitter-javascript.workspace = true codebook-tree-sitter-latex.workspace = true tree-sitter-lua.workspace = true +tree-sitter-odin-codebook.workspace = true tree-sitter-php.workspace = true tree-sitter-python.workspace = true tree-sitter-r.workspace = true diff --git a/crates/codebook/src/queries.rs b/crates/codebook/src/queries.rs index 54893ca..ada21f8 100644 --- a/crates/codebook/src/queries.rs +++ b/crates/codebook/src/queries.rs @@ -17,6 +17,7 @@ pub enum LanguageType { Javascript, Latex, Lua, + Odin, Php, Python, R, @@ -187,7 +188,13 @@ pub static LANGUAGE_SETTINGS: &[LanguageSetting] = &[ query: include_str!("queries/bash.scm"), extensions: &["sh", "bash"], }, - // Added PHP + LanguageSetting { + type_: LanguageType::Odin, + ids: &["odin"], + dictionary_ids: &["odin"], + query: include_str!("queries/odin.scm"), + extensions: &["odin"], + }, LanguageSetting { type_: LanguageType::Php, ids: &["php"], @@ -251,6 +258,7 @@ impl LanguageSetting { LanguageType::Javascript => Some(tree_sitter_javascript::LANGUAGE.into()), LanguageType::Latex => Some(codebook_tree_sitter_latex::LANGUAGE.into()), LanguageType::Lua => Some(tree_sitter_lua::LANGUAGE.into()), + LanguageType::Odin => Some(tree_sitter_odin_codebook::LANGUAGE.into()), LanguageType::Php => Some(tree_sitter_php::LANGUAGE_PHP.into()), LanguageType::Python => Some(tree_sitter_python::LANGUAGE.into()), LanguageType::R => Some(tree_sitter_r::LANGUAGE.into()), diff --git a/crates/codebook/src/queries/odin.scm b/crates/codebook/src/queries/odin.scm new file mode 100644 index 0000000..894c679 --- /dev/null +++ b/crates/codebook/src/queries/odin.scm @@ -0,0 +1,44 @@ +; Comments +(comment) @comment +(block_comment) @comment + +; Procedure declarations (including parameter names) +(procedure_declaration + (expression) @identifier) +(overloaded_procedure_declaration + (expression) @identifier) +(parameter + (identifier) @identifier) +(default_parameter + (identifier) @identifier) + +; Variables and constants identifiers (declaration-only) +(var_declaration + (expression) @identifier ":") +(assignment_statement + (expression) @identifier ":=") +(const_declaration + (expression)+ @identifier) +(const_type_declaration + (expression)+ @identifier) + +; Struct, enum, union, bit_fields names +(struct_declaration + (expression) @identifier) +(enum_declaration + (expression) @identifier) +(union_declaration + (expression) @identifier) +(bit_field_declaration + (expression) @identifier "::") + +; Fields and enum variant names +(field + (identifier) @identifier) +(bit_field_member + name: (identifier) @identifier) +(enum_member + name: (identifier) @identifier) + +; Strings +(string_content) @string From c23b9c80e0541201c9c30185e60ba22d095e09ae Mon Sep 17 00:00:00 2001 From: "Ignat S." Date: Mon, 5 Jan 2026 14:12:30 +0300 Subject: [PATCH 2/4] Add Odin examples --- crates/codebook/tests/examples/example.odin | 98 +++++++++++++++++++++ examples/example.odin | 98 +++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 crates/codebook/tests/examples/example.odin create mode 100644 examples/example.odin diff --git a/crates/codebook/tests/examples/example.odin b/crates/codebook/tests/examples/example.odin new file mode 100644 index 0000000..aa5ea40 --- /dev/null +++ b/crates/codebook/tests/examples/example.odin @@ -0,0 +1,98 @@ +package main + +import "core:fmt" + +// Test program for the Codebook spell-checking tool. +// The goal is to spellcheck comments, strings, +// identifiers during declarations, but not during usage + +// Commennt +/* + Block cooment + /* + Netsed block + */ +*/ + +my_proecdure :: proc(my_prameter: int, another_paramter: f64) { + fmt.println(cast(f64)my_prameter + another_paramter) +} + +another_porocedure :: proc(my_prameter: f64, another_paramter: int) { + fmt.println(my_prameter + cast(f64)another_paramter) +} + +overloded_procedure:: proc{my_proecdure, another_porocedure} + +with_deafult :: proc(my_prameter:= 42) { + fmt.println(my_prameter) +} + +with_varidic :: proc(numberes: ..int) { + fmt.println(numberes) +} + +MY_CONSATANT : int : 123 +ANOTHER_COONSTANT :: 456 + +main :: proc() { + declaring_without_assignement: int + declaring_anotther, and_annother: int + with_assignement := 42 + assignement_with_explicit_type : int = 33 + and_another_one, and_more := "Helloep", "Wordl" + fmt.println( + MY_CONSATANT, + ANOTHER_COONSTANT, + declaring_without_assignement, + declaring_anotther, + with_assignement, + assignement_with_explicit_type, + and_another_one, + and_more, + ) + + MyAwseomeStruct :: struct { + my_field: f32, + another_field: f32, + } + foo := MyAwseomeStruct{1, 2} + fmt.println(foo.my_field, foo.another_field) + + CompacotStruct :: struct { + aples, banananas, ornages: int + } + bar := CompacotStruct{3, 4, 5} + fmt.println(bar.aples, bar.banananas, bar.ornages) + + TWOOF :: 2 + MyCratfyEnum :: enum { + Aapple, + Baanana = 2, + Oranege = TWOOF, + } + buzz := MyCratfyEnum.Baanana + fmt.println(buzz) + + MyUnberakableUnion :: union {int, bool} + + MyFruttyInstruction :: bit_field u64 { + verison: u8 | 3, + ttl: u8 | 8, + fruit: MyCratfyEnum | TWOOF, + opration: u8 | 3, + left_opernd: u16 | 16, + right_oprand: u16 | 16, + destination: u16 | 16, + } + i := MyFruttyInstruction{} + i.fruit = .Baanana + fmt.println(i.left_opernd, i.right_oprand) + + fmt.println("Helolo, Wlorld!") + + overloded_procedure(33, 3.3) + overloded_procedure(4, 44.4) + with_deafult(42) + with_varidic(1, 2, 3) +} diff --git a/examples/example.odin b/examples/example.odin new file mode 100644 index 0000000..aa5ea40 --- /dev/null +++ b/examples/example.odin @@ -0,0 +1,98 @@ +package main + +import "core:fmt" + +// Test program for the Codebook spell-checking tool. +// The goal is to spellcheck comments, strings, +// identifiers during declarations, but not during usage + +// Commennt +/* + Block cooment + /* + Netsed block + */ +*/ + +my_proecdure :: proc(my_prameter: int, another_paramter: f64) { + fmt.println(cast(f64)my_prameter + another_paramter) +} + +another_porocedure :: proc(my_prameter: f64, another_paramter: int) { + fmt.println(my_prameter + cast(f64)another_paramter) +} + +overloded_procedure:: proc{my_proecdure, another_porocedure} + +with_deafult :: proc(my_prameter:= 42) { + fmt.println(my_prameter) +} + +with_varidic :: proc(numberes: ..int) { + fmt.println(numberes) +} + +MY_CONSATANT : int : 123 +ANOTHER_COONSTANT :: 456 + +main :: proc() { + declaring_without_assignement: int + declaring_anotther, and_annother: int + with_assignement := 42 + assignement_with_explicit_type : int = 33 + and_another_one, and_more := "Helloep", "Wordl" + fmt.println( + MY_CONSATANT, + ANOTHER_COONSTANT, + declaring_without_assignement, + declaring_anotther, + with_assignement, + assignement_with_explicit_type, + and_another_one, + and_more, + ) + + MyAwseomeStruct :: struct { + my_field: f32, + another_field: f32, + } + foo := MyAwseomeStruct{1, 2} + fmt.println(foo.my_field, foo.another_field) + + CompacotStruct :: struct { + aples, banananas, ornages: int + } + bar := CompacotStruct{3, 4, 5} + fmt.println(bar.aples, bar.banananas, bar.ornages) + + TWOOF :: 2 + MyCratfyEnum :: enum { + Aapple, + Baanana = 2, + Oranege = TWOOF, + } + buzz := MyCratfyEnum.Baanana + fmt.println(buzz) + + MyUnberakableUnion :: union {int, bool} + + MyFruttyInstruction :: bit_field u64 { + verison: u8 | 3, + ttl: u8 | 8, + fruit: MyCratfyEnum | TWOOF, + opration: u8 | 3, + left_opernd: u16 | 16, + right_oprand: u16 | 16, + destination: u16 | 16, + } + i := MyFruttyInstruction{} + i.fruit = .Baanana + fmt.println(i.left_opernd, i.right_oprand) + + fmt.println("Helolo, Wlorld!") + + overloded_procedure(33, 3.3) + overloded_procedure(4, 44.4) + with_deafult(42) + with_varidic(1, 2, 3) +} From a620e3ff324ffc1d29c4ab043765455eb32d3c26 Mon Sep 17 00:00:00 2001 From: "Ignat S." Date: Mon, 5 Jan 2026 14:13:00 +0300 Subject: [PATCH 3/4] Add tests for Odin --- crates/codebook/tests/test_odin.rs | 201 +++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 crates/codebook/tests/test_odin.rs diff --git a/crates/codebook/tests/test_odin.rs b/crates/codebook/tests/test_odin.rs new file mode 100644 index 0000000..9680a1e --- /dev/null +++ b/crates/codebook/tests/test_odin.rs @@ -0,0 +1,201 @@ +use codebook::{ + parser::{TextRange, WordLocation}, + queries::LanguageType, +}; + +mod utils; + +#[test] +fn test_odin_location() { + utils::init_logging(); + let sample_text = include_str!("./examples/example.odin"); + let expected = vec![ + // Comments + WordLocation::new( + "Commennt".to_string(), + vec![TextRange { start_byte: 196, end_byte: 204 }], + ), + WordLocation::new( + "cooment".to_string(), + vec![TextRange { start_byte: 215, end_byte: 222 }], + ), + WordLocation::new( + "Netsed".to_string(), + vec![TextRange { start_byte: 229, end_byte: 235 }], + ), + // Procedure declarations + WordLocation::new( + "proecdure".to_string(), + vec![TextRange { start_byte: 253, end_byte: 262 }], + ), + WordLocation::new( + "porocedure".to_string(), + vec![TextRange { start_byte: 379, end_byte: 389 }], + ), + WordLocation::new( + "overloded".to_string(), + vec![TextRange { start_byte: 498, end_byte: 507 }], + ), + WordLocation::new( + "deafult".to_string(), + vec![TextRange { start_byte: 565, end_byte: 572 }], + ), + WordLocation::new( + "varidic".to_string(), + vec![TextRange { start_byte: 635, end_byte: 642 }], + ), + // Parameters + WordLocation::new( + "prameter".to_string(), + vec![ + TextRange { start_byte: 274, end_byte: 282 }, + TextRange { start_byte: 401, end_byte: 409 }, + TextRange { start_byte: 584, end_byte: 592 }, + ], + ), + WordLocation::new( + "paramter".to_string(), + vec![ + TextRange { start_byte: 297, end_byte: 305 }, + TextRange { start_byte: 424, end_byte: 432 }, + ], + ), + WordLocation::new( + "numberes".to_string(), + vec![TextRange { start_byte: 651, end_byte: 659 }], + ), + // Constants + WordLocation::new( + "CONSATANT".to_string(), + vec![TextRange { start_byte: 699, end_byte: 708 }], + ), + WordLocation::new( + "COONSTANT".to_string(), + vec![TextRange { start_byte: 729, end_byte: 738 }], + ), + WordLocation::new( + "TWOOF".to_string(), + vec![TextRange { start_byte: 1448, end_byte: 1453 }], + ), + // Variable declarations + WordLocation::new( + "assignement".to_string(), + vec![ + TextRange { start_byte: 783, end_byte: 794 }, + TextRange { start_byte: 845, end_byte: 856 }, + TextRange { start_byte: 864, end_byte: 875 }, + ], + ), + WordLocation::new( + "anotther".to_string(), + vec![TextRange { start_byte: 811, end_byte: 819 }], + ), + WordLocation::new( + "annother".to_string(), + vec![TextRange { start_byte: 825, end_byte: 833 }], + ), + // Strings + WordLocation::new( + "Helloep".to_string(), + vec![TextRange { start_byte: 937, end_byte: 944 }], + ), + WordLocation::new( + "Wordl".to_string(), + vec![TextRange { start_byte: 948, end_byte: 953 }], + ), + WordLocation::new( + "Helolo".to_string(), + vec![TextRange { start_byte: 1969, end_byte: 1975 }], + ), + WordLocation::new( + "Wlorld".to_string(), + vec![TextRange { start_byte: 1977, end_byte: 1983 }], + ), + // Struct declarations and fields + WordLocation::new( + "Awseome".to_string(), + vec![TextRange { start_byte: 1153, end_byte: 1160 }], + ), + WordLocation::new( + "Compacot".to_string(), + vec![TextRange { start_byte: 1299, end_byte: 1307 }], + ), + WordLocation::new( + "aples".to_string(), + vec![TextRange { start_byte: 1328, end_byte: 1333 }], + ), + WordLocation::new( + "banananas".to_string(), + vec![TextRange { start_byte: 1335, end_byte: 1344 }], + ), + WordLocation::new( + "ornages".to_string(), + vec![TextRange { start_byte: 1346, end_byte: 1353 }], + ), + // Enum declaration and members + WordLocation::new( + "Cratfy".to_string(), + vec![TextRange { start_byte: 1462, end_byte: 1468 }], + ), + WordLocation::new( + "Aapple".to_string(), + vec![TextRange { start_byte: 1485, end_byte: 1491 }], + ), + WordLocation::new( + "Baanana".to_string(), + vec![TextRange { start_byte: 1495, end_byte: 1502 }], + ), + WordLocation::new( + "Oranege".to_string(), + vec![TextRange { start_byte: 1510, end_byte: 1517 }], + ), + // Union declaration + WordLocation::new( + "Unberakable".to_string(), + vec![TextRange { start_byte: 1583, end_byte: 1594 }], + ), + // Bit field declaration and members + WordLocation::new( + "Frutty".to_string(), + vec![TextRange { start_byte: 1625, end_byte: 1631 }], + ), + WordLocation::new( + "verison".to_string(), + vec![TextRange { start_byte: 1664, end_byte: 1671 }], + ), + WordLocation::new( + "ttl".to_string(), + vec![TextRange { start_byte: 1691, end_byte: 1694 }], + ), + WordLocation::new( + "opration".to_string(), + vec![TextRange { start_byte: 1750, end_byte: 1758 }], + ), + WordLocation::new( + "opernd".to_string(), + vec![TextRange { start_byte: 1782, end_byte: 1788 }], + ), + WordLocation::new( + "oprand".to_string(), + vec![TextRange { start_byte: 1811, end_byte: 1817 }], + ), + ]; + let not_expected = ["fmt", "println"]; + let processor = utils::get_processor(); + let misspelled = processor + .spell_check(sample_text, Some(LanguageType::Odin), None) + .to_vec(); + println!("Misspelled words: {misspelled:?}"); + for e in &expected { + println!("Expecting: {e:?}"); + let miss = misspelled.iter().find(|r| r.word == e.word).unwrap(); + // assert_eq!(miss.locations, e.locations); + assert!(miss.locations.len() == e.locations.len()); + for location in &miss.locations { + assert!(e.locations.contains(location)); + } + } + for result in misspelled { + assert!(!not_expected.contains(&result.word.as_str())); + } +} From 306cdd00ffe4281eb67f910750b78018736b5e62 Mon Sep 17 00:00:00 2001 From: Bo Lopker Date: Wed, 31 Dec 2025 15:23:30 -0800 Subject: [PATCH 4/4] Add Odin to readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d22e01c..48fa0bb 100644 --- a/README.md +++ b/README.md @@ -135,17 +135,19 @@ Codebook is in active development. As better dictionaries are added, words that | Language | Status | | --- | --- | | C | ✅ | +| C# | ⚠️ | | C++ | ⚠️ | | CSS | ⚠️ | | Elixir | ⚠️ | | Go | ✅ | -| HTML | ⚠️ | | Haskell | ⚠️ | +| HTML | ⚠️ | | Java | ✅ | | JavaScript | ✅ | | LaTeX | ⚠️ | | Lua | ✅ | | Markdown | ✅ | +| Odin | ✅ | | PHP | ⚠️ | | Plain Text | ✅ | | Python | ✅ | @@ -156,7 +158,6 @@ Codebook is in active development. As better dictionaries are added, words that | TypeScript | ✅ | | Typst | ⚠️ | | Zig | ✅ | -| C# | ⚠️ | ✅ = Good to go. ⚠️ = Supported, but needs more testing. Help us improve!