feat(repo_map): add elixir support (#894)
This commit is contained in:
		
							parent
							
								
									e60ccd2db4
								
							
						
					
					
						commit
						890fd92594
					
				
							
								
								
									
										11
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -40,6 +40,7 @@ dependencies = [ | |||||||
|  "tree-sitter", |  "tree-sitter", | ||||||
|  "tree-sitter-c", |  "tree-sitter-c", | ||||||
|  "tree-sitter-cpp", |  "tree-sitter-cpp", | ||||||
|  |  "tree-sitter-elixir", | ||||||
|  "tree-sitter-go", |  "tree-sitter-go", | ||||||
|  "tree-sitter-javascript", |  "tree-sitter-javascript", | ||||||
|  "tree-sitter-language", |  "tree-sitter-language", | ||||||
| @ -1340,6 +1341,16 @@ dependencies = [ | |||||||
|  "tree-sitter-language", |  "tree-sitter-language", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "tree-sitter-elixir" | ||||||
|  | version = "0.3.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "97bf0efa4be41120018f23305b105ad4dfd3be1b7f302dc4071d0e6c2dec3a32" | ||||||
|  | dependencies = [ | ||||||
|  |  "cc", | ||||||
|  |  "tree-sitter-language", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tree-sitter-go" | name = "tree-sitter-go" | ||||||
| version = "0.23.1" | version = "0.23.1" | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ tree-sitter-lua = "0.2" | |||||||
| tree-sitter-ruby = "0.23" | tree-sitter-ruby = "0.23" | ||||||
| tree-sitter-zig = "1.0.2" | tree-sitter-zig = "1.0.2" | ||||||
| tree-sitter-scala = "0.23" | tree-sitter-scala = "0.23" | ||||||
|  | tree-sitter-elixir = "0.3.1" | ||||||
| 
 | 
 | ||||||
| [lints] | [lints] | ||||||
| workspace = true | workspace = true | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								crates/avante-repo-map/queries/tree-sitter-elixir-defs.scm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								crates/avante-repo-map/queries/tree-sitter-elixir-defs.scm
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | ; * modules and protocols | ||||||
|  | (call | ||||||
|  |   target: (identifier) @ignore | ||||||
|  |   (arguments (alias) @class) | ||||||
|  |   (#match? @ignore "^(defmodule|defprotocol)$")) | ||||||
|  | 
 | ||||||
|  | ; * functions | ||||||
|  | (call | ||||||
|  |   target: (identifier) @ignore | ||||||
|  |   (arguments | ||||||
|  |     [ | ||||||
|  |       ; zero-arity functions with no parentheses | ||||||
|  |       (identifier) @method | ||||||
|  |       ; regular function clause | ||||||
|  |       (call target: (identifier) @method) | ||||||
|  |       ; function clause with a guard clause | ||||||
|  |       (binary_operator | ||||||
|  |         left: (call target: (identifier) @method) | ||||||
|  |         operator: "when") | ||||||
|  |     ]) | ||||||
|  |   (#match? @ignore "^(def|defdelegate|defguard|defn)$")) | ||||||
| @ -14,6 +14,7 @@ pub struct Func { | |||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub struct Class { | pub struct Class { | ||||||
|  |     pub type_name: String, | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub methods: Vec<Func>, |     pub methods: Vec<Func>, | ||||||
|     pub properties: Vec<Variable>, |     pub properties: Vec<Variable>, | ||||||
| @ -61,6 +62,7 @@ fn get_ts_language(language: &str) -> Option<LanguageFn> { | |||||||
|         "ruby" => Some(tree_sitter_ruby::LANGUAGE), |         "ruby" => Some(tree_sitter_ruby::LANGUAGE), | ||||||
|         "zig" => Some(tree_sitter_zig::LANGUAGE), |         "zig" => Some(tree_sitter_zig::LANGUAGE), | ||||||
|         "scala" => Some(tree_sitter_scala::LANGUAGE), |         "scala" => Some(tree_sitter_scala::LANGUAGE), | ||||||
|  |         "elixir" => Some(tree_sitter_elixir::LANGUAGE), | ||||||
|         _ => None, |         _ => None, | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -76,6 +78,7 @@ const ZIG_QUERY: &str = include_str!("../queries/tree-sitter-zig-defs.scm"); | |||||||
| const TYPESCRIPT_QUERY: &str = include_str!("../queries/tree-sitter-typescript-defs.scm"); | const TYPESCRIPT_QUERY: &str = include_str!("../queries/tree-sitter-typescript-defs.scm"); | ||||||
| const RUBY_QUERY: &str = include_str!("../queries/tree-sitter-ruby-defs.scm"); | const RUBY_QUERY: &str = include_str!("../queries/tree-sitter-ruby-defs.scm"); | ||||||
| const SCALA_QUERY: &str = include_str!("../queries/tree-sitter-scala-defs.scm"); | const SCALA_QUERY: &str = include_str!("../queries/tree-sitter-scala-defs.scm"); | ||||||
|  | const ELIXIR_QUERY: &str = include_str!("../queries/tree-sitter-elixir-defs.scm"); | ||||||
| 
 | 
 | ||||||
| fn get_definitions_query(language: &str) -> Result<Query, String> { | fn get_definitions_query(language: &str) -> Result<Query, String> { | ||||||
|     let ts_language = get_ts_language(language); |     let ts_language = get_ts_language(language); | ||||||
| @ -95,6 +98,7 @@ fn get_definitions_query(language: &str) -> Result<Query, String> { | |||||||
|         "typescript" => TYPESCRIPT_QUERY, |         "typescript" => TYPESCRIPT_QUERY, | ||||||
|         "ruby" => RUBY_QUERY, |         "ruby" => RUBY_QUERY, | ||||||
|         "scala" => SCALA_QUERY, |         "scala" => SCALA_QUERY, | ||||||
|  |         "elixir" => ELIXIR_QUERY, | ||||||
|         _ => return Err(format!("Unsupported language: {language}")), |         _ => return Err(format!("Unsupported language: {language}")), | ||||||
|     }; |     }; | ||||||
|     let query = Query::new(&ts_language.into(), contents) |     let query = Query::new(&ts_language.into(), contents) | ||||||
| @ -185,6 +189,23 @@ fn zig_find_type_in_parent<'a>(node: &'a Node, source: &'a [u8]) -> Option<Strin | |||||||
|     None |     None | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | fn ex_find_parent_module_declaration_name<'a>(node: &'a Node, source: &'a [u8],) -> Option<String> { | ||||||
|  |     let mut parent = node.parent(); | ||||||
|  |     while let Some(parent_node) = parent { | ||||||
|  |         if parent_node.kind() == "call" { | ||||||
|  |             let text = get_node_text(&parent_node, source); | ||||||
|  |             if text.starts_with("defmodule ") { | ||||||
|  |                 let arguments_node = find_child_by_type(&parent_node, "arguments"); | ||||||
|  |                 if let Some(arguments_node) = arguments_node { | ||||||
|  |                     return Some(get_node_text(&arguments_node, source)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         parent = parent_node.parent(); | ||||||
|  |     } | ||||||
|  |     None | ||||||
|  | } | ||||||
|  | 
 | ||||||
| fn get_node_text<'a>(node: &'a Node, source: &'a [u8]) -> String { | fn get_node_text<'a>(node: &'a Node, source: &'a [u8]) -> String { | ||||||
|     node.utf8_text(source).unwrap_or_default().to_string() |     node.utf8_text(source).unwrap_or_default().to_string() | ||||||
| } | } | ||||||
| @ -235,9 +256,14 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|     let mut enum_def_map: BTreeMap<String, RefCell<Enum>> = BTreeMap::new(); |     let mut enum_def_map: BTreeMap<String, RefCell<Enum>> = BTreeMap::new(); | ||||||
|     let mut union_def_map: BTreeMap<String, RefCell<Union>> = BTreeMap::new(); |     let mut union_def_map: BTreeMap<String, RefCell<Union>> = BTreeMap::new(); | ||||||
| 
 | 
 | ||||||
|     let ensure_class_def = |name: &str, class_def_map: &mut BTreeMap<String, RefCell<Class>>| { |     let ensure_class_def = |language: &str, name: &str, class_def_map: &mut BTreeMap<String, RefCell<Class>>| { | ||||||
|  |         let mut type_name = "class"; | ||||||
|  |         if language == "elixir" { | ||||||
|  |             type_name = "module" | ||||||
|  |         } | ||||||
|         class_def_map.entry(name.to_string()).or_insert_with(|| { |         class_def_map.entry(name.to_string()).or_insert_with(|| { | ||||||
|             RefCell::new(Class { |             RefCell::new(Class { | ||||||
|  |                 type_name: type_name.to_string(), | ||||||
|                 name: name.to_string(), |                 name: name.to_string(), | ||||||
|                 methods: vec![], |                 methods: vec![], | ||||||
|                 properties: vec![], |                 properties: vec![], | ||||||
| @ -337,7 +363,7 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                         if language == "go" && !is_first_letter_uppercase(&name) { |                         if language == "go" && !is_first_letter_uppercase(&name) { | ||||||
|                             continue; |                             continue; | ||||||
|                         } |                         } | ||||||
|                         ensure_class_def(&name, &mut class_def_map); |                         ensure_class_def(&language, &name, &mut class_def_map); | ||||||
|                         let visibility_modifier_node = |                         let visibility_modifier_node = | ||||||
|                             find_child_by_type(&node, "visibility_modifier"); |                             find_child_by_type(&node, "visibility_modifier"); | ||||||
|                         let visibility_modifier = visibility_modifier_node |                         let visibility_modifier = visibility_modifier_node | ||||||
| @ -449,12 +475,18 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                         .child_by_field_name("parameters") |                         .child_by_field_name("parameters") | ||||||
|                         .or_else(|| find_descendant_by_type(&node, "parameter_list")); |                         .or_else(|| find_descendant_by_type(&node, "parameter_list")); | ||||||
| 
 | 
 | ||||||
|                     let function_node = find_ancestor_by_type(&node, "function_declaration"); |                     let zig_function_node = find_ancestor_by_type(&node, "function_declaration"); | ||||||
|                     if language == "zig" { |                     if language == "zig" { | ||||||
|                         params_node = function_node |                         params_node = zig_function_node | ||||||
|                             .as_ref() |                             .as_ref() | ||||||
|                             .and_then(|n| find_child_by_type(n, "parameters")); |                             .and_then(|n| find_child_by_type(n, "parameters")); | ||||||
|                     } |                     } | ||||||
|  |                     let ex_function_node = find_ancestor_by_type(&node, "call"); | ||||||
|  |                     if language == "elixir" { | ||||||
|  |                         params_node = ex_function_node | ||||||
|  |                             .as_ref() | ||||||
|  |                             .and_then(|n| find_child_by_type(n, "arguments")); | ||||||
|  |                     } | ||||||
| 
 | 
 | ||||||
|                     let params = params_node |                     let params = params_node | ||||||
|                         .map(|n| n.utf8_text(source.as_bytes()).unwrap()) |                         .map(|n| n.utf8_text(source.as_bytes()).unwrap()) | ||||||
| @ -480,6 +512,9 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                         return_type_node = node.child_by_field_name("result"); |                         return_type_node = node.child_by_field_name("result"); | ||||||
|                     } |                     } | ||||||
|                     let mut return_type = "void".to_string(); |                     let mut return_type = "void".to_string(); | ||||||
|  |                     if language == "elixir" { | ||||||
|  |                         return_type = "".to_string(); | ||||||
|  |                     } | ||||||
|                     if return_type_node.is_some() { |                     if return_type_node.is_some() { | ||||||
|                         return_type = get_node_type(&return_type_node.unwrap(), source.as_bytes()); |                         return_type = get_node_type(&return_type_node.unwrap(), source.as_bytes()); | ||||||
|                         if return_type.is_empty() { |                         if return_type.is_empty() { | ||||||
| @ -496,6 +531,9 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                     let class_name = if language == "zig" { |                     let class_name = if language == "zig" { | ||||||
|                         zig_find_parent_variable_declaration_name(&node, source.as_bytes()) |                         zig_find_parent_variable_declaration_name(&node, source.as_bytes()) | ||||||
|                             .unwrap_or_default() |                             .unwrap_or_default() | ||||||
|  |                     } else if language == "elixir" { | ||||||
|  |                         ex_find_parent_module_declaration_name(&node, source.as_bytes()) | ||||||
|  |                             .unwrap_or_default() | ||||||
|                     } else if language == "cpp" { |                     } else if language == "cpp" { | ||||||
|                         find_ancestor_by_type(&node, "class_specifier") |                         find_ancestor_by_type(&node, "class_specifier") | ||||||
|                             .or_else(|| find_ancestor_by_type(&node, "struct_specifier")) |                             .or_else(|| find_ancestor_by_type(&node, "struct_specifier")) | ||||||
| @ -524,7 +562,7 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     ensure_class_def(&class_name, &mut class_def_map); |                     ensure_class_def(&language, &class_name, &mut class_def_map); | ||||||
|                     let class_def = class_def_map.get_mut(&class_name).unwrap(); |                     let class_def = class_def_map.get_mut(&class_name).unwrap(); | ||||||
| 
 | 
 | ||||||
|                     let accessibility_modifier_node = |                     let accessibility_modifier_node = | ||||||
| @ -569,7 +607,7 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                     if class_name.is_empty() { |                     if class_name.is_empty() { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|                     ensure_class_def(&class_name, &mut class_def_map); |                     ensure_class_def(&language, &class_name, &mut class_def_map); | ||||||
|                     let class_def = class_def_map.get_mut(&class_name).unwrap(); |                     let class_def = class_def_map.get_mut(&class_name).unwrap(); | ||||||
|                     let variable = Variable { |                     let variable = Variable { | ||||||
|                         name: left.to_string(), |                         name: left.to_string(), | ||||||
| @ -623,7 +661,7 @@ fn extract_definitions(language: &str, source: &str) -> Result<Vec<Definition>, | |||||||
|                     if !name.is_empty() && language == "go" && !is_first_letter_uppercase(&name) { |                     if !name.is_empty() && language == "go" && !is_first_letter_uppercase(&name) { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|                     ensure_class_def(&class_name, &mut class_def_map); |                     ensure_class_def(&language, &class_name, &mut class_def_map); | ||||||
|                     let class_def = class_def_map.get_mut(&class_name).unwrap(); |                     let class_def = class_def_map.get_mut(&class_name).unwrap(); | ||||||
|                     let variable = Variable { |                     let variable = Variable { | ||||||
|                         name: name.to_string(), |                         name: name.to_string(), | ||||||
| @ -888,7 +926,7 @@ fn stringify_union_item(item: &Variable) -> String { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn stringify_class(class: &Class) -> String { | fn stringify_class(class: &Class) -> String { | ||||||
|     let mut res = format!("class {}{{", class.name); |     let mut res = format!("{} {}{{", class.type_name, class.name); | ||||||
|     for method in &class.methods { |     for method in &class.methods { | ||||||
|         let method_str = stringify_function(method); |         let method_str = stringify_function(method); | ||||||
|         res = format!("{res}{method_str}"); |         res = format!("{res}{method_str}"); | ||||||
| @ -1428,6 +1466,45 @@ mod tests { | |||||||
|         assert_eq!(stringified, expected); |         assert_eq!(stringified, expected); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[test] | ||||||
|  |     fn test_elixir() { | ||||||
|  |         let source = r#" | ||||||
|  |         defmodule TestModule do | ||||||
|  |           @moduledoc """ | ||||||
|  |           This is a test module | ||||||
|  |           """ | ||||||
|  | 
 | ||||||
|  |           @test_const "test" | ||||||
|  |           @other_const 123 | ||||||
|  | 
 | ||||||
|  |           def test_func(a, b) do | ||||||
|  |             a + b | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           defp private_func(x) do | ||||||
|  |             x * 2 | ||||||
|  |           end | ||||||
|  | 
 | ||||||
|  |           defmacro test_macro(expr) do | ||||||
|  |             quote do | ||||||
|  |               unquote(expr) | ||||||
|  |             end | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         defmodule AnotherModule do | ||||||
|  |           def another_func() do | ||||||
|  |             :ok | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |         "#;
 | ||||||
|  |         let definitions = extract_definitions("elixir", source).unwrap(); | ||||||
|  |         let stringified = stringify_definitions(&definitions); | ||||||
|  |         println!("{stringified}"); | ||||||
|  |         let expected = "module AnotherModule{func another_func();};module TestModule{func test_func(a, b);};"; | ||||||
|  |         assert_eq!(stringified, expected); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_unsupported_language() { |     fn test_unsupported_language() { | ||||||
|         let source = "print('Hello, world!')"; |         let source = "print('Hello, world!')"; | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ local Popup = require("nui.popup") | |||||||
| local Utils = require("avante.utils") | local Utils = require("avante.utils") | ||||||
| local event = require("nui.utils.autocmd").event | local event = require("nui.utils.autocmd").event | ||||||
| local Config = require("avante.config") | local Config = require("avante.config") | ||||||
| local fn = vim.fn |  | ||||||
| 
 | 
 | ||||||
| local filetype_map = { | local filetype_map = { | ||||||
|   ["javascriptreact"] = "javascript", |   ["javascriptreact"] = "javascript", | ||||||
| @ -34,14 +33,13 @@ function RepoMap.get_ts_lang(filepath) | |||||||
| end | end | ||||||
| 
 | 
 | ||||||
| function RepoMap.get_filetype(filepath) | function RepoMap.get_filetype(filepath) | ||||||
|   local filetype = vim.filetype.match({ filename = filepath }) |   -- Some files are sometimes not detected correctly when buffer is not included | ||||||
|   -- TypeScript files are sometimes not detected correctly |  | ||||||
|   -- https://github.com/neovim/neovim/issues/27265 |   -- https://github.com/neovim/neovim/issues/27265 | ||||||
|   if not filetype then | 
 | ||||||
|     local ext = fn.fnamemodify(filepath, ":e") |   local buf = vim.api.nvim_create_buf(false, true) | ||||||
|     if ext == "tsx" then filetype = "typescriptreact" end |   local filetype = vim.filetype.match({ filename = filepath, buf = buf }) | ||||||
|     if ext == "ts" then filetype = "typescript" end |   vim.api.nvim_buf_delete(buf, { force = true }) | ||||||
|   end | 
 | ||||||
|   return filetype |   return filetype | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Radosław Woźniak
						Radosław Woźniak