diff --git a/Cargo.lock b/Cargo.lock index 88b2ac1..5d08e32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1397,6 +1397,7 @@ dependencies = [ "tree-sitter-go", "tree-sitter-java", "tree-sitter-javascript", + "tree-sitter-kotlin", "tree-sitter-python", "tree-sitter-rust", "tree-sitter-typescript", @@ -3691,6 +3692,16 @@ dependencies = [ "tree-sitter", ] +[[package]] +name = "tree-sitter-kotlin" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ff60aeb036f5762515ceb31404512ea4f9599764bcd3857074bb82867bdd34" +dependencies = [ + "cc", + "tree-sitter", +] + [[package]] name = "tree-sitter-python" version = "0.21.0" diff --git a/crates/g3-core/Cargo.toml b/crates/g3-core/Cargo.toml index 8d02b63..37e4274 100644 --- a/crates/g3-core/Cargo.toml +++ b/crates/g3-core/Cargo.toml @@ -37,4 +37,5 @@ tree-sitter-go = "0.21" tree-sitter-java = "0.21" tree-sitter-c = "0.21" tree-sitter-cpp = "0.21" +tree-sitter-kotlin = "0.3" walkdir = "2.4" diff --git a/crates/g3-core/examples/test_code/Example.kt b/crates/g3-core/examples/test_code/Example.kt new file mode 100644 index 0000000..eaae013 --- /dev/null +++ b/crates/g3-core/examples/test_code/Example.kt @@ -0,0 +1,24 @@ +package com.example + +class Person(val name: String, val age: Int) { + fun greet() { + println("Hello, I'm $name") + } + + fun getAge(): Int { + return age + } +} + +interface Greeter { + fun sayHello() +} + +fun main() { + val person = Person("Alice", 30) + person.greet() +} + +fun add(a: Int, b: Int): Int { + return a + b +} diff --git a/crates/g3-core/examples/test_code/example.rkt b/crates/g3-core/examples/test_code/example.rkt new file mode 100644 index 0000000..64fe470 --- /dev/null +++ b/crates/g3-core/examples/test_code/example.rkt @@ -0,0 +1,24 @@ +#lang racket + +(define (greet name) + (printf "Hello, ~a!\n" name)) + +(define (add x y) + (+ x y)) + +(define (factorial n) + (if (<= n 1) + 1 + (* n (factorial (- n 1))))) + +(struct person (name age) #:transparent) + +(define (person-greet p) + (printf "Hello, I'm ~a\n" (person-name p))) + +(greet "World") +(displayln (add 5 3)) +(displayln (factorial 5)) + +(define alice (person "Alice" 30)) +(person-greet alice) diff --git a/crates/g3-core/src/code_search/searcher.rs b/crates/g3-core/src/code_search/searcher.rs index 9727e26..12bdfde 100644 --- a/crates/g3-core/src/code_search/searcher.rs +++ b/crates/g3-core/src/code_search/searcher.rs @@ -118,6 +118,17 @@ impl TreeSitterSearcher { languages.insert("cpp".to_string(), language); } + // Initialize Kotlin + { + let mut parser = Parser::new(); + let language: Language = tree_sitter_kotlin::language().into(); + parser + .set_language(&language) + .map_err(|e| anyhow!("Failed to set Kotlin language: {}", e))?; + parsers.insert("kotlin".to_string(), parser); + languages.insert("kotlin".to_string(), language); + } + if parsers.is_empty() { return Err(anyhow!( "No language parsers available. Enable at least one language feature." @@ -299,6 +310,7 @@ impl TreeSitterSearcher { ("java", Some("java")) => true, ("c", Some("c" | "h")) => true, ("cpp", Some("cpp" | "cc" | "cxx" | "hpp" | "hxx" | "h")) => true, + ("kotlin", Some("kt" | "kts")) => true, _ => false, } } diff --git a/crates/g3-core/src/lib.rs b/crates/g3-core/src/lib.rs index 0771bd1..67babe0 100644 --- a/crates/g3-core/src/lib.rs +++ b/crates/g3-core/src/lib.rs @@ -1908,7 +1908,7 @@ Template: // Add code_search tool tools.push(Tool { name: "code_search".to_string(), - description: "Batch syntax-aware code searches using embedded tree-sitter. Supports up to 20 searches in parallel for Rust, Python, JavaScript, TypeScript, Go, Java, C, and C++. Uses tree-sitter query syntax (S-expressions).".to_string(), + description: "Batch syntax-aware code searches using embedded tree-sitter. Supports up to 20 searches in parallel for Rust, Python, JavaScript, TypeScript, Go, Java, C, C++, and Kotlin. Uses tree-sitter query syntax (S-expressions).".to_string(), input_schema: json!({ "type": "object", "properties": { @@ -1920,7 +1920,7 @@ Template: "properties": { "name": { "type": "string", "description": "Label for this search." }, "query": { "type": "string", "description": "tree-sitter query in S-expression format (e.g., \"(function_item name: (identifier) @name)\")"}, - "language": { "type": "string", "enum": ["rust", "python", "javascript", "typescript", "go", "java", "c", "cpp"], "description": "Programming language to search." }, + "language": { "type": "string", "enum": ["rust", "python", "javascript", "typescript", "go", "java", "c", "cpp", "kotlin"], "description": "Programming language to search." }, "paths": { "type": "array", "items": { "type": "string" }, "description": "Paths/dirs to search. Defaults to current dir if empty." }, "context_lines": { "type": "integer", "minimum": 0, "maximum": 20, "default": 0, "description": "Lines of context to include around each match." } }, diff --git a/crates/g3-core/tests/code_search_test.rs b/crates/g3-core/tests/code_search_test.rs index 6f25cfd..612e9fc 100644 --- a/crates/g3-core/tests/code_search_test.rs +++ b/crates/g3-core/tests/code_search_test.rs @@ -549,3 +549,28 @@ async fn test_cpp_search() { .collect(); assert!(names.contains(&"Person")); } + +#[tokio::test] +async fn test_kotlin_search() { + let request = CodeSearchRequest { + searches: vec![SearchSpec { + name: "kotlin_classes".to_string(), + query: "(class_declaration (type_identifier) @name)".to_string(), + language: "kotlin".to_string(), + paths: vec!["examples/test_code".to_string()], + context_lines: 0, + }], + max_concurrency: 4, + max_matches_per_search: 500, + }; + + let response = execute_code_search(request).await.unwrap(); + assert_eq!(response.searches.len(), 1); + assert!(response.searches[0].matches.len() > 0); + + // Should find Person class + let names: Vec<&str> = response.searches[0].matches.iter() + .filter_map(|m| m.captures.get("name").map(|s| s.as_str())) + .collect(); + assert!(names.contains(&"Person")); +}