diff --git a/crates/pi-natives/src/chunk/ast_astro.rs b/crates/pi-natives/src/chunk/ast_astro.rs index 53a854fba..45b1a9864 100644 --- a/crates/pi-natives/src/chunk/ast_astro.rs +++ b/crates/pi-natives/src/chunk/ast_astro.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct AstroClassifier; @@ -27,89 +27,116 @@ impl LangClassifier for AstroClassifier { fn classify_astro_node<'t>(node: Node<'t>, source: &str) -> Option> { match node.kind() { "frontmatter" => Some(classify_frontmatter(node, source)), - "frontmatter_js_block" => Some(group_candidate(node, "code", source)), + "frontmatter_js_block" => Some(group_candidate(node, ChunkKind::Code, source)), "element" => classify_element(node, source), "script_element" => Some(classify_script_element(node, source)), "style_element" => Some(classify_style_element(node, source)), "html_interpolation" => Some(classify_html_interpolation(node, source)), "attribute_interpolation" => Some(classify_attribute_interpolation(node, source)), - "attribute_js_expr" => Some(group_candidate(node, "expr", source)), - "text" => Some(group_candidate(node, "text", source)), + "attribute_js_expr" => Some(group_candidate(node, ChunkKind::Expression, source)), + "text" => Some(group_candidate(node, ChunkKind::Text, source)), _ => None, } } fn classify_frontmatter<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - make_container_chunk( + force_container(make_container_chunk( node, - "frontmatter".to_string(), + ChunkKind::Frontmatter, + None, source, recurse_into(node, ChunkContext::ClassBody, &[], &["frontmatter_js_block"]), - ) + )) } fn classify_element<'t>(node: Node<'t>, source: &str) -> Option> { let tag_name = extract_tag_name(node, source)?; - let prefix = if is_component_name(tag_name.as_str()) { - "component" + let recurse = Some(recurse_self(node, ChunkContext::ClassBody)); + if is_component_name(tag_name.as_str()) { + Some(force_container(make_explicit_candidate( + node, + ChunkKind::Tag, + format!("component_{tag_name}"), + source, + recurse, + ))) } else { - "tag" - }; - Some(make_container_chunk( - node, - format!("{prefix}_{tag_name}"), - source, - Some(recurse_self(node, ChunkContext::ClassBody)), - )) + Some(force_container(make_container_chunk( + node, + ChunkKind::Tag, + Some(tag_name), + source, + recurse, + ))) + } } fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "is:inline", source) { - "script_inline" - } else { - "script" - }; + let identifier = has_attribute(node, "is:inline", source).then_some("inline".to_string()); // The Astro grammar exposes script bodies as `raw_text`, not nested JS AST. - make_named_chunk(node, name.to_string(), source, None) + make_kind_chunk(node, ChunkKind::Script, identifier, source, None) } fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "define:vars", source) { - "style_vars" + let identifier = if has_attribute(node, "define:vars", source) { + Some("vars".to_string()) } else if has_attribute(node, "is:global", source) { - "style_global" + Some("global".to_string()) } else { - "style" + None }; // The Astro grammar exposes style bodies as `raw_text`, so the section itself // is the truthful chunk boundary. - make_named_chunk(node, name.to_string(), source, None) + make_kind_chunk(node, ChunkKind::Style, identifier, source, None) } fn classify_html_interpolation<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = child_by_kind(node, &["permissible_text"]) - .and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte()))) - .map_or_else(|| "expr".to_string(), |expr| format!("expr_{expr}")); + let identifier = child_by_kind(node, &["permissible_text"]) + .and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte()))); if let Some(nested_element) = child_by_kind(node, &["element", "script_element", "style_element"]) { - make_container_chunk( + force_container(make_container_chunk( node, - name, + ChunkKind::Expression, + identifier, source, Some(recurse_self(nested_element, ChunkContext::ClassBody)), - ) + )) } else { - make_named_chunk(node, name, source, None) + make_kind_chunk(node, ChunkKind::Expression, identifier, source, None) } } fn classify_attribute_interpolation<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = child_by_kind(node, &["attribute_js_expr"]) + let identifier = child_by_kind(node, &["attribute_js_expr"]) .and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte()))) - .map_or_else(|| "attr_expr".to_string(), |expr| format!("attr_expr_{expr}")); - make_named_chunk(node, name, source, None) + .map_or_else(|| "expr".to_string(), |expr| format!("expr_{expr}")); + make_kind_chunk(node, ChunkKind::Attr, Some(identifier), source, None) +} + +fn make_explicit_candidate<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + recurse, + source, + ) +} + +const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> { + candidate.force_recurse = true; + candidate } fn extract_tag_name(node: Node<'_>, source: &str) -> Option { diff --git a/crates/pi-natives/src/chunk/ast_bash_make_diff.rs b/crates/pi-natives/src/chunk/ast_bash_make_diff.rs index 1d5b2b826..eb0372bd7 100644 --- a/crates/pi-natives/src/chunk/ast_bash_make_diff.rs +++ b/crates/pi-natives/src/chunk/ast_bash_make_diff.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct ShellBuildClassifier; @@ -29,7 +29,8 @@ impl LangClassifier for ShellBuildClassifier { Self::extract_rule_target(node, source).unwrap_or_else(|| "anonymous".to_string()); Some(make_container_chunk( node, - format!("rule_{name}"), + ChunkKind::Rule, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &[], &["recipe"]), )) @@ -37,30 +38,32 @@ impl LangClassifier for ShellBuildClassifier { "variable_assignment" | "shell_assignment" => { let name = Self::extract_var_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("var_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)) }, "define_directive" => { let name = Self::extract_var_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("define_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Define, Some(name), source, None)) }, - "conditional" => Some(positional_candidate(node, "if", source)), + "conditional" => Some(positional_candidate(node, ChunkKind::If, source)), // Bash commands and pipelines - "command" | "pipeline" => Some(group_candidate(node, "stmts", source)), + "command" | "pipeline" => Some(group_candidate(node, ChunkKind::Statements, source)), // Bash control flow - "if_statement" => Some(positional_candidate(node, "if", source)), - "case_statement" => Some(positional_candidate(node, "switch", source)), - "while_statement" | "for_statement" => Some(positional_candidate(node, "loop", source)), + "if_statement" => Some(positional_candidate(node, ChunkKind::If, source)), + "case_statement" => Some(positional_candidate(node, ChunkKind::Switch, source)), + "while_statement" | "for_statement" => { + Some(positional_candidate(node, ChunkKind::Loop, source)) + }, // Bash function definition "function_definition" => Some(named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody), )), // Diff nodes - "hunks" => Some(group_candidate(node, "hunks", source)), - "file_change" => Some(named_candidate(node, "file", source, None)), + "hunks" => Some(group_candidate(node, ChunkKind::Hunks, source)), + "file_change" => Some(named_candidate(node, ChunkKind::File, source, None)), _ => None, } } @@ -71,11 +74,13 @@ impl LangClassifier for ShellBuildClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { - "if_statement" => Some(positional_candidate(node, "if", source)), - "case_statement" => Some(positional_candidate(node, "switch", source)), - "while_statement" | "for_statement" => Some(positional_candidate(node, "loop", source)), - "command" | "pipeline" => Some(group_candidate(node, "stmts", source)), - "subshell" => Some(positional_candidate(node, "block", source)), + "if_statement" => Some(positional_candidate(node, ChunkKind::If, source)), + "case_statement" => Some(positional_candidate(node, ChunkKind::Switch, source)), + "while_statement" | "for_statement" => { + Some(positional_candidate(node, ChunkKind::Loop, source)) + }, + "command" | "pipeline" => Some(group_candidate(node, ChunkKind::Statements, source)), + "subshell" => Some(positional_candidate(node, ChunkKind::Block, source)), _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_c_cpp_objc.rs b/crates/pi-natives/src/chunk/ast_c_cpp_objc.rs index 5ecebc62c..9d75a061a 100644 --- a/crates/pi-natives/src/chunk/ast_c_cpp_objc.rs +++ b/crates/pi-natives/src/chunk/ast_c_cpp_objc.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*, defaults::classify_var_decl}; +use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind}; pub struct CCppClassifier; @@ -94,30 +94,27 @@ fn extract_c_field_name(node: Node<'_>, source: &str) -> Option { extract_c_declarator_name(decl, source) } -/// Build a prefixed name for a C/C++ function node using declarator traversal. -fn c_prefixed_fn_name(prefix: &str, node: Node<'_>, source: &str) -> String { - let identifier = - extract_c_function_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - format!("{prefix}_{identifier}") -} - impl LangClassifier for CCppClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { // ── Imports ── "include_directive" | "preproc_include" | "using_directive" | "using_statement" - | "import_declaration" | "module_import" => Some(group_candidate(node, "imports", source)), + | "import_declaration" | "module_import" => { + Some(group_candidate(node, ChunkKind::Imports, source)) + }, // ── Functions ── - "function_definition" | "function_declaration" => Some(make_named_chunk( + "function_definition" | "function_declaration" => Some(make_kind_chunk( node, - c_prefixed_fn_name("fn", node, source), + ChunkKind::Function, + extract_c_function_name(node, source), source, recurse_body(node, ChunkContext::FunctionBody), )), - "constructor_definition" => Some(make_named_chunk( + "constructor_definition" => Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )), @@ -144,47 +141,50 @@ impl LangClassifier for CCppClassifier { candidate.checksum_start_byte = node.start_byte(); Some(candidate) }, - None => Some(named_candidate( + None => Some(make_candidate( node, - "template", - source, + ChunkKind::Template, + None, + NameStyle::Named, + signature_for_node(node, source), recurse_body(node, ChunkContext::FunctionBody), + source, )), } }, // ── Containers ── "class_specifier" | "class_declaration" | "class_interface" | "class_implementation" => { - Some(container_candidate(node, "class", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node))) }, "struct_specifier" | "struct_declaration" => { - Some(container_candidate(node, "struct", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node))) }, "enum_specifier" | "enum_declaration" => { - Some(container_candidate(node, "enum", source, recurse_enum(node))) + Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))) }, "namespace_definition" => { - Some(container_candidate(node, "mod", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node))) }, "union_declaration" => { - Some(container_candidate(node, "union", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Union, source, recurse_class(node))) }, // ── Types ── "type_alias_declaration" | "user_defined_type_definition" => { - Some(named_candidate(node, "type", source, recurse_class(node))) + Some(named_candidate(node, ChunkKind::Type, source, recurse_class(node))) }, // ── Variables / assignments ── "variable_declaration" => Some(classify_var_decl(node, source)), "assignment_statement" | "property_declaration" => { - Some(group_candidate(node, "decls", source)) + Some(group_candidate(node, ChunkKind::Declarations, source)) }, // ── Macros ── "macro_definition" => Some(named_candidate( node, - "macro", + ChunkKind::Macro, source, recurse_body(node, ChunkContext::FunctionBody), )), @@ -194,7 +194,7 @@ impl LangClassifier for CCppClassifier { | "do_statement" | "try_block" => Some(classify_function_c(node, source)), // ── Statements ── - "expression_statement" => Some(group_candidate(node, "stmts", source)), + "expression_statement" => Some(group_candidate(node, ChunkKind::Statements, source)), _ => None, } @@ -208,16 +208,18 @@ impl LangClassifier for CCppClassifier { .or_else(|| extract_identifier(node, source)) .unwrap_or_else(|| "anonymous".to_string()); if name == "constructor" { - Some(make_named_chunk( + Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )) } else { - Some(make_named_chunk( + Some(make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), )) @@ -225,40 +227,41 @@ impl LangClassifier for CCppClassifier { }, // ── Constructors ── - "constructor_definition" | "constructor_declaration" => Some(make_named_chunk( + "constructor_definition" | "constructor_declaration" => Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )), // ── Fields ── "field_declaration" => Some(match extract_c_field_name(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }), // ── Enum variants ── "enum_constant" => Some(match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None), - None => group_candidate(node, "variants", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None), + None => group_candidate(node, ChunkKind::Variants, source), }), // ── Nested containers ── "class_specifier" | "class_declaration" | "class_interface" | "class_implementation" => { - Some(container_candidate(node, "class", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node))) }, "struct_specifier" | "struct_declaration" => { - Some(container_candidate(node, "struct", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node))) }, "enum_specifier" | "enum_declaration" => { - Some(container_candidate(node, "enum", source, recurse_enum(node))) + Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))) }, "union_declaration" => { - Some(container_candidate(node, "union", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Union, source, recurse_class(node))) }, "namespace_definition" => { - Some(container_candidate(node, "mod", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node))) }, // ── Templates (class body) ── @@ -281,17 +284,20 @@ impl LangClassifier for CCppClassifier { candidate.checksum_start_byte = node.start_byte(); Some(candidate) }, - None => Some(named_candidate( + None => Some(make_candidate( node, - "template", - source, + ChunkKind::Template, + None, + NameStyle::Named, + signature_for_node(node, source), recurse_body(node, ChunkContext::FunctionBody), + source, )), } }, // ── Types ── - "type_alias_declaration" => Some(named_candidate(node, "type", source, None)), + "type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)), _ => None, } @@ -306,68 +312,39 @@ fn classify_function_c<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandid let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); match node.kind() { "if_statement" => { - make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source) + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, + "switch_statement" => { + make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source) + }, + "try_block" | "catch_clause" | "finally_clause" => { + make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source) + }, + "for_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, + "while_statement" => { + make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source) + }, + "do_statement" => { + make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source) }, - "switch_statement" => make_candidate( - node, - "switch".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "try_block" | "catch_clause" | "finally_clause" => make_candidate( - node, - "try".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "for_statement" => make_candidate( - node, - "for".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "while_statement" => make_candidate( - node, - "while".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "do_statement" => make_candidate( - node, - "block".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), "variable_declaration" => { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); if span > 1 { if let Some(name) = extract_single_declarator_name(node, source) { - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) } else { - group_candidate(node, "variable", source) + group_candidate(node, ChunkKind::Variable, source) } } else { - group_candidate(node, "variable", source) + group_candidate(node, ChunkKind::Variable, source) } }, _ => { let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + let kind = ChunkKind::from_sanitized_kind(kind_name.as_str()); + group_candidate(node, kind, source) }, } } diff --git a/crates/pi-natives/src/chunk/ast_clojure.rs b/crates/pi-natives/src/chunk/ast_clojure.rs index 64851c46e..06888aeec 100644 --- a/crates/pi-natives/src/chunk/ast_clojure.rs +++ b/crates/pi-natives/src/chunk/ast_clojure.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct ClojureClassifier; @@ -25,35 +25,29 @@ fn form_name(node: Node<'_>, source: &str) -> Option { }) } -/// Build a prefixed name from a Clojure form, falling back to "anonymous". -fn form_prefixed_name(node: Node<'_>, prefix: &str, source: &str) -> String { - let name = form_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - format!("{prefix}_{name}") -} - /// Classify a `list_lit` Clojure form based on its head symbol. fn classify_form<'t>(node: Node<'t>, source: &str, at_root: bool) -> RawChunkCandidate<'t> { let Some(head) = form_head(node, source) else { - return positional_candidate(node, "form", source); + return positional_candidate(node, ChunkKind::Form, source); }; match head.as_str() { "ns" | "require" | "use" | "import" | "refer-clojure" => { - group_candidate(node, "imports", source) + group_candidate(node, ChunkKind::Imports, source) }, "defn" | "defn-" | "defmacro" | "defmulti" | "defmethod" => { - make_named_chunk(node, form_prefixed_name(node, "fn", source), source, None) + make_kind_chunk(node, ChunkKind::Function, form_name(node, source), source, None) }, "def" | "defonce" => { - make_named_chunk(node, form_prefixed_name(node, "decl", source), source, None) + make_kind_chunk(node, ChunkKind::Decl, form_name(node, source), source, None) }, "defprotocol" => { - make_container_chunk(node, form_prefixed_name(node, "proto", source), source, None) + make_container_chunk(node, ChunkKind::Proto, form_name(node, source), source, None) }, "deftype" | "defrecord" | "extend-type" | "extend-protocol" => { - make_container_chunk(node, form_prefixed_name(node, "type", source), source, None) + make_container_chunk(node, ChunkKind::Type, form_name(node, source), source, None) }, - _ if at_root => positional_candidate(node, "form", source), - _ => group_candidate(node, "block", source), + _ if at_root => positional_candidate(node, ChunkKind::Form, source), + _ => group_candidate(node, ChunkKind::Block, source), } } diff --git a/crates/pi-natives/src/chunk/ast_cmake.rs b/crates/pi-natives/src/chunk/ast_cmake.rs index dd62d9c18..70d1918c7 100644 --- a/crates/pi-natives/src/chunk/ast_cmake.rs +++ b/crates/pi-natives/src/chunk/ast_cmake.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct CMakeClassifier; @@ -47,7 +47,8 @@ fn classify_definition<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> Option Some(make_container_chunk( node, - "if".to_string(), + ChunkKind::If, + None, source, Some(recurse_self(node, ChunkContext::FunctionBody)), )), "foreach_loop" | "while_loop" => Some(make_container_chunk( node, - "loop".to_string(), + ChunkKind::Loop, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["body"]), )), @@ -85,37 +89,38 @@ fn classify_command<'t>(node: Node<'t>, source: &str) -> Option make_named_chunk(node, "version_gate".to_string(), source, None), + "cmake_minimum_required" => make_kind_chunk(node, ChunkKind::VersionGate, None, source, None), "project" => { let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("project_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Project, Some(name), source, None) }, - "include" | "find_package" => group_candidate(node, "imports", source), + "include" | "find_package" => group_candidate(node, ChunkKind::Imports, source), "option" => { let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("option_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Option, Some(name), source, None) }, "set" => { let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) }, "add_library" | "add_executable" | "add_custom_target" => { let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("target_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Target, Some(name), source, None) }, - "install" | "export" => group_candidate(node, "install", source), - other => group_candidate(node, &format!("cmd_{other}"), source), + "install" | "export" => group_candidate(node, ChunkKind::Install, source), + other => make_kind_chunk(node, ChunkKind::Cmd, Some(other.to_string()), source, None), }) } fn classify_if_child<'t>(node: Node<'t>, source: &str) -> Option> { match node.kind() { - "if_command" => Some(group_candidate(node, "cond", source)), - "elseif_command" => Some(positional_candidate(node, "elif", source)), - "else_command" => Some(positional_candidate(node, "else", source)), + "if_command" => Some(group_candidate(node, ChunkKind::Cond, source)), + "elseif_command" => Some(positional_candidate(node, ChunkKind::Elif, source)), + "else_command" => Some(positional_candidate(node, ChunkKind::Else, source)), "body" => Some(make_container_chunk( node, - "block".to_string(), + ChunkKind::Block, + None, source, Some(recurse_self(node, ChunkContext::FunctionBody)), )), diff --git a/crates/pi-natives/src/chunk/ast_csharp_java.rs b/crates/pi-natives/src/chunk/ast_csharp_java.rs index 6c7a9b3e0..ff82e8202 100644 --- a/crates/pi-natives/src/chunk/ast_csharp_java.rs +++ b/crates/pi-natives/src/chunk/ast_csharp_java.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*, defaults::classify_var_decl}; +use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind}; pub struct CSharpJavaClassifier; @@ -13,54 +13,57 @@ impl LangClassifier for CSharpJavaClassifier { "import_declaration" | "using_directive" | "package_declaration" - | "namespace_statement" => Some(group_candidate(node, "imports", source)), + | "namespace_statement" => Some(group_candidate(node, ChunkKind::Imports, source)), // ── Functions ── "method_declaration" => Some(named_candidate( node, - "meth", + ChunkKind::Method, source, recurse_body(node, ChunkContext::FunctionBody), )), "function_declaration" | "function_definition" => Some(named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody), )), // ── Constructors ── - "constructor_declaration" => Some(make_named_chunk( + "constructor_declaration" => Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )), // ── Containers ── "class_declaration" => { - Some(container_candidate(node, "class", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node))) }, "interface_declaration" => { - Some(container_candidate(node, "iface", source, recurse_interface(node))) + Some(container_candidate(node, ChunkKind::Iface, source, recurse_interface(node))) + }, + "enum_declaration" => { + Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))) }, - "enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))), "namespace_declaration" | "file_scoped_namespace_declaration" => { - Some(container_candidate(node, "mod", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node))) }, "struct_declaration" | "record_declaration" => { - Some(container_candidate(node, "struct", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node))) }, // ── Types ── "type_alias_declaration" => { - Some(named_candidate(node, "type", source, recurse_class(node))) + Some(named_candidate(node, ChunkKind::Type, source, recurse_class(node))) }, // ── Variables / assignments ── "variable_declaration" | "lexical_declaration" => Some(classify_var_decl(node, source)), "property_declaration" | "state_variable_declaration" => { - Some(group_candidate(node, "decls", source)) + Some(group_candidate(node, ChunkKind::Declarations, source)) }, // ── Control flow (top-level scripts) ── @@ -70,7 +73,7 @@ impl LangClassifier for CSharpJavaClassifier { }, // ── Statements ── - "expression_statement" => Some(group_candidate(node, "stmts", source)), + "expression_statement" => Some(group_candidate(node, ChunkKind::Statements, source)), _ => None, } @@ -80,33 +83,37 @@ impl LangClassifier for CSharpJavaClassifier { match node.kind() { // ── Container declarations (inside namespace/class bodies) ── "class_declaration" => { - Some(container_candidate(node, "class", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node))) }, "interface_declaration" => { - Some(container_candidate(node, "iface", source, recurse_interface(node))) + Some(container_candidate(node, ChunkKind::Iface, source, recurse_interface(node))) + }, + "enum_declaration" => { + Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))) }, - "enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))), "struct_declaration" | "record_declaration" => { - Some(container_candidate(node, "struct", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node))) }, "namespace_declaration" | "file_scoped_namespace_declaration" => { - Some(container_candidate(node, "mod", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node))) }, // ── Methods ── "method_declaration" | "function_declaration" | "function_definition" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); if name == "constructor" { - Some(make_named_chunk( + Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )) } else { - Some(make_named_chunk( + Some(make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), )) @@ -114,9 +121,10 @@ impl LangClassifier for CSharpJavaClassifier { }, // ── Constructors ── - "constructor_declaration" | "secondary_constructor" => Some(make_named_chunk( + "constructor_declaration" | "secondary_constructor" => Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )), @@ -126,21 +134,21 @@ impl LangClassifier for CSharpJavaClassifier { | "property_declaration" | "constant_declaration" | "event_field_declaration" => Some(match extract_field_name(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }), // ── Enum members ── "enum_member_declaration" | "enum_constant" | "enum_entry" => { Some(match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None), - None => group_candidate(node, "variants", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None), + None => group_candidate(node, ChunkKind::Variants, source), }) }, // ── Static blocks ── "class_static_block" => { - Some(make_named_chunk(node, "static_init".to_string(), source, None)) + Some(make_kind_chunk(node, ChunkKind::StaticInit, None, source, None)) }, _ => None, @@ -176,79 +184,44 @@ fn classify_function_csharp_java<'tree>( let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); match node.kind() { "if_statement" => { - make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source) + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, + "switch_statement" | "switch_expression" => { + make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source) + }, + "try_statement" | "catch_clause" | "finally_clause" => { + make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source) + }, + "for_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, + "foreach_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, + "while_statement" => { + make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source) + }, + "do_statement" => { + make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source) }, - "switch_statement" | "switch_expression" => make_candidate( - node, - "switch".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "try_statement" | "catch_clause" | "finally_clause" => make_candidate( - node, - "try".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "for_statement" => make_candidate( - node, - "for".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "foreach_statement" => make_candidate( - node, - "for".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "while_statement" => make_candidate( - node, - "while".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "do_statement" => make_candidate( - node, - "block".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), "variable_declaration" | "lexical_declaration" => { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); if span > 1 { if let Some(name) = extract_single_declarator_name(node, source) { - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + group_from_sanitized(node, source) } } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + group_from_sanitized(node, source) } }, - _ => { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) - }, + _ => group_from_sanitized(node, source), } } + +fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let kind_name = sanitize_node_kind(node.kind()); + let kind = ChunkKind::from_sanitized_kind(kind_name.as_str()); + make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source) +} diff --git a/crates/pi-natives/src/chunk/ast_css.rs b/crates/pi-natives/src/chunk/ast_css.rs index d6545ebed..2bcd608ef 100644 --- a/crates/pi-natives/src/chunk/ast_css.rs +++ b/crates/pi-natives/src/chunk/ast_css.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct CssClassifier; @@ -29,7 +29,8 @@ fn classify_rule_set<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> let name = extract_css_selector(node, source).unwrap_or_else(|| "anonymous".to_string()); make_container_chunk( node, - format!("rule_{name}"), + ChunkKind::Rule, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &[], &["block"]), ) @@ -39,10 +40,10 @@ fn classify_rule_set<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> /// named container. fn classify_at_rule<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = extract_css_selector(node, source).unwrap_or_else(|| "rule".to_string()); - // keyframes_statement uses `keyframe_block_list` as body; others use `block` make_container_chunk( node, - format!("at_{name}"), + ChunkKind::At, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &[], &["block", "keyframe_block_list"]), ) @@ -53,19 +54,16 @@ fn classify_at_rule<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { fn classify_css_node<'t>(node: Node<'t>, source: &str) -> Option> { match node.kind() { "rule_set" => Some(classify_rule_set(node, source)), - // tree-sitter-css emits specific `*_statement` kinds for known at-rules - // and generic `at_rule` for unknown ones (e.g. @font-face). "at_rule" | "media_statement" | "keyframes_statement" | "supports_statement" => { Some(classify_at_rule(node, source)) }, "keyframe_block" => Some(named_candidate( node, - "frame", + ChunkKind::Frame, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), - // Top-level or nested property declarations. - "declaration" => Some(group_candidate(node, "fields", source)), + "declaration" => Some(group_candidate(node, ChunkKind::Fields, source)), _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_data_formats.rs b/crates/pi-natives/src/chunk/ast_data_formats.rs index dd06f8cd4..2eb1d5890 100644 --- a/crates/pi-natives/src/chunk/ast_data_formats.rs +++ b/crates/pi-natives/src/chunk/ast_data_formats.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct DataFormatsClassifier; @@ -33,65 +33,89 @@ fn classify_data_node<'t>( // Key-value pairs (JSON pairs, YAML mappings) "pair" => { let name = extract_pair_key(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("key_{name}"), source, recurse_value_container(node))) + Some(make_kind_chunk( + node, + ChunkKind::Key, + Some(name), + source, + recurse_value_container(node), + )) }, "block_mapping_pair" | "flow_pair" => { let name = extract_yaml_key(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("key_{name}"), source, recurse_value_container(node))) + Some(make_kind_chunk( + node, + ChunkKind::Key, + Some(name), + source, + recurse_value_container(node), + )) }, // TOML tables "table" => Some(container_candidate( node, - "table", + ChunkKind::Table, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), - // TOML array tables [[...]] - "table_array_element" => Some(container_candidate( + // TOML array tables + "table_array_element" => Some(make_candidate( node, - "table_array", - source, + ChunkKind::Table, + "table_array".to_string(), + NameStyle::Named, + signature_for_node(node, source), Some(recurse_self(node, ChunkContext::ClassBody)), + source, )), // TOML inline tables "inline_table" => Some(make_container_chunk( node, - "table".to_string(), + ChunkKind::Table, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), // JSON objects "object" => Some(make_container_chunk( node, - "object".to_string(), + ChunkKind::Object, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), // JSON arrays "array" => Some(make_container_chunk( node, - "array".to_string(), + ChunkKind::Array, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), // YAML block/flow mappings "block_mapping" | "flow_mapping" => Some(make_container_chunk( node, - "map".to_string(), + ChunkKind::Map, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), // YAML block/flow sequences "block_sequence" | "flow_sequence" => Some(make_container_chunk( node, - "list".to_string(), + ChunkKind::List, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), // YAML sequence items (only when nested, not at root level) - "block_sequence_item" if !is_root => Some(positional_candidate(node, "item", source)), + "block_sequence_item" if !is_root => { + Some(positional_candidate(node, ChunkKind::Item, source)) + }, // Nix-style attributes that appear in data contexts - "attribute" => Some(named_candidate(node, "attr", source, recurse_value_container(node))), + "attribute" => { + Some(named_candidate(node, ChunkKind::Attr, source, recurse_value_container(node))) + }, _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_dockerfile.rs b/crates/pi-natives/src/chunk/ast_dockerfile.rs index e3d5f0821..c78f0a109 100644 --- a/crates/pi-natives/src/chunk/ast_dockerfile.rs +++ b/crates/pi-natives/src/chunk/ast_dockerfile.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct DockerfileClassifier; @@ -53,7 +53,15 @@ fn classify_command_instruction<'t>(node: Node<'t>, source: &str) -> Option return None, }; - Some(make_container_chunk(node, chunk_name.to_string(), source, recurse_command(node))) + Some(make_candidate( + node, + ChunkKind::Cmd, + chunk_name.to_string(), + NameStyle::Named, + signature_for_node(node, source), + recurse_command(node), + source, + )) } impl LangClassifier for DockerfileClassifier { @@ -61,36 +69,37 @@ impl LangClassifier for DockerfileClassifier { match node.kind() { "from_instruction" => { let name = extract_stage_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("stage_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Stage, Some(name), source, None)) }, "arg_instruction" => { let name = extract_arg_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("arg_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Arg, Some(name), source, None)) }, "env_instruction" => { let name = extract_pair_key(node, "env_pair", source) .unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("env_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Env, Some(name), source, None)) }, "label_instruction" => { let name = extract_pair_key(node, "label_pair", source) .unwrap_or_else(|| "anonymous".to_string()); - Some(make_named_chunk(node, format!("label_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Label, Some(name), source, None)) }, "run_instruction" | "cmd_instruction" | "entrypoint_instruction" => { classify_command_instruction(node, source) }, "healthcheck_instruction" => Some(make_container_chunk( node, - "healthcheck".to_string(), + ChunkKind::Healthcheck, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["cmd_instruction"]), )), - "copy_instruction" => Some(group_candidate(node, "copy", source)), - "add_instruction" => Some(group_candidate(node, "add", source)), - "workdir_instruction" => Some(group_candidate(node, "workdir", source)), - "expose_instruction" => Some(group_candidate(node, "expose", source)), - "user_instruction" => Some(group_candidate(node, "user", source)), + "copy_instruction" => Some(group_candidate(node, ChunkKind::Copy, source)), + "add_instruction" => Some(group_candidate(node, ChunkKind::Add, source)), + "workdir_instruction" => Some(group_candidate(node, ChunkKind::Workdir, source)), + "expose_instruction" => Some(group_candidate(node, ChunkKind::Expose, source)), + "user_instruction" => Some(group_candidate(node, ChunkKind::User, source)), _ => None, } } @@ -102,8 +111,8 @@ impl LangClassifier for DockerfileClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { "cmd_instruction" => classify_command_instruction(node, source), - "shell_command" => Some(group_candidate(node, "shell", source)), - "json_string_array" => Some(group_candidate(node, "argv", source)), + "shell_command" => Some(group_candidate(node, ChunkKind::Shell, source)), + "json_string_array" => Some(group_candidate(node, ChunkKind::Argv, source)), _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_elixir.rs b/crates/pi-natives/src/chunk/ast_elixir.rs index fdb06be7f..805811654 100644 --- a/crates/pi-natives/src/chunk/ast_elixir.rs +++ b/crates/pi-natives/src/chunk/ast_elixir.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct ElixirClassifier; @@ -21,44 +21,49 @@ fn classify_call<'t>(node: Node<'t>, source: &str, at_root: bool) -> RawChunkCan match target.as_str() { "defmodule" => make_container_chunk( node, - format!("mod_{}", name()), + ChunkKind::Module, + Some(name()), source, recurse_body(node, ChunkContext::ClassBody), ), "defprotocol" => make_container_chunk( node, - format!("proto_{}", name()), + ChunkKind::Proto, + Some(name()), source, recurse_body(node, ChunkContext::ClassBody), ), "defimpl" => make_container_chunk( node, - format!("impl_{}", name()), + ChunkKind::Impl, + Some(name()), source, recurse_body(node, ChunkContext::ClassBody), ), "def" | "defp" | "defdelegate" | "defguard" | "defguardp" | "defn" | "defnp" => { - make_named_chunk( + make_kind_chunk( node, - format!("fn_{}", name()), + ChunkKind::Function, + Some(name()), source, recurse_body(node, ChunkContext::FunctionBody), ) }, - "defmacro" | "defmacrop" => make_named_chunk( + "defmacro" | "defmacrop" => make_kind_chunk( node, - format!("macro_{}", name()), + ChunkKind::Macro, + Some(name()), source, recurse_body(node, ChunkContext::FunctionBody), ), - "alias" | "import" | "require" | "use" => group_candidate(node, "imports", source), - "defstruct" | "defexception" => group_candidate(node, "decls", source), - "if" | "unless" => positional_candidate(node, "if", source), - "case" | "cond" | "receive" => positional_candidate(node, "switch", source), - "for" => positional_candidate(node, "for", source), - "try" | "with" => positional_candidate(node, "block", source), - _ if at_root => group_candidate(node, "stmts", source), - _ => group_candidate(node, "block", source), + "alias" | "import" | "require" | "use" => group_candidate(node, ChunkKind::Imports, source), + "defstruct" | "defexception" => group_candidate(node, ChunkKind::Declarations, source), + "if" | "unless" => positional_candidate(node, ChunkKind::If, source), + "case" | "cond" | "receive" => positional_candidate(node, ChunkKind::Switch, source), + "for" => positional_candidate(node, ChunkKind::For, source), + "try" | "with" => positional_candidate(node, ChunkKind::Block, source), + _ if at_root => group_candidate(node, ChunkKind::Statements, source), + _ => group_candidate(node, ChunkKind::Block, source), } } diff --git a/crates/pi-natives/src/chunk/ast_erlang.rs b/crates/pi-natives/src/chunk/ast_erlang.rs index cc4476fef..a175a49bd 100644 --- a/crates/pi-natives/src/chunk/ast_erlang.rs +++ b/crates/pi-natives/src/chunk/ast_erlang.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct ErlangClassifier; @@ -54,28 +54,34 @@ impl LangClassifier for ErlangClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { "module_attribute" => { - make_named_chunk(node, format!("mod_{}", erlang_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Module, erlang_name(node, source), source, None) }, - "export_attribute" | "export_type_attribute" => group_candidate(node, "exports", source), - "import_attribute" => group_candidate(node, "imports", source), - "pp_include" | "pp_include_lib" => group_candidate(node, "includes", source), + "export_attribute" | "export_type_attribute" => { + group_candidate(node, ChunkKind::Exports, source) + }, + "import_attribute" => group_candidate(node, ChunkKind::Imports, source), + "pp_include" | "pp_include_lib" => group_candidate(node, ChunkKind::Includes, source), "pp_define" => { - make_named_chunk(node, format!("macro_{}", erlang_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Macro, erlang_name(node, source), source, None) }, - "record_decl" => make_container_chunk( + "record_decl" => make_candidate( node, + ChunkKind::Struct, format!("record_{}", erlang_name(node, source)?), - source, + NameStyle::Named, + signature_for_node(node, source), Some(recurse_self(node, ChunkContext::ClassBody)), + source, ), "type_alias" => { - make_named_chunk(node, format!("type_{}", erlang_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Type, erlang_name(node, source), source, None) }, // The Erlang grammar exposes each top-level clause as its own `fun_decl`. // Keep that shape instead of inventing a synthetic merged function node. - "fun_decl" => make_named_chunk( + "fun_decl" => make_kind_chunk( node, - format!("fn_{}", erlang_name(node, source)?), + ChunkKind::Function, + erlang_name(node, source), source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), @@ -87,7 +93,7 @@ impl LangClassifier for ErlangClassifier { fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { "record_field" => { - make_named_chunk(node, format!("field_{}", erlang_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Field, erlang_name(node, source), source, None) }, _ => return None, }) @@ -95,42 +101,71 @@ impl LangClassifier for ErlangClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "function_clause" => make_named_chunk( + "function_clause" => make_kind_chunk( node, - format!("clause_{}", erlang_name(node, source)?), + ChunkKind::Clause, + erlang_name(node, source), source, recurse_clause_body(node), ), - "fun_clause" | "cr_clause" => { - make_named_chunk(node, "clause".to_string(), source, recurse_clause_body(node)) - }, - "receive_after" => { - make_named_chunk(node, "after".to_string(), source, recurse_clause_body(node)) - }, - "catch_clause" => { - make_named_chunk(node, "catch".to_string(), source, recurse_clause_body(node)) - }, - "receive_expr" => make_named_chunk( + "fun_clause" | "cr_clause" => make_candidate( node, - "receive".to_string(), + ChunkKind::Clause, + None, + NameStyle::Named, + signature_for_node(node, source), + recurse_clause_body(node), source, - Some(recurse_self(node, ChunkContext::FunctionBody)), ), - "case_expr" => make_named_chunk( + "receive_after" => make_candidate( node, - "case".to_string(), + ChunkKind::After, + None, + NameStyle::Named, + signature_for_node(node, source), + recurse_clause_body(node), source, - Some(recurse_self(node, ChunkContext::FunctionBody)), ), - "try_expr" => make_named_chunk( + "catch_clause" => make_candidate( node, - "try".to_string(), + ChunkKind::Catch, + None, + NameStyle::Named, + signature_for_node(node, source), + recurse_clause_body(node), source, - Some(recurse_self(node, ChunkContext::FunctionBody)), ), - "anonymous_fun" => make_named_chunk( + "receive_expr" => make_candidate( node, - "fn_anonymous".to_string(), + ChunkKind::Receive, + None, + NameStyle::Named, + signature_for_node(node, source), + Some(recurse_self(node, ChunkContext::FunctionBody)), + source, + ), + "case_expr" => make_candidate( + node, + ChunkKind::Case, + None, + NameStyle::Named, + signature_for_node(node, source), + Some(recurse_self(node, ChunkContext::FunctionBody)), + source, + ), + "try_expr" => make_candidate( + node, + ChunkKind::Try, + None, + NameStyle::Named, + signature_for_node(node, source), + Some(recurse_self(node, ChunkContext::FunctionBody)), + source, + ), + "anonymous_fun" => make_kind_chunk( + node, + ChunkKind::Function, + Some("anonymous".to_string()), source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), diff --git a/crates/pi-natives/src/chunk/ast_go.rs b/crates/pi-natives/src/chunk/ast_go.rs index 7e28e290b..baf24872a 100644 --- a/crates/pi-natives/src/chunk/ast_go.rs +++ b/crates/pi-natives/src/chunk/ast_go.rs @@ -1,6 +1,6 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct GoClassifier; @@ -8,26 +8,28 @@ impl LangClassifier for GoClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { // ── Imports / package ── - "import_declaration" | "package_clause" => Some(group_candidate(node, "imports", source)), + "import_declaration" | "package_clause" => { + Some(group_candidate(node, ChunkKind::Imports, source)) + }, // ── Variables ── "const_declaration" | "var_declaration" | "short_var_declaration" => { Some(match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("var_{name}"), source, None), - None => group_candidate(node, "decls", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None), + None => group_candidate(node, ChunkKind::Declarations, source), }) }, // ── Functions ── "function_declaration" => Some(named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody), )), "method_declaration" => Some(named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody), )), @@ -45,7 +47,7 @@ impl LangClassifier for GoClassifier { // ── Statements ── "expression_statement" | "go_statement" | "defer_statement" | "send_statement" => { - Some(group_candidate(node, "stmts", source)) + Some(group_candidate(node, ChunkKind::Statements, source)) }, _ => None, @@ -55,17 +57,17 @@ impl LangClassifier for GoClassifier { fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { // ── Methods ── - "method_spec" => Some(named_candidate(node, "meth", source, None)), + "method_spec" => Some(named_candidate(node, ChunkKind::Method, source, None)), // ── Fields ── "field_declaration" | "embedded_field" => Some(match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }), // ── Field / method lists ── - "field_declaration_list" => Some(group_candidate(node, "fields", source)), - "method_spec_list" => Some(group_candidate(node, "methods", source)), + "field_declaration_list" => Some(group_candidate(node, ChunkKind::Fields, source)), + "method_spec_list" => Some(group_candidate(node, ChunkKind::Methods, source)), _ => None, } @@ -76,48 +78,48 @@ impl LangClassifier for GoClassifier { // ── Control flow ── "if_statement" => Some(make_candidate( node, - "if".to_string(), + ChunkKind::If, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), "switch_statement" | "expression_switch_statement" | "type_switch_statement" => { Some(make_candidate( node, - "switch".to_string(), + ChunkKind::Switch, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )) }, "select_statement" => Some(make_candidate( node, - "switch".to_string(), + ChunkKind::Switch, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), // ── Loops ── "for_statement" => Some(make_candidate( node, - "for".to_string(), + ChunkKind::For, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), // ── Blocks ── "go_statement" | "defer_statement" | "send_statement" => { - Some(group_candidate(node, "stmts", source)) + Some(group_candidate(node, ChunkKind::Statements, source)) }, // ── Variables ── @@ -125,14 +127,12 @@ impl LangClassifier for GoClassifier { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); Some(if span > 1 { if let Some(name) = extract_identifier(node, source) { - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + group_from_sanitized(node, source) } } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + group_from_sanitized(node, source) }) }, @@ -147,30 +147,18 @@ fn classify_function_go<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<' let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); match node.kind() { "if_statement" => { - make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source) + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) }, "switch_statement" | "expression_switch_statement" | "type_switch_statement" - | "select_statement" => make_candidate( - node, - "switch".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "for_statement" => make_candidate( - node, - "for".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - _ => group_candidate(node, "stmts", source), + | "select_statement" => { + make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source) + }, + "for_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, + _ => group_candidate(node, ChunkKind::Statements, source), } } @@ -192,15 +180,22 @@ fn classify_type_decl<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> return make_container_chunk_from( node, spec, - format!("type_{name}"), + ChunkKind::Type, + Some(name), source, Some(recurse), ); } - return make_named_chunk_from(node, spec, format!("type_{name}"), source, None); + return make_kind_chunk_from(node, spec, ChunkKind::Type, Some(name), source, None); } - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) +} + +fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let kind_name = sanitize_node_kind(node.kind()); + let kind = ChunkKind::from_sanitized_kind(kind_name.as_str()); + make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source) } /// For a `type_spec`, find a `struct_type` or `interface_type` child and return diff --git a/crates/pi-natives/src/chunk/ast_graphql.rs b/crates/pi-natives/src/chunk/ast_graphql.rs index febea2755..3fc952716 100644 --- a/crates/pi-natives/src/chunk/ast_graphql.rs +++ b/crates/pi-natives/src/chunk/ast_graphql.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct GraphqlClassifier; @@ -39,21 +39,21 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option Some(make_container_chunk( node, - "schema".to_string(), + ChunkKind::Schema, + None, source, Some(recurse_self(node, ChunkContext::ClassBody)), )), - "directive_definition" => Some(make_container_chunk( + "directive_definition" => Some(make_named_graphql_chunk( node, - format!( - "directive_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Directive, + extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()), source, recurse_into(node, ChunkContext::ClassBody, &[], &["arguments_definition"]), )), - "scalar_type_definition" => Some(make_named_chunk( + "scalar_type_definition" => Some(make_named_graphql_chunk( node, + ChunkKind::Type, format!( "scalar_{}", extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -63,42 +63,35 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option Some(make_container_chunk( node, - format!( - "type_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Type, + extract_graphql_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["fields_definition"]), )), "interface_type_definition" => Some(make_container_chunk( node, - format!( - "interface_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Interface, + extract_graphql_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["fields_definition"]), )), - "union_type_definition" => Some(make_named_chunk( + "union_type_definition" => Some(make_kind_chunk( node, - format!( - "union_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Union, + extract_graphql_name(node, source), source, None, )), "enum_type_definition" => Some(make_container_chunk( node, - format!( - "enum_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Enum, + extract_graphql_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["enum_values_definition"]), )), - "input_object_type_definition" => Some(make_container_chunk( + "input_object_type_definition" => Some(make_named_graphql_chunk( node, + ChunkKind::Type, format!( "input_{}", extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -106,14 +99,16 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option Some(make_container_chunk( + "operation_definition" => Some(make_named_graphql_chunk( node, + ChunkKind::Operation, extract_graphql_operation_chunk_name(node, source), source, recurse_into(node, ChunkContext::FunctionBody, &[], &["selection_set"]), )), - "fragment_definition" => Some(make_container_chunk( + "fragment_definition" => Some(make_named_graphql_chunk( node, + ChunkKind::Operation, format!( "fragment_{}", extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -127,12 +122,10 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> Option> { match node.kind() { - "root_operation_type_definition" => Some(make_named_chunk( + "root_operation_type_definition" => Some(make_named_graphql_chunk( node, - format!( - "root_{}", - extract_graphql_operation_type(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Root, + extract_graphql_operation_type(node, source).unwrap_or_else(|| "anonymous".to_string()), source, None, )), @@ -141,14 +134,15 @@ fn classify_graphql_class<'t>(node: Node<'t>, source: &str) -> Option { - make_container_chunk(node, format!("field_{name}"), source, Some(recurse)) + make_container_chunk(node, ChunkKind::Field, Some(name), source, Some(recurse)) }, - None => make_named_chunk(node, format!("field_{name}"), source, None), + None => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), }) }, "input_value_definition" => Some(classify_graphql_input_value(node, source)), - "enum_value_definition" => Some(make_named_chunk( + "enum_value_definition" => Some(make_named_graphql_chunk( node, + ChunkKind::Variant, format!( "value_{}", extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -177,16 +171,18 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option make_container_chunk_from( node, child, - format!("field_{name}"), + ChunkKind::Field, + Some(name), source, Some(recurse), ), - None => make_named_chunk_from(node, child, format!("field_{name}"), source, None), + None => make_kind_chunk_from(node, child, ChunkKind::Field, Some(name), source, None), }) }, - "fragment_spread" => Some(make_named_chunk_from( + "fragment_spread" => Some(make_named_graphql_chunk_from( node, child, + ChunkKind::Operation, format!( "spread_{}", extract_graphql_name(child, source).unwrap_or_else(|| "anonymous".to_string()) @@ -197,7 +193,8 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option Some(make_container_chunk_from( node, child, - "inline_fragment".to_string(), + ChunkKind::InlineFragment, + None, source, recurse_into(child, ChunkContext::FunctionBody, &[], &["selection_set"]), )), @@ -206,18 +203,49 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let prefix = match node.parent().map(|parent| parent.kind()) { - Some("input_fields_definition") => "field", - _ => "arg", - }; - make_named_chunk( + let name = extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()); + match node.parent().map(|parent| parent.kind()) { + Some("input_fields_definition") => { + make_kind_chunk(node, ChunkKind::Field, Some(name), source, None) + }, + _ => make_kind_chunk(node, ChunkKind::Arg, Some(name), source, None), + } +} + +fn make_named_graphql_chunk<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( node, - format!( - "{prefix}_{}", - extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + recurse, + source, + ) +} + +fn make_named_graphql_chunk_from<'t>( + range_node: Node<'t>, + signature_node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( + range_node, + kind, + identifier, + NameStyle::Named, + signature_for_node(signature_node, source), + recurse, source, - None, ) } diff --git a/crates/pi-natives/src/chunk/ast_haskell_scala.rs b/crates/pi-natives/src/chunk/ast_haskell_scala.rs index 5dc5d4ce8..62e7264dd 100644 --- a/crates/pi-natives/src/chunk/ast_haskell_scala.rs +++ b/crates/pi-natives/src/chunk/ast_haskell_scala.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct HaskellScalaClassifier; @@ -10,35 +10,49 @@ impl LangClassifier for HaskellScalaClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { // ── Imports / packages ── - "import_declaration" => group_candidate(node, "imports", source), - "package_declaration" => group_candidate(node, "imports", source), + "import_declaration" => group_candidate(node, ChunkKind::Imports, source), + "package_declaration" => group_candidate(node, ChunkKind::Imports, source), // ── Haskell module ── - "module" => container_candidate(node, "mod", source, recurse_class(node)), + "module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)), // ── Functions ── - "function_declaration" => { - named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody)) - }, - "function_definition" => { - named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody)) - }, + "function_declaration" => named_candidate( + node, + ChunkKind::Function, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), + "function_definition" => named_candidate( + node, + ChunkKind::Function, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), // ── Containers (Scala) ── - "class_definition" => container_candidate(node, "class", source, recurse_class(node)), - "object_definition" => container_candidate(node, "mod", source, recurse_class(node)), - "trait_definition" => container_candidate(node, "iface", source, recurse_interface(node)), + "class_definition" => { + container_candidate(node, ChunkKind::Class, source, recurse_class(node)) + }, + "object_definition" => { + container_candidate(node, ChunkKind::Module, source, recurse_class(node)) + }, + "trait_definition" => { + container_candidate(node, ChunkKind::Iface, source, recurse_interface(node)) + }, // ── Types ── "type_alias_declaration" | "type_item" => { - named_candidate(node, "type", source, recurse_class(node)) + named_candidate(node, ChunkKind::Type, source, recurse_class(node)) }, // ── Variables / assignments ── - "variable_declaration" | "assignment" => group_candidate(node, "decls", source), + "variable_declaration" | "assignment" => { + group_candidate(node, ChunkKind::Declarations, source) + }, // ── Statements ── - "expression_statement" => group_candidate(node, "stmts", source), + "expression_statement" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) @@ -50,16 +64,18 @@ impl LangClassifier for HaskellScalaClassifier { "function_declaration" | "function_definition" | "method_definition" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); if name == "constructor" { - make_named_chunk( + make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), ) } else { - make_named_chunk( + make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), ) @@ -69,8 +85,8 @@ impl LangClassifier for HaskellScalaClassifier { // ── Fields ── "variable_declaration" | "property_declaration" => { match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), } }, @@ -82,42 +98,36 @@ impl LangClassifier for HaskellScalaClassifier { let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); Some(match node.kind() { // ── Control flow ── - "if_statement" => make_candidate( - node, - "if".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), + "if_statement" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, "match_expression" => make_candidate( node, - "match".to_string(), + ChunkKind::Match, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), "for_expression" | "while_expression" => make_candidate( node, - "loop".to_string(), + ChunkKind::Loop, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), // ── Blocks ── "block_expression" => make_candidate( node, - "block".to_string(), + ChunkKind::Block, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), diff --git a/crates/pi-natives/src/chunk/ast_html_xml.rs b/crates/pi-natives/src/chunk/ast_html_xml.rs index e306c5392..c23e4f0a7 100644 --- a/crates/pi-natives/src/chunk/ast_html_xml.rs +++ b/crates/pi-natives/src/chunk/ast_html_xml.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct HtmlXmlClassifier; @@ -20,12 +20,13 @@ fn classify_element<'t>(node: Node<'t>, source: &str) -> Option Some(group_candidate(node, "text", source)), + "text_node" => Some(group_candidate(node, ChunkKind::Text, source)), _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_ini.rs b/crates/pi-natives/src/chunk/ast_ini.rs index dad78cdbf..80f0c58ad 100644 --- a/crates/pi-natives/src/chunk/ast_ini.rs +++ b/crates/pi-natives/src/chunk/ast_ini.rs @@ -7,7 +7,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct IniClassifier; @@ -25,20 +25,25 @@ fn classify_ini_root<'t>(node: Node<'t>, source: &str) -> Option make_container_chunk( node, - format!("section_{}", ini_name(node, source)?), + ChunkKind::Section, + Some(ini_name(node, source)?), source, Some(recurse_self(node, ChunkContext::ClassBody)), ), // INI permits settings before any section header; keep them as first-class // chunks instead of forcing them under a synthetic container. - "setting" => make_named_chunk(node, format!("key_{}", ini_name(node, source)?), source, None), + "setting" => { + make_kind_chunk(node, ChunkKind::Key, Some(ini_name(node, source)?), source, None) + }, _ => return None, }) } fn classify_ini_class<'t>(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "setting" => make_named_chunk(node, format!("key_{}", ini_name(node, source)?), source, None), + "setting" => { + make_kind_chunk(node, ChunkKind::Key, Some(ini_name(node, source)?), source, None) + }, _ => return None, }) } diff --git a/crates/pi-natives/src/chunk/ast_ipynb.rs b/crates/pi-natives/src/chunk/ast_ipynb.rs index 04f9c75e5..5c75ce2cb 100644 --- a/crates/pi-natives/src/chunk/ast_ipynb.rs +++ b/crates/pi-natives/src/chunk/ast_ipynb.rs @@ -26,7 +26,9 @@ use serde::Serialize; use serde_json::{Map, Value}; use crate::chunk::{ - build_chunk_tree, chunk_checksum, line_start_offsets, + build_chunk_tree, chunk_checksum, + kind::ChunkKind, + line_start_offsets, types::{ChunkNode, ChunkTree}, }; @@ -395,7 +397,8 @@ pub fn build_notebook_tree_from_virtual( let mut chunks: Vec = Vec::with_capacity(1 + regions.len() * 2); chunks.push(ChunkNode { path: String::new(), - name: "root".to_string(), + identifier: None, + kind: ChunkKind::Root, leaf: false, parent_path: None, children: Vec::new(), @@ -437,7 +440,6 @@ pub fn build_notebook_tree_from_virtual( if body_has_content { let sub_tree = build_chunk_tree(body, cell_language_str.as_str()) .map_err(|err| format!("Failed to parse cell_{} body: {err}", region.cell_num))?; - // sub_tree.chunks[0] is the sub-root; skip it. for sub_chunk in sub_tree.chunks.into_iter().skip(1) { let translated_path = format!("{}.{}", cell_path, sub_chunk.path); let translated_parent = match sub_chunk.parent_path.as_deref() { @@ -458,7 +460,8 @@ pub fn build_notebook_tree_from_virtual( let line_shift = region.content_line.saturating_sub(1); chunks.push(ChunkNode { path: translated_path.clone(), - name: sub_chunk.name, + identifier: sub_chunk.identifier, + kind: sub_chunk.kind, leaf: sub_chunk.leaf, parent_path: translated_parent, children: translated_children, @@ -484,13 +487,11 @@ pub fn build_notebook_tree_from_virtual( group: false, }); } - // Collect direct children (those whose parent_path equals cell_path). for sub_path in sub_tree.root_children { cell_children_paths.push(format!("{cell_path}.{sub_path}")); } } - // Cell parent chunk: spans from the marker line through the body. let cell_line_count = { let body_lines = if body_has_content { if body.ends_with('\n') { @@ -501,13 +502,14 @@ pub fn build_notebook_tree_from_virtual( } else { 0 }; - 1 + body_lines as u32 // +1 for the marker line + 1 + body_lines as u32 }; let cell_end_line = region.marker_line + cell_line_count.saturating_sub(1); let cell_leaf = cell_children_paths.is_empty(); chunks.push(ChunkNode { path: cell_path.clone(), - name: cell_path.clone(), + identifier: Some(cell_path.clone()), + kind: ChunkKind::Cell, leaf: cell_leaf, parent_path: Some(String::new()), children: cell_children_paths, diff --git a/crates/pi-natives/src/chunk/ast_js_ts.rs b/crates/pi-natives/src/chunk/ast_js_ts.rs index 1519661d8..e0c385214 100644 --- a/crates/pi-natives/src/chunk/ast_js_ts.rs +++ b/crates/pi-natives/src/chunk/ast_js_ts.rs @@ -2,7 +2,9 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*, defaults::promote_assigned_expression}; +use super::{ + classify::LangClassifier, common::*, defaults::promote_assigned_expression, kind::ChunkKind, +}; pub struct JsTsClassifier; @@ -19,7 +21,7 @@ impl LangClassifier for JsTsClassifier { // ── Imports ── "import_statement" | "import_declaration" => { - Some(group_candidate(node, "imports", source)) + Some(group_candidate(node, ChunkKind::Imports, source)) }, // ── Variables ── @@ -33,25 +35,30 @@ impl LangClassifier for JsTsClassifier { // ── Functions ── "function_declaration" => Some(named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody), )), // ── Containers ── "class_declaration" => { - Some(container_candidate(node, "class", source, recurse_class(node))) + Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node))) }, "interface_declaration" => { - Some(container_candidate(node, "interface", source, recurse_interface(node))) + Some(container_candidate(node, ChunkKind::Interface, source, recurse_interface(node))) }, - "enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))), - "internal_module" => { - Some(container_candidate(node, "mod", source, recurse_internal_module(node))) + "enum_declaration" => { + Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))) }, + "internal_module" => Some(container_candidate( + node, + ChunkKind::Module, + source, + recurse_internal_module(node), + )), // ── Types ── - "type_alias_declaration" => Some(named_candidate(node, "type", source, None)), + "type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)), // ── Control flow at top level ── "if_statement" | "switch_statement" | "switch_expression" | "try_statement" @@ -65,9 +72,9 @@ impl LangClassifier for JsTsClassifier { .into_iter() .find(|c| c.kind() == "internal_module"); if let Some(ns) = inner { - Some(container_candidate(ns, "mod", source, recurse_internal_module(ns))) + Some(container_candidate(ns, ChunkKind::Module, source, recurse_internal_module(ns))) } else { - Some(group_candidate(node, "stmts", source)) + Some(group_candidate(node, ChunkKind::Statements, source)) } }, @@ -85,9 +92,10 @@ impl LangClassifier for JsTsClassifier { "lexical_declaration" | "variable_declaration" => Some(classify_var_decl_js(node, source)), // ── Constructor ── - "constructor" => Some(make_named_chunk( + "constructor" => Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )), @@ -96,16 +104,18 @@ impl LangClassifier for JsTsClassifier { "method_definition" | "method_signature" | "abstract_method_signature" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); if name == "constructor" { - Some(make_named_chunk( + Some(make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), )) } else { - Some(make_named_chunk( + Some(make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), )) @@ -119,23 +129,23 @@ impl LangClassifier for JsTsClassifier { | "property_signature" | "property_declaration" | "abstract_class_field" => match extract_identifier(node, source) { - Some(name) => Some(make_named_chunk(node, format!("field_{name}"), source, None)), - None => Some(group_candidate(node, "fields", source)), + Some(name) => Some(make_kind_chunk(node, ChunkKind::Field, Some(name), source, None)), + None => Some(group_candidate(node, ChunkKind::Fields, source)), }, // ── Enum members ── "enum_assignment" | "enum_member_declaration" => match extract_identifier(node, source) { - Some(name) => Some(make_named_chunk(node, format!("variant_{name}"), source, None)), - None => Some(group_candidate(node, "variants", source)), + Some(name) => Some(make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None)), + None => Some(group_candidate(node, ChunkKind::Variants, source)), }, // ── Static blocks ── "class_static_block" => { - Some(make_named_chunk(node, "static_init".to_string(), source, None)) + Some(make_kind_chunk(node, ChunkKind::StaticInit, None, source, None)) }, // ── Types ── - "type_alias_declaration" => Some(named_candidate(node, "type", source, None)), + "type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)), _ => None, } @@ -153,41 +163,54 @@ fn classify_function_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<' let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); match node.kind() { // ── Control flow ── - "if_statement" => make_named_chunk(node, "if".to_string(), source, fn_recurse()), - "switch_statement" | "switch_expression" => { - make_named_chunk(node, "switch".to_string(), source, fn_recurse()) + "if_statement" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, + "switch_statement" | "switch_expression" => { + make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source) + }, + "try_statement" => { + make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source) }, - "try_statement" => make_named_chunk(node, "try".to_string(), source, fn_recurse()), // ── Loops ── - "for_statement" => make_named_chunk(node, "for".to_string(), source, fn_recurse()), - "for_in_statement" => make_named_chunk(node, "for_in".to_string(), source, fn_recurse()), - "for_of_statement" => make_named_chunk(node, "for_of".to_string(), source, fn_recurse()), - "while_statement" => make_named_chunk(node, "while".to_string(), source, fn_recurse()), - "do_statement" => make_named_chunk(node, "block".to_string(), source, fn_recurse()), + "for_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, + "for_in_statement" => { + make_candidate(node, ChunkKind::ForIn, None, NameStyle::Named, None, fn_recurse(), source) + }, + "for_of_statement" => { + make_candidate(node, ChunkKind::ForOf, None, NameStyle::Named, None, fn_recurse(), source) + }, + "while_statement" => { + make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source) + }, + "do_statement" => { + make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source) + }, // ── Blocks ── - "with_statement" => make_named_chunk(node, "block".to_string(), source, fn_recurse()), + "with_statement" => { + make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source) + }, // ── Variables ── "lexical_declaration" | "variable_declaration" => { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); if span > 1 { if let Some(name) = extract_single_declarator_name(node, source) { - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) } else { - group_candidate(node, &sanitize_node_kind(node.kind()), source) + group_from_sanitized(node, source) } } else { - group_candidate(node, &sanitize_node_kind(node.kind()), source) + group_from_sanitized(node, source) } }, // ── Fallback ── - _ => { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) - }, + _ => group_from_sanitized(node, source), } } @@ -209,11 +232,11 @@ fn classify_var_decl_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<' match value.kind() { "arrow_function" | "function_expression" | "function" => { let recurse = recurse_body(value, ChunkContext::FunctionBody); - return make_named_chunk(node, format!("fn_{name}"), source, recurse); + return make_kind_chunk(node, ChunkKind::Function, Some(name), source, recurse); }, "class" | "class_expression" => { let recurse = recurse_class(value); - return make_container_chunk(node, format!("class_{name}"), source, recurse); + return make_container_chunk(node, ChunkKind::Class, Some(name), source, recurse); }, _ => {}, } @@ -221,9 +244,15 @@ fn classify_var_decl_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<' } // Not promoted — fall back to var_NAME or group. if let Some(name) = extract_single_declarator_name(node, source) { - return make_named_chunk(node, format!("var_{name}"), source, None); + return make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None); } - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) +} + +fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let kind_name = sanitize_node_kind(node.kind()); + let kind = ChunkKind::from_sanitized_kind(kind_name.as_str()); + make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source) } // ── Export statement ───────────────────────────────────────────────────── @@ -244,9 +273,9 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid let Some(child) = inner else { // `export { foo } from "bar"` with no inner declaration node. return if is_default { - make_named_chunk(node, "default_export".to_string(), source, None) + make_kind_chunk(node, ChunkKind::DefaultExport, None, source, None) } else { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) }; }; @@ -254,34 +283,48 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid "class_declaration" => { let recurse = recurse_class(child); if is_default { - make_container_chunk_from(node, child, "default_export".to_string(), source, recurse) + make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse) } else { make_container_chunk_from( node, child, - prefixed_name("class", child, source), + ChunkKind::Class, + extract_identifier(child, source), source, recurse, ) } }, - "function_declaration" => { + "function_declaration" + | "function" + | "function_expression" + | "arrow_function" + | "generator_function" + | "generator_function_declaration" => { let recurse = recurse_body(child, ChunkContext::FunctionBody); if is_default { - make_named_chunk_from(node, child, "default_export".to_string(), source, recurse) + make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse) } else { - make_named_chunk_from(node, child, prefixed_name("fn", child, source), source, recurse) + make_kind_chunk_from( + node, + child, + ChunkKind::Function, + extract_identifier(child, source), + source, + recurse, + ) } }, "interface_declaration" => { let recurse = recurse_interface(child); if is_default { - make_container_chunk_from(node, child, "default_export".to_string(), source, recurse) + make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse) } else { make_container_chunk_from( node, child, - prefixed_name("interface", child, source), + ChunkKind::Interface, + extract_identifier(child, source), source, recurse, ) @@ -289,20 +332,28 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid }, "type_alias_declaration" => { if is_default { - make_named_chunk_from(node, child, "default_export".to_string(), source, None) + make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None) } else { - make_named_chunk_from(node, child, prefixed_name("type", child, source), source, None) + make_kind_chunk_from( + node, + child, + ChunkKind::Type, + extract_identifier(child, source), + source, + None, + ) } }, "enum_declaration" => { let recurse = recurse_enum(child); if is_default { - make_container_chunk_from(node, child, "default_export".to_string(), source, recurse) + make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse) } else { make_container_chunk_from( node, child, - prefixed_name("enum", child, source), + ChunkKind::Enum, + extract_identifier(child, source), source, recurse, ) @@ -311,12 +362,13 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid "internal_module" => { let recurse = recurse_internal_module(child); if is_default { - make_container_chunk_from(node, child, "default_export".to_string(), source, recurse) + make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse) } else { make_container_chunk_from( node, child, - prefixed_name("mod", child, source), + ChunkKind::Module, + extract_identifier(child, source), source, recurse, ) @@ -324,7 +376,7 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid }, "lexical_declaration" | "variable_declaration" => { if is_default { - make_named_chunk_from(node, child, "default_export".to_string(), source, None) + make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None) } else { promote_assigned_expression(node, child, source) .unwrap_or_else(|| super::defaults::classify_var_decl(child, source)) @@ -333,16 +385,14 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid _ => { // expression_statement, re-exports, or anything else. if is_default { - make_named_chunk_from(node, child, "default_export".to_string(), source, None) + make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None) } else { - group_candidate(child, "stmts", source) + group_candidate(child, ChunkKind::Statements, source) } }, } } -// ── Decorated definition ───────────────────────────────────────────────── - /// Unwrap `@decorator` wrappers (TS/Python `decorated_definition`) to find /// the inner class or function definition. fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { @@ -356,16 +406,22 @@ fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> match inner { Some(child) if child.kind() == "class_declaration" || child.kind() == "class_definition" => { let recurse = recurse_class(child); - make_container_chunk(node, prefixed_name("class", child, source), source, recurse) + make_container_chunk( + node, + ChunkKind::Class, + extract_identifier(child, source), + source, + recurse, + ) }, Some(child) => { // function_declaration | function_definition let name = extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("fn_{name}"), source, { + make_kind_chunk(node, ChunkKind::Function, Some(name), source, { let context = ChunkContext::FunctionBody; recurse_into(child, context, &["body"], &["block"]) }) }, - None => positional_candidate(node, "block", source), + None => positional_candidate(node, ChunkKind::Block, source), } } diff --git a/crates/pi-natives/src/chunk/ast_just.rs b/crates/pi-natives/src/chunk/ast_just.rs index 6e9c9b8c9..5e6d16497 100644 --- a/crates/pi-natives/src/chunk/ast_just.rs +++ b/crates/pi-natives/src/chunk/ast_just.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct JustClassifier; @@ -42,17 +42,18 @@ fn classify_just_root_node<'t>(node: Node<'t>, source: &str) -> Option { let name = extract_setting_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("setting_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Setting, Some(name), source, None) }, "alias" => { let name = extract_alias_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("alias_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Alias, Some(name), source, None) }, "recipe" => { let name = extract_recipe_name(node, source).unwrap_or_else(|| "anonymous".to_string()); make_container_chunk( node, - format!("recipe_{name}"), + ChunkKind::Recipe, + Some(name), source, recurse_into(node, ChunkContext::FunctionBody, &[], &["recipe_body"]), ) @@ -65,8 +66,8 @@ fn classify_just_body_node<'t>(node: Node<'t>, source: &str) -> Option group_candidate(node, "cmd", source), - "shebang" => make_named_chunk(node, "shebang".to_string(), source, None), + "recipe_line" => group_candidate(node, ChunkKind::Cmd, source), + "shebang" => make_kind_chunk(node, ChunkKind::Shebang, None, source, None), _ => return None, }) } diff --git a/crates/pi-natives/src/chunk/ast_markup.rs b/crates/pi-natives/src/chunk/ast_markup.rs index 52cfc3e1e..0c54d0f2d 100644 --- a/crates/pi-natives/src/chunk/ast_markup.rs +++ b/crates/pi-natives/src/chunk/ast_markup.rs @@ -2,36 +2,38 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct MarkupClassifier; impl MarkupClassifier { fn classify_section<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = extract_markdown_heading(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_container_chunk( + force_container(make_container_chunk( node, - format!("section_{name}"), + ChunkKind::Section, + Some(name), source, Some(recurse_self(node, ChunkContext::ClassBody)), - ) + )) } fn classify_block_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = extract_glimmer_block_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_container_chunk( + force_container(make_container_chunk( node, - format!("block_{name}"), + ChunkKind::Block, + Some(name), source, Some(recurse_self(node, ChunkContext::ClassBody)), - ) + )) } fn classify_mustache_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = extract_glimmer_mustache_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("mustache_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Mustache, Some(name), source, None) } /// Classify HTML-like element nodes that appear inside handlebars blocks. @@ -40,14 +42,15 @@ impl MarkupClassifier { "element" | "script_element" | "style_element" | "element_node" => { let name = extract_element_tag_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - Some(make_container_chunk( + Some(force_container(make_container_chunk( node, - format!("tag_{name}"), + ChunkKind::Tag, + Some(name), source, Some(recurse_self(node, ChunkContext::ClassBody)), - )) + ))) }, - "text_node" => Some(group_candidate(node, "text", source)), + "text_node" => Some(group_candidate(node, ChunkKind::Text, source)), _ => None, } } @@ -87,6 +90,11 @@ impl LangClassifier for MarkupClassifier { } } +const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> { + candidate.force_recurse = true; + candidate +} + /// Extract heading text from a Markdown `section` node's `atx_heading` or /// `setext_heading` child. fn extract_markdown_heading(node: Node<'_>, source: &str) -> Option { diff --git a/crates/pi-natives/src/chunk/ast_misc.rs b/crates/pi-natives/src/chunk/ast_misc.rs index 2ab68a426..f57d1a525 100644 --- a/crates/pi-natives/src/chunk/ast_misc.rs +++ b/crates/pi-natives/src/chunk/ast_misc.rs @@ -6,10 +6,23 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*, defaults::classify_var_decl}; +use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind}; pub struct MiscClassifier; +fn sanitized_group_candidate<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let sanitized = sanitize_node_kind(node.kind()); + let kind = ChunkKind::from_sanitized_kind(sanitized.as_str()); + // For unknown kinds that fall back to `Chunk`, preserve the original + // tree-sitter kind as the identifier so the path stays informative. + let identifier = if kind == ChunkKind::Chunk { + Some(sanitized) + } else { + None + }; + make_candidate(node, kind, identifier, NameStyle::Group, None, None, source) +} + impl LangClassifier for MiscClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { let fn_recurse = || { @@ -37,21 +50,21 @@ impl LangClassifier for MiscClassifier { | "import_list" | "import_header" | "package_header" - | "package_declaration" => group_candidate(node, "imports", source), + | "package_declaration" => group_candidate(node, ChunkKind::Imports, source), // ── Variables / assignments ── "lexical_declaration" | "variable_declaration" => classify_var_decl(node, source), "const_declaration" | "var_declaration" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("var_{name}"), source, None), - None => group_candidate(node, "decls", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None), + None => group_candidate(node, ChunkKind::Declarations, source), }, "assignment" | "property_declaration" | "state_variable_declaration" => { - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) }, // ── Statements ── "expression_statement" | "global_statement" | "command" | "pipeline" | "function_call" => { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) }, // ── Functions ── @@ -59,61 +72,74 @@ impl LangClassifier for MiscClassifier { | "function_definition" | "procedure_declaration" | "overloaded_procedure_declaration" - | "test_declaration" => named_candidate(node, "fn", source, fn_recurse()), - "method_declaration" => { - named_candidate(node, "meth", source, recurse_body(node, ChunkContext::FunctionBody)) - }, + | "test_declaration" => named_candidate(node, ChunkKind::Function, source, fn_recurse()), + "method_declaration" => named_candidate( + node, + ChunkKind::Method, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), "constructor_definition" | "constructor_declaration" | "secondary_constructor" | "init_declaration" - | "fallback_receive_definition" => make_named_chunk( + | "fallback_receive_definition" => make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), ), // ── Containers ── "class_declaration" | "class_definition" => { - container_candidate(node, "class", source, recurse_class(node)) + container_candidate(node, ChunkKind::Class, source, recurse_class(node)) }, "interface_declaration" | "protocol_declaration" => { - container_candidate(node, "iface", source, recurse_interface(node)) + container_candidate(node, ChunkKind::Iface, source, recurse_interface(node)) }, "struct_declaration" | "object_declaration" => { - container_candidate(node, "struct", source, recurse_class(node)) + container_candidate(node, ChunkKind::Struct, source, recurse_class(node)) }, "enum_declaration" | "enum_definition" => { - container_candidate(node, "enum", source, recurse_enum(node)) + container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)) }, "trait_definition" | "class" => { - container_candidate(node, "trait", source, recurse_class(node)) + container_candidate(node, ChunkKind::Trait, source, recurse_class(node)) }, "contract_declaration" | "library_declaration" | "trait_declaration" => { - container_candidate(node, "contract", source, recurse_class(node)) + container_candidate(node, ChunkKind::Contract, source, recurse_class(node)) }, "namespace_declaration" | "namespace_definition" | "module_definition" - | "extension_definition" => container_candidate(node, "mod", source, module_recurse()), + | "extension_definition" => { + container_candidate(node, ChunkKind::Module, source, module_recurse()) + }, // ── Types / aliases ── "type_alias_declaration" | "const_type_declaration" | "opaque_declaration" => { - named_candidate(node, "type", source, recurse_class(node)) + named_candidate(node, ChunkKind::Type, source, recurse_class(node)) }, // ── Macros ── - "macro_definition" | "modifier_definition" => { - named_candidate(node, "macro", source, recurse_body(node, ChunkContext::FunctionBody)) - }, + "macro_definition" | "modifier_definition" => named_candidate( + node, + ChunkKind::Macro, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), // ── Systems (Verilog etc.) ── "covergroup_declaration" | "checker_declaration" => { - container_candidate(node, "group", source, recurse_class(node)) + container_candidate(node, ChunkKind::Group, source, recurse_class(node)) + }, + "module_declaration" => { + container_candidate(node, ChunkKind::Module, source, recurse_class(node)) + }, + "union_declaration" => { + container_candidate(node, ChunkKind::Union, source, recurse_class(node)) }, - "module_declaration" => container_candidate(node, "mod", source, recurse_class(node)), - "union_declaration" => container_candidate(node, "union", source, recurse_class(node)), // ── Control flow at top level → delegate to function-level ── "if_statement" @@ -145,9 +171,10 @@ impl LangClassifier for MiscClassifier { "constructor" | "constructor_declaration" | "secondary_constructor" - | "init_declaration" => make_named_chunk( + | "init_declaration" => make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), ), @@ -166,16 +193,18 @@ impl LangClassifier for MiscClassifier { | "singleton_method" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); if name == "constructor" { - make_named_chunk( + make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), ) } else { - make_named_chunk( + make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), ) @@ -193,8 +222,8 @@ impl LangClassifier for MiscClassifier { | "const_declaration" | "constant_declaration" | "event_field_declaration" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }, // ── Enum variants ── @@ -203,27 +232,27 @@ impl LangClassifier for MiscClassifier { | "enum_constant" | "enum_entry" | "enum_variant" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None), - None => group_candidate(node, "variants", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None), + None => group_candidate(node, ChunkKind::Variants, source), }, // ── Other fields ── "field_declaration" | "embedded_field" | "container_field" | "binding" => { match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), } }, // ── Method specs ── - "method_spec" => named_candidate(node, "meth", source, None), + "method_spec" => named_candidate(node, ChunkKind::Method, source, None), // ── Field / method lists ── - "field_declaration_list" => group_candidate(node, "fields", source), - "method_spec_list" => group_candidate(node, "methods", source), + "field_declaration_list" => group_candidate(node, ChunkKind::Fields, source), + "method_spec_list" => group_candidate(node, ChunkKind::Methods, source), // ── Static initializer ── - "class_static_block" => make_named_chunk(node, "static_init".to_string(), source, None), + "class_static_block" => make_kind_chunk(node, ChunkKind::StaticInit, None, source, None), // ── Decorated definitions ── "decorated_definition" => { @@ -233,7 +262,7 @@ impl LangClassifier for MiscClassifier { if let Some(child) = inner { let name = extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk(node, format!("fn_{name}"), source, { + make_kind_chunk(node, ChunkKind::Function, Some(name), source, { let context = ChunkContext::FunctionBody; recurse_into(child, context, &["body"], &["block"]) }) @@ -248,15 +277,15 @@ impl LangClassifier for MiscClassifier { | "attribute" | "pair" | "block_mapping_pair" - | "flow_pair" => group_candidate(node, "fields", source), + | "flow_pair" => group_candidate(node, ChunkKind::Fields, source), // ── Types inside classes ── "type_item" | "type_alias_declaration" | "type_alias" => { - named_candidate(node, "type", source, None) + named_candidate(node, ChunkKind::Type, source, None) }, // ── Const / macro inside classes ── - "const_item" | "macro_invocation" => group_candidate(node, "fields", source), + "const_item" | "macro_invocation" => group_candidate(node, ChunkKind::Fields, source), _ => return None, }) @@ -266,15 +295,9 @@ impl LangClassifier for MiscClassifier { let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); Some(match node.kind() { // ── Control flow: conditionals ── - "if_statement" | "unless" | "guard_statement" => make_candidate( - node, - "if".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), + "if_statement" | "unless" | "guard_statement" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, // ── Control flow: switches ── "switch_statement" @@ -287,87 +310,72 @@ impl LangClassifier for MiscClassifier { | "receive_statement" | "yul_switch_statement" => make_candidate( node, - "switch".to_string(), + ChunkKind::Switch, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), // ── Control flow: try/catch ── "try_statement" | "try_block" | "catch_clause" | "finally_clause" - | "assembly_statement" => make_candidate( - node, - "try".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), + | "assembly_statement" => { + make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source) + }, // ── Loops: for variants (with Python-like check) ── "for_statement" | "for_in_statement" | "for_of_statement" => { - let name = if looks_like_python_statement(node, source) { - "loop".to_string() + let kind = if looks_like_python_statement(node, source) { + ChunkKind::Loop } else { - sanitize_node_kind(node.kind()) + match node.kind() { + "for_statement" => ChunkKind::For, + "for_in_statement" => ChunkKind::ForIn, + "for_of_statement" => ChunkKind::ForOf, + _ => unreachable!(), + } }; - make_candidate(node, name, NameStyle::Named, None, fn_recurse(), false, source) + make_candidate(node, kind, None, NameStyle::Named, None, fn_recurse(), source) }, // ── Loops: while ── "while_statement" => { - let name = if looks_like_python_statement(node, source) { - "loop" + let kind = if looks_like_python_statement(node, source) { + ChunkKind::Loop } else { - "while" + ChunkKind::While }; - make_candidate( - node, - name.to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ) + make_candidate(node, kind, None, NameStyle::Named, None, fn_recurse(), source) }, // ── Blocks ── "do_statement" | "with_statement" | "do_block" | "subshell" | "async_block" | "unsafe_block" | "const_block" | "block_expression" => make_candidate( node, - "block".to_string(), + ChunkKind::Block, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), // ── Loops: foreach ── - "foreach_statement" => make_candidate( - node, - "for".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), + "foreach_statement" => { + make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source) + }, // ── Statements ── "defer_statement" | "go_statement" | "send_statement" => { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) }, // ── Positional candidates ── - "elif_clause" => positional_candidate(node, "elif", source), - "except_clause" => positional_candidate(node, "except", source), - "when_statement" => positional_candidate(node, "when", source), - "match_expression" | "match_block" => positional_candidate(node, "match", source), + "elif_clause" => positional_candidate(node, ChunkKind::Elif, source), + "except_clause" => positional_candidate(node, ChunkKind::Except, source), + "when_statement" => positional_candidate(node, ChunkKind::When, source), + "match_expression" | "match_block" => positional_candidate(node, ChunkKind::Match, source), // ── Loops / misc expressions ── "loop_expression" @@ -378,7 +386,7 @@ impl LangClassifier for MiscClassifier { | "nosuspend_statement" | "suspend_statement" | "yul_if_statement" - | "yul_for_statement" => positional_candidate(node, "loop", source), + | "yul_for_statement" => positional_candidate(node, ChunkKind::Loop, source), // ── Variable declarations ── "lexical_declaration" @@ -390,14 +398,12 @@ impl LangClassifier for MiscClassifier { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); if span > 1 { if let Some(name) = extract_single_declarator_name(node, source) { - make_named_chunk(node, format!("var_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + sanitized_group_candidate(node, source) } } else { - let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + sanitized_group_candidate(node, source) } }, diff --git a/crates/pi-natives/src/chunk/ast_nix_hcl.rs b/crates/pi-natives/src/chunk/ast_nix_hcl.rs index 25f884f50..7c226b76a 100644 --- a/crates/pi-natives/src/chunk/ast_nix_hcl.rs +++ b/crates/pi-natives/src/chunk/ast_nix_hcl.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct NixHclClassifier; @@ -59,16 +59,21 @@ fn recurse_nix_binding_value(node: Node<'_>) -> Option> { fn classify_nix_binding<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = extract_nix_binding_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - let chunk_name = format!("attr_{name}"); let expression = node.child_by_field_name("expression"); if let Some(expression) = expression && matches!( expression.kind(), "attrset_expression" | "let_attrset_expression" | "rec_attrset_expression" ) { - return make_container_chunk(node, chunk_name, source, recurse_nix_attrset(expression)); + return make_container_chunk( + node, + ChunkKind::Attr, + Some(name), + source, + recurse_nix_attrset(expression), + ); } - make_named_chunk(node, chunk_name, source, recurse_nix_binding_value(node)) + make_kind_chunk(node, ChunkKind::Attr, Some(name), source, recurse_nix_binding_value(node)) } impl LangClassifier for NixHclClassifier { @@ -77,11 +82,14 @@ impl LangClassifier for NixHclClassifier { // Nix top-level attrsets should recurse into their binding_set so the file exposes // structural attr chunks instead of a single opaque attrset_expr leaf. "attrset_expression" | "let_attrset_expression" | "rec_attrset_expression" => { - Some(make_container_chunk( + Some(make_candidate( node, - sanitize_node_kind(node.kind()), - source, + ChunkKind::Attrs, + None, + NameStyle::Named, + signature_for_node(node, source), recurse_nix_attrset(node), + source, )) }, // Older tree-sitter-nix revisions used `attribute`; current grammars expose `binding`. @@ -91,29 +99,44 @@ impl LangClassifier for NixHclClassifier { if let Some(name) = extract_hcl_block_name(node, source) { Some(make_container_chunk( node, - format!("block_{name}"), + ChunkKind::Block, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &[], &["body"]), )) } else { - Some(group_candidate(node, "hunks", source)) + Some(group_candidate(node, ChunkKind::Hunks, source)) } }, // Nix expressions - "function_expression" | "let_expression" => { - Some(named_candidate(node, "expr", source, recurse_value_container(node))) - }, + "function_expression" | "let_expression" => Some(named_candidate( + node, + ChunkKind::Expression, + source, + recurse_value_container(node), + )), // Nix inherit - "inherit" => Some(group_candidate(node, "imports", source)), + "inherit" => Some(group_candidate(node, ChunkKind::Imports, source)), // Variable/assignment declarations - "variable_declaration" | "assignment" => Some(group_candidate(node, "decls", source)), + "variable_declaration" | "assignment" => { + Some(group_candidate(node, ChunkKind::Declarations, source)) + }, // HCL top-level block types "provider" | "resource" | "data" | "locals" | "variable" | "output" | "module" => { - Some(container_candidate( + let kind = match node.kind() { + "locals" => ChunkKind::BlockLocals, + "variable" => ChunkKind::Variable, + "module" => ChunkKind::Module, + _ => ChunkKind::Block, + }; + Some(make_candidate( node, - sanitize_node_kind(node.kind()).as_str(), - source, + kind, + prefixed_name(sanitize_node_kind(node.kind()).as_str(), node, source), + NameStyle::Named, + signature_for_node(node, source), recurse_into(node, ChunkContext::ClassBody, &[], &["body"]), + source, )) }, _ => None, @@ -126,18 +149,22 @@ impl LangClassifier for NixHclClassifier { "block" => extract_hcl_block_name(node, source).map(|name| { make_container_chunk( node, - format!("block_{name}"), + ChunkKind::Block, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &[], &["body"]), ) }), // Nested Nix attrset values recurse into their binding_set just like top-level ones. "attrset_expression" | "let_attrset_expression" | "rec_attrset_expression" => { - Some(make_container_chunk( + Some(make_candidate( node, - sanitize_node_kind(node.kind()), - source, + ChunkKind::Attrs, + None, + NameStyle::Named, + signature_for_node(node, source), recurse_nix_attrset(node), + source, )) }, // Nested Nix binding @@ -149,8 +176,8 @@ impl LangClassifier for NixHclClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { // Nix control flow - "if_expression" => Some(positional_candidate(node, "if", source)), - "let_expression" => Some(positional_candidate(node, "block", source)), + "if_expression" => Some(positional_candidate(node, ChunkKind::If, source)), + "let_expression" => Some(positional_candidate(node, ChunkKind::Block, source)), _ => None, } } diff --git a/crates/pi-natives/src/chunk/ast_ocaml.rs b/crates/pi-natives/src/chunk/ast_ocaml.rs index 7b03d2d78..ef6d8f240 100644 --- a/crates/pi-natives/src/chunk/ast_ocaml.rs +++ b/crates/pi-natives/src/chunk/ast_ocaml.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct OcamlClassifier; @@ -13,22 +13,24 @@ impl LangClassifier for OcamlClassifier { fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { - "method_definition" => Some(make_named_chunk( + "method_definition" => Some(make_kind_chunk( node, - format!("fn_{}", ocaml_named_text(node, source, &["method_name"])?), + ChunkKind::Function, + ocaml_named_text(node, source, &["method_name"]), source, ocaml_method_recurse(node), )), - "method_specification" => Some(make_named_chunk( + "method_specification" => Some(make_kind_chunk( node, - format!("fn_{}", ocaml_named_text(node, source, &["method_name"])?), + ChunkKind::Function, + ocaml_named_text(node, source, &["method_name"]), source, None, )), "instance_variable_definition" => { Some(match ocaml_named_text(node, source, &["instance_variable_name"]) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }) }, _ => classify_ocaml_item(node, source), @@ -37,23 +39,32 @@ impl LangClassifier for OcamlClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { - "function_expression" | "match_expression" => Some(make_named_chunk( + "function_expression" | "match_expression" => Some(make_candidate( node, - "match".to_string(), - source, + ChunkKind::Match, + None, + NameStyle::Named, + signature_for_node(node, source), Some(recurse_self(node, ChunkContext::FunctionBody)), + source, )), - "match_case" => Some(make_named_chunk( + "match_case" => Some(make_candidate( node, - "case".to_string(), - source, + ChunkKind::Case, + None, + NameStyle::Named, + signature_for_node(node, source), Some(recurse_self(node, ChunkContext::FunctionBody)), + source, )), - "let_expression" => Some(make_named_chunk( + "let_expression" => Some(make_candidate( node, - "let".to_string(), - source, + ChunkKind::Let, + None, + NameStyle::Named, + signature_for_node(node, source), Some(recurse_self(node, ChunkContext::FunctionBody)), + source, )), _ => classify_ocaml_item(node, source), } @@ -62,47 +73,60 @@ impl LangClassifier for OcamlClassifier { fn classify_ocaml_item<'t>(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "open_module" => group_candidate(node, "imports", source), + "open_module" => group_candidate(node, ChunkKind::Imports, source), "module_definition" => make_container_chunk( node, - format!("mod_{}", ocaml_named_text(node, source, &["module_name"])?), + ChunkKind::Module, + ocaml_named_text(node, source, &["module_name"]), source, ocaml_module_recurse(node), ), - "module_type_definition" => make_container_chunk( + "module_type_definition" => make_candidate( node, + ChunkKind::Interface, format!("modtype_{}", ocaml_named_text(node, source, &["module_type_name"])?), - source, + NameStyle::Named, + signature_for_node(node, source), ocaml_module_type_recurse(node), + source, ), "class_definition" => make_container_chunk( node, - format!("class_{}", ocaml_named_text(node, source, &["class_name"])?), + ChunkKind::Class, + ocaml_named_text(node, source, &["class_name"]), source, ocaml_class_recurse(node), ), - "class_type_definition" => make_container_chunk( + "class_type_definition" => make_candidate( node, + ChunkKind::Iface, format!("classtype_{}", ocaml_named_text(node, source, &["class_type_name"])?), - source, + NameStyle::Named, + signature_for_node(node, source), ocaml_class_type_recurse(node), + source, ), - "type_definition" => make_named_chunk( + "type_definition" => make_kind_chunk( node, - format!("type_{}", ocaml_named_text(node, source, &["type_constructor"])?), + ChunkKind::Type, + ocaml_named_text(node, source, &["type_constructor"]), source, None, ), - "exception_definition" => make_named_chunk( + "exception_definition" => make_candidate( node, + ChunkKind::Constructor, format!("exception_{}", ocaml_named_text(node, source, &["constructor_name"])?), - source, + NameStyle::Named, + signature_for_node(node, source), None, + source, ), "value_definition" => classify_ocaml_value_definition(node, source)?, - "value_specification" => make_named_chunk( + "value_specification" => make_kind_chunk( node, - format!("val_{}", ocaml_named_text(node, source, &["value_name"])?), + ChunkKind::Val, + ocaml_named_text(node, source, &["value_name"]), source, None, ), @@ -117,9 +141,9 @@ fn classify_ocaml_value_definition<'t>( let name = ocaml_named_text(node, source, &["value_name"])?; let recurse = ocaml_value_recurse(node); if ocaml_value_definition_is_function(node) { - Some(make_named_chunk(node, format!("fn_{name}"), source, recurse)) + Some(make_kind_chunk(node, ChunkKind::Function, Some(name), source, recurse)) } else { - Some(make_named_chunk(node, format!("val_{name}"), source, recurse)) + Some(make_kind_chunk(node, ChunkKind::Val, Some(name), source, recurse)) } } diff --git a/crates/pi-natives/src/chunk/ast_perl.rs b/crates/pi-natives/src/chunk/ast_perl.rs index 8c3adb916..5f07b2f98 100644 --- a/crates/pi-natives/src/chunk/ast_perl.rs +++ b/crates/pi-natives/src/chunk/ast_perl.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct PerlClassifier; @@ -25,30 +25,22 @@ fn classify_perl_node<'t>(node: Node<'t>, source: &str) -> Option { - make_named_chunk(node, format!("mod_{}", perl_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Module, Some(perl_name(node, source)?), source, None) }, - "use_statement" => group_candidate(node, "imports", source), - "subroutine_declaration_statement" => { - make_named_chunk(node, format!("fn_{}", perl_name(node, source)?), source, body_recurse()) + "use_statement" => group_candidate(node, ChunkKind::Imports, source), + "subroutine_declaration_statement" => make_kind_chunk( + node, + ChunkKind::Function, + Some(perl_name(node, source)?), + source, + body_recurse(), + ), + "conditional_statement" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, body_recurse(), source) + }, + "for_statement" | "loop_statement" => { + make_candidate(node, ChunkKind::Loop, None, NameStyle::Named, None, body_recurse(), source) }, - "conditional_statement" => make_candidate( - node, - "if".to_string(), - NameStyle::Named, - None, - body_recurse(), - false, - source, - ), - "for_statement" | "loop_statement" => make_candidate( - node, - "loop".to_string(), - NameStyle::Named, - None, - body_recurse(), - false, - source, - ), "expression_statement" => classify_perl_statement(node, source), _ => return None, }) @@ -56,9 +48,9 @@ fn classify_perl_node<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { if perl_declares_variable(node) { - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) } else { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) } } diff --git a/crates/pi-natives/src/chunk/ast_powershell.rs b/crates/pi-natives/src/chunk/ast_powershell.rs index 4ca68f68b..ededa99b4 100644 --- a/crates/pi-natives/src/chunk/ast_powershell.rs +++ b/crates/pi-natives/src/chunk/ast_powershell.rs @@ -2,29 +2,32 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct PowershellClassifier; impl LangClassifier for PowershellClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "param_block" => group_candidate(node, "params", source), + "param_block" => group_candidate(node, ChunkKind::Parameters, source), "statement_list" => make_container_chunk( node, - "body".to_string(), + ChunkKind::Body, + None, source, Some(recurse_self(node, ChunkContext::Root)), ), "class_statement" => make_container_chunk( node, - format!("class_{}", powershell_name(node, source)?), + ChunkKind::Class, + Some(powershell_name(node, source)?), source, Some(recurse_self(node, ChunkContext::ClassBody)), ), - "function_statement" => make_named_chunk( + "function_statement" => make_container_chunk( node, - format!("fn_{}", powershell_name(node, source)?), + ChunkKind::Function, + Some(powershell_name(node, source)?), source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), @@ -32,7 +35,7 @@ impl LangClassifier for PowershellClassifier { "switch_statement" | "if_statement" | "foreach_statement" => { return self.classify_function(node, source); }, - "flow_control_statement" => group_candidate(node, "stmts", source), + "flow_control_statement" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) } @@ -40,8 +43,8 @@ impl LangClassifier for PowershellClassifier { fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { "class_property_definition" => match powershell_name(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }, "class_method_definition" => classify_class_method(node, source)?, _ => return None, @@ -50,51 +53,60 @@ impl LangClassifier for PowershellClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "class_method_parameter_list" | "param_block" => group_candidate(node, "params", source), + "class_method_parameter_list" | "param_block" => { + group_candidate(node, ChunkKind::Parameters, source) + }, "script_block" => make_container_chunk( node, - block_name_for_parent(node).to_string(), + block_kind_for_parent(node), + None, source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), "script_block_body" | "statement_block" => make_container_chunk( node, - "block".to_string(), + ChunkKind::Block, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_list"]), ), "pipeline" => classify_powershell_pipeline(node, source), - "if_statement" => make_named_chunk( + "if_statement" => make_container_chunk( node, - "if".to_string(), + ChunkKind::If, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]), ), - "foreach_statement" => make_named_chunk( + "foreach_statement" => make_container_chunk( node, - "loop".to_string(), + ChunkKind::Loop, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]), ), - "switch_statement" => make_named_chunk( + "switch_statement" => make_container_chunk( node, - "switch".to_string(), + ChunkKind::Switch, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["switch_body"]), ), "switch_clauses" => make_container_chunk( node, - "cases".to_string(), + ChunkKind::Cases, + None, source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), - "switch_clause" => make_named_chunk( + "switch_clause" => make_container_chunk( node, - "case".to_string(), + ChunkKind::Case, + None, source, recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]), ), - "flow_control_statement" => group_candidate(node, "stmts", source), + "flow_control_statement" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) } @@ -107,15 +119,16 @@ impl LangClassifier for PowershellClassifier { fn classify_class_method<'t>(node: Node<'t>, source: &str) -> Option> { let name = powershell_name(node, source)?; let class_name = powershell_name(node.parent()?, source)?; - let chunk_name = if name == "new" || name == class_name { - "constructor".to_string() + let (kind, identifier) = if name == "new" || name == class_name { + (ChunkKind::Constructor, None) } else { - format!("fn_{name}") + (ChunkKind::Function, Some(name)) }; - Some(make_named_chunk( + Some(make_container_chunk( node, - chunk_name, + kind, + identifier, source, Some(recurse_self(node, ChunkContext::FunctionBody)), )) @@ -125,23 +138,24 @@ fn classify_powershell_pipeline<'t>(node: Node<'t>, source: &str) -> RawChunkCan if let Some(command_name) = powershell_command_name(node, source) && matches!(command_name.as_str(), "using" | "using-module" | "Import-Module") { - return group_candidate(node, "imports", source); + return group_candidate(node, ChunkKind::Imports, source); } if let Some((name, script_block)) = assigned_script_block(node, source) { return make_container_chunk_from( node, node, - format!("block_{name}"), + ChunkKind::Block, + Some(name), source, Some(recurse_self(script_block, ChunkContext::FunctionBody)), ); } if child_by_kind(node, &["assignment_expression"]).is_some() { - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) } else { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) } } @@ -170,10 +184,10 @@ fn find_script_block(node: Node<'_>) -> Option> { None } -fn block_name_for_parent(node: Node<'_>) -> &'static str { +fn block_kind_for_parent(node: Node<'_>) -> ChunkKind { match node.parent().map(|parent| parent.kind()) { - Some("function_statement" | "class_method_definition") => "body", - _ => "block", + Some("function_statement" | "class_method_definition") => ChunkKind::Body, + _ => ChunkKind::Block, } } diff --git a/crates/pi-natives/src/chunk/ast_proto.rs b/crates/pi-natives/src/chunk/ast_proto.rs index 0d1ba8510..32d803668 100644 --- a/crates/pi-natives/src/chunk/ast_proto.rs +++ b/crates/pi-natives/src/chunk/ast_proto.rs @@ -7,7 +7,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct ProtoClassifier; @@ -23,23 +23,26 @@ impl LangClassifier for ProtoClassifier { fn classify_proto_root<'t>(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "syntax" | "package" => group_candidate(node, "headers", source), - "import" => group_candidate(node, "imports", source), - "option" => group_candidate(node, "options", source), - "message" => make_container_chunk( + "syntax" | "package" => group_candidate(node, ChunkKind::Headers, source), + "import" => group_candidate(node, ChunkKind::Imports, source), + "option" => group_candidate(node, ChunkKind::Options, source), + "message" => make_named_proto_chunk( node, + ChunkKind::Type, format!("msg_{}", proto_name(node, source)?), source, recurse_into(node, ChunkContext::ClassBody, &[], &["message_body"]), ), "enum" => make_container_chunk( node, - format!("enum_{}", proto_name(node, source)?), + ChunkKind::Enum, + proto_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["enum_body"]), ), - "service" => make_container_chunk( + "service" => make_named_proto_chunk( node, + ChunkKind::Interface, format!("service_{}", proto_name(node, source)?), source, Some(recurse_self(node, ChunkContext::ClassBody)), @@ -50,36 +53,40 @@ fn classify_proto_root<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "option" => group_candidate(node, "options", source), + "option" => group_candidate(node, ChunkKind::Options, source), "field" if is_proto_message_field(node) => { - make_named_chunk(node, format!("field_{}", proto_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Field, proto_name(node, source), source, None) }, - "oneof" => make_container_chunk( + "oneof" => make_named_proto_chunk( node, + ChunkKind::Either, format!("oneof_{}", proto_name(node, source)?), source, Some(recurse_self(node, ChunkContext::ClassBody)), ), "oneof_field" => { - make_named_chunk(node, format!("field_{}", proto_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Field, proto_name(node, source), source, None) }, - "message" => make_container_chunk( + "message" => make_named_proto_chunk( node, + ChunkKind::Type, format!("msg_{}", proto_name(node, source)?), source, recurse_into(node, ChunkContext::ClassBody, &[], &["message_body"]), ), "enum" => make_container_chunk( node, - format!("enum_{}", proto_name(node, source)?), + ChunkKind::Enum, + proto_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["enum_body"]), ), "enum_field" => { - make_named_chunk(node, format!("variant_{}", proto_name(node, source)?), source, None) + make_kind_chunk(node, ChunkKind::Variant, proto_name(node, source), source, None) }, - "rpc" => make_container_chunk( + "rpc" => make_named_proto_chunk( node, + ChunkKind::Proc, format!("rpc_{}", proto_name(node, source)?), source, proto_rpc_recurse(node), @@ -88,6 +95,24 @@ fn classify_proto_class<'t>(node: Node<'t>, source: &str) -> Option( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + recurse, + source, + ) +} + fn is_proto_message_field(node: Node<'_>) -> bool { node .parent() diff --git a/crates/pi-natives/src/chunk/ast_python.rs b/crates/pi-natives/src/chunk/ast_python.rs index 960fe0b2d..79ab9c52a 100644 --- a/crates/pi-natives/src/chunk/ast_python.rs +++ b/crates/pi-natives/src/chunk/ast_python.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct PythonClassifier; @@ -11,16 +11,17 @@ impl LangClassifier for PythonClassifier { match node.kind() { // ── Imports ── "import_statement" | "import_from_statement" => { - Some(group_candidate(node, "imports", source)) + Some(group_candidate(node, ChunkKind::Imports, source)) }, // ── Variables / assignments ── - "assignment" => Some(group_candidate(node, "decls", source)), + "assignment" => Some(group_candidate(node, ChunkKind::Declarations, source)), // ── Functions ── - "function_definition" => Some(make_named_chunk( + "function_definition" => Some(make_kind_chunk( node, - prefixed_name("fn", node, source), + ChunkKind::Function, + extract_identifier(node, source), source, recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"]), )), @@ -28,7 +29,8 @@ impl LangClassifier for PythonClassifier { // ── Containers ── "class_definition" => Some(make_container_chunk( node, - prefixed_name("class", node, source), + ChunkKind::Class, + extract_identifier(node, source), source, recurse_into(node, ChunkContext::ClassBody, &["body"], &["block"]), )), @@ -39,7 +41,7 @@ impl LangClassifier for PythonClassifier { // ── Statements ── "expression_statement" | "global_statement" => { - Some(group_candidate(node, "stmts", source)) + Some(group_candidate(node, ChunkKind::Statements, source)) }, // ── Decorated ── @@ -54,14 +56,20 @@ impl LangClassifier for PythonClassifier { // ── Methods ── "function_definition" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); - let chunk_name = if name == "__init__" || name == "__new__" { - "constructor".to_string() + let kind = if name == "__init__" || name == "__new__" { + ChunkKind::Constructor } else { - format!("fn_{name}") + ChunkKind::Function }; - Some(make_named_chunk( + let identifier = if kind == ChunkKind::Constructor { + None + } else { + Some(name) + }; + Some(make_kind_chunk( node, - chunk_name, + kind, + identifier, source, recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"]), )) @@ -75,14 +83,20 @@ impl LangClassifier for PythonClassifier { if let Some(child) = inner { let name = extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string()); - let chunk_name = if name == "__init__" || name == "__new__" { - "constructor".to_string() + let kind = if name == "__init__" || name == "__new__" { + ChunkKind::Constructor } else { - format!("fn_{name}") + ChunkKind::Function }; - Some(make_named_chunk( + let identifier = if kind == ChunkKind::Constructor { + None + } else { + Some(name) + }; + Some(make_kind_chunk( node, - chunk_name, + kind, + identifier, source, recurse_into(child, ChunkContext::FunctionBody, &["body"], &["block"]), )) @@ -92,10 +106,12 @@ impl LangClassifier for PythonClassifier { }, // ── Fields ── - "expression_statement" | "assignment" => Some(group_candidate(node, "fields", source)), + "expression_statement" | "assignment" => { + Some(group_candidate(node, ChunkKind::Fields, source)) + }, // ── Type aliases ── - "type_alias_statement" => Some(named_candidate(node, "type", source, None)), + "type_alias_statement" => Some(named_candidate(node, ChunkKind::Type, source, None)), _ => None, } @@ -106,48 +122,50 @@ impl LangClassifier for PythonClassifier { // ── Control flow ── "if_statement" => Some(make_candidate( node, - "if".to_string(), + ChunkKind::If, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), "for_statement" | "while_statement" => Some(make_candidate( node, - "loop".to_string(), + ChunkKind::Loop, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), "try_statement" => Some(make_candidate( node, - "try".to_string(), + ChunkKind::Try, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), "with_statement" => Some(make_candidate( node, - "block".to_string(), + ChunkKind::Block, + None, NameStyle::Named, None, recurse_body(node, ChunkContext::FunctionBody), - false, source, )), // ── Positional ── - "elif_clause" => Some(positional_candidate(node, "elif", source)), - "except_clause" => Some(positional_candidate(node, "except", source)), - "match_statement" => Some(positional_candidate(node, "match", source)), + "elif_clause" => Some(positional_candidate(node, ChunkKind::Elif, source)), + "except_clause" => Some(positional_candidate(node, ChunkKind::Except, source)), + "match_statement" => Some(positional_candidate(node, ChunkKind::Match, source)), // ── Variables ── - "expression_statement" | "assignment" => Some(group_candidate(node, "stmts", source)), + "expression_statement" | "assignment" => { + Some(group_candidate(node, ChunkKind::Statements, source)) + }, _ => None, } @@ -160,36 +178,18 @@ fn classify_function_python<'t>(node: Node<'t>, source: &str) -> RawChunkCandida let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); match node.kind() { "if_statement" => { - make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source) + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) }, - "for_statement" | "while_statement" => make_candidate( - node, - "loop".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "try_statement" => make_candidate( - node, - "try".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "with_statement" => make_candidate( - node, - "block".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - _ => group_candidate(node, "stmts", source), + "for_statement" | "while_statement" => { + make_candidate(node, ChunkKind::Loop, None, NameStyle::Named, None, fn_recurse(), source) + }, + "try_statement" => { + make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source) + }, + "with_statement" => { + make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source) + }, + _ => group_candidate(node, ChunkKind::Statements, source), } } @@ -200,16 +200,18 @@ fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> match inner { Some(child) if child.kind() == "class_definition" => make_container_chunk( node, - prefixed_name("class", child, source), + ChunkKind::Class, + extract_identifier(child, source), source, recurse_into(child, ChunkContext::ClassBody, &["body"], &["block"]), ), - Some(child) if child.kind() == "function_definition" => make_named_chunk( + Some(child) if child.kind() == "function_definition" => make_kind_chunk( node, - prefixed_name("fn", child, source), + ChunkKind::Function, + extract_identifier(child, source), source, recurse_into(child, ChunkContext::FunctionBody, &["body"], &["block"]), ), - _ => positional_candidate(node, "block", source), + _ => positional_candidate(node, ChunkKind::Block, source), } } diff --git a/crates/pi-natives/src/chunk/ast_r.rs b/crates/pi-natives/src/chunk/ast_r.rs index f6b832050..e4f5a24ec 100644 --- a/crates/pi-natives/src/chunk/ast_r.rs +++ b/crates/pi-natives/src/chunk/ast_r.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct RClassifier; @@ -10,21 +10,23 @@ impl LangClassifier for RClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { // ── Imports ── - "call" if is_import_call(node, source) => group_candidate(node, "imports", source), - "call" => group_candidate(node, "stmts", source), + "call" if is_import_call(node, source) => { + group_candidate(node, ChunkKind::Imports, source) + }, + "call" => group_candidate(node, ChunkKind::Statements, source), // ── Function / value assignments ── "binary_operator" => classify_assignment(node, source, ChunkScope::Root)?, // ── Control flow at script scope ── - "if_statement" => control_candidate(node, "if", source, recurse_if(node)), + "if_statement" => control_candidate(node, ChunkKind::If, source, recurse_if(node)), "for_statement" | "while_statement" | "repeat_statement" => { - control_candidate(node, "loop", source, recurse_loop(node)) + control_candidate(node, ChunkKind::Loop, source, recurse_loop(node)) }, // ── Bare expressions ── "identifier" | "subset" | "subset2" | "extract_operator" => { - group_candidate(node, "stmts", source) + group_candidate(node, ChunkKind::Statements, source) }, _ => return None, @@ -37,14 +39,14 @@ impl LangClassifier for RClassifier { "binary_operator" => classify_assignment(node, source, ChunkScope::Function)?, // ── Control flow ── - "if_statement" => control_candidate(node, "if", source, recurse_if(node)), + "if_statement" => control_candidate(node, ChunkKind::If, source, recurse_if(node)), "for_statement" | "while_statement" | "repeat_statement" => { - control_candidate(node, "loop", source, recurse_loop(node)) + control_candidate(node, ChunkKind::Loop, source, recurse_loop(node)) }, // ── Calls / bare expressions ── "call" | "identifier" | "subset" | "subset2" | "extract_operator" | "break" | "next" - | "return" => group_candidate(node, "stmts", source), + | "return" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) @@ -66,10 +68,11 @@ fn classify_assignment<'t>( if rhs.kind() == "function_definition" { let name = simple_lhs_name(lhs, source).unwrap_or_else(|| "anonymous".to_string()); - return Some(make_named_chunk_from( + return Some(make_kind_chunk_from( node, rhs, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(rhs, ChunkContext::FunctionBody), )); @@ -77,16 +80,16 @@ fn classify_assignment<'t>( match (scope, simple_lhs_name(lhs, source)) { (ChunkScope::Root, Some(name)) => { - Some(make_named_chunk(node, format!("var_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)) }, (ChunkScope::Function, Some(name)) if spans_multiple_lines(node) => { - Some(make_named_chunk(node, format!("var_{name}"), source, None)) + Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)) }, _ => Some(group_candidate( node, match scope { - ChunkScope::Root => "decls", - ChunkScope::Function => "stmts", + ChunkScope::Root => ChunkKind::Declarations, + ChunkScope::Function => ChunkKind::Statements, }, source, )), @@ -132,11 +135,11 @@ fn recurse_loop(node: Node<'_>) -> Option> { fn control_candidate<'t>( node: Node<'t>, - name: &str, + kind: ChunkKind, source: &str, recurse: Option>, ) -> RawChunkCandidate<'t> { - make_candidate(node, name.to_string(), NameStyle::Named, None, recurse, true, source) + make_candidate(node, kind, None, NameStyle::Named, None, recurse, source) } fn spans_multiple_lines(node: Node<'_>) -> bool { diff --git a/crates/pi-natives/src/chunk/ast_ruby_lua.rs b/crates/pi-natives/src/chunk/ast_ruby_lua.rs index 526336b58..e11b6892e 100644 --- a/crates/pi-natives/src/chunk/ast_ruby_lua.rs +++ b/crates/pi-natives/src/chunk/ast_ruby_lua.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct RubyLuaClassifier; @@ -20,38 +20,46 @@ impl LangClassifier for RubyLuaClassifier { let target = extract_identifier(node, source); match target.as_deref() { Some("require" | "require_relative" | "load" | "autoload") => { - group_candidate(node, "imports", source) + group_candidate(node, ChunkKind::Imports, source) }, - _ => group_candidate(node, "stmts", source), + _ => group_candidate(node, ChunkKind::Statements, source), } }, // ── Functions ── - "function_definition" => { - named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody)) - }, - "method" | "singleton_method" => { - named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody)) - }, + "function_definition" => named_candidate( + node, + ChunkKind::Function, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), + "method" | "singleton_method" => named_candidate( + node, + ChunkKind::Function, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), // ── Containers ── - "class" => container_candidate(node, "class", source, recurse_class(node)), - "module" => container_candidate(node, "mod", source, recurse_class(node)), + "class" => container_candidate(node, ChunkKind::Class, source, recurse_class(node)), + "module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)), // ── Control flow (top-level scripts) ── "if_statement" | "unless" | "while_statement" | "for_statement" => { return Some( self .classify_function(node, source) - .unwrap_or_else(|| group_candidate(node, "stmts", source)), + .unwrap_or_else(|| group_candidate(node, ChunkKind::Statements, source)), ); }, // ── Assignments ── - "assignment" => group_candidate(node, "decls", source), + "assignment" => group_candidate(node, ChunkKind::Declarations, source), // ── Statements ── - "expression_statement" | "function_call" => group_candidate(node, "stmts", source), + "expression_statement" | "function_call" => { + group_candidate(node, ChunkKind::Statements, source) + }, _ => return None, }) @@ -63,16 +71,18 @@ impl LangClassifier for RubyLuaClassifier { "method" | "singleton_method" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); if name == "initialize" { - make_named_chunk( + make_kind_chunk( node, - "constructor".to_string(), + ChunkKind::Constructor, + None, source, recurse_body(node, ChunkContext::FunctionBody), ) } else { - make_named_chunk( + make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), ) @@ -80,14 +90,14 @@ impl LangClassifier for RubyLuaClassifier { }, // ── Nested containers ── - "class" => container_candidate(node, "class", source, recurse_class(node)), - "module" => container_candidate(node, "mod", source, recurse_class(node)), + "class" => container_candidate(node, ChunkKind::Class, source, recurse_class(node)), + "module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)), // ── Fields / constants ── - "assignment" => group_candidate(node, "fields", source), + "assignment" => group_candidate(node, ChunkKind::Fields, source), // ── Calls (include, attr_reader, etc.) and bare identifiers (private) ── - "call" | "command" | "identifier" => group_candidate(node, "stmts", source), + "call" | "command" | "identifier" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) @@ -97,36 +107,30 @@ impl LangClassifier for RubyLuaClassifier { let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); Some(match node.kind() { // ── Control flow ── - "if_statement" | "unless" => make_candidate( - node, - "if".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), + "if_statement" | "unless" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, "case_statement" | "case_match" => make_candidate( node, - "switch".to_string(), + ChunkKind::Switch, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), "while_statement" | "for_statement" => make_candidate( node, - "loop".to_string(), + ChunkKind::Loop, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), // ── Variables ── - "assignment" => group_candidate(node, "stmts", source), + "assignment" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) diff --git a/crates/pi-natives/src/chunk/ast_rust.rs b/crates/pi-natives/src/chunk/ast_rust.rs index 13030a730..b596b2294 100644 --- a/crates/pi-natives/src/chunk/ast_rust.rs +++ b/crates/pi-natives/src/chunk/ast_rust.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct RustClassifier; @@ -10,56 +10,62 @@ impl LangClassifier for RustClassifier { fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { // ── Imports ── - "use_declaration" | "extern_crate_declaration" => group_candidate(node, "imports", source), + "use_declaration" | "extern_crate_declaration" => { + group_candidate(node, ChunkKind::Imports, source) + }, // ── Functions ── "function_item" | "function_definition" => named_candidate( node, - "fn", + ChunkKind::Function, source, recurse_body(node, ChunkContext::FunctionBody) .or_else(|| recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"])), ), // ── Containers ── - "struct_item" => container_candidate(node, "struct", source, recurse_class(node)), - "enum_item" => container_candidate(node, "enum", source, recurse_enum(node)), - "trait_item" => container_candidate(node, "trait", source, recurse_class(node)), + "struct_item" => container_candidate(node, ChunkKind::Struct, source, recurse_class(node)), + "enum_item" => container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)), + "trait_item" => container_candidate(node, ChunkKind::Trait, source, recurse_class(node)), "mod_item" | "foreign_block" => { - container_candidate(node, "mod", source, recurse_class(node)) + container_candidate(node, ChunkKind::Module, source, recurse_class(node)) }, "impl_item" => { let name = extract_impl_name(node, source).unwrap_or_else(|| "anonymous".to_string()); make_container_chunk( node, - format!("impl_{name}"), + ChunkKind::Impl, + Some(name), source, recurse_into(node, ChunkContext::ClassBody, &["body"], &["declaration_list"]), ) }, // ── Types ── - "type_item" => named_candidate(node, "type", source, recurse_class(node)), + "type_item" => named_candidate(node, ChunkKind::Type, source, recurse_class(node)), // ── Macros ── - "macro_definition" | "macro_rule" => { - named_candidate(node, "macro", source, recurse_body(node, ChunkContext::FunctionBody)) - }, + "macro_definition" | "macro_rule" => named_candidate( + node, + ChunkKind::Macro, + source, + recurse_body(node, ChunkContext::FunctionBody), + ), // ── Statics / consts ── - "static_item" | "const_item" => group_candidate(node, "decls", source), + "static_item" | "const_item" => group_candidate(node, ChunkKind::Declarations, source), // ── Attributes ── - "inner_attribute_item" => group_candidate(node, "attrs", source), + "inner_attribute_item" => group_candidate(node, ChunkKind::Attrs, source), // ── Variables ── "let_declaration" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("var_{name}"), source, None), - None => group_candidate(node, "decls", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None), + None => group_candidate(node, ChunkKind::Declarations, source), }, // ── Expression statements ── - "expression_statement" => group_candidate(node, "stmts", source), + "expression_statement" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) @@ -70,34 +76,35 @@ impl LangClassifier for RustClassifier { // ── Methods ── "function_item" | "function_definition" => { let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_named_chunk( + make_kind_chunk( node, - format!("fn_{name}"), + ChunkKind::Function, + Some(name), source, recurse_body(node, ChunkContext::FunctionBody), ) }, // ── Types ── - "type_item" | "type_alias" => named_candidate(node, "type", source, None), + "type_item" | "type_alias" => named_candidate(node, ChunkKind::Type, source, None), // ── Fields ── "field_declaration" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("field_{name}"), source, None), - None => group_candidate(node, "fields", source), + Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None), + None => group_candidate(node, ChunkKind::Fields, source), }, // ── Enum variants ── "enum_variant" => match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None), - None => group_candidate(node, "variants", source), + Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None), + None => group_candidate(node, ChunkKind::Variants, source), }, // ── Consts / macros in class body ── - "const_item" | "macro_invocation" => group_candidate(node, "fields", source), + "const_item" | "macro_invocation" => group_candidate(node, ChunkKind::Fields, source), // ── Attributes (absorbed by the framework, but handle explicitly) ── - "attribute_item" => return None, // absorbed by is_absorbable_attr + "attribute_item" => return None, _ => return None, }) @@ -107,28 +114,22 @@ impl LangClassifier for RustClassifier { let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody); Some(match node.kind() { // ── Control flow ── - "if_expression" => make_candidate( - node, - "if".to_string(), - NameStyle::Named, - None, - fn_recurse(), - false, - source, - ), - "match_expression" => positional_candidate(node, "match", source), + "if_expression" => { + make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source) + }, + "match_expression" => positional_candidate(node, ChunkKind::Match, source), "loop_expression" | "while_expression" | "for_expression" => { - positional_candidate(node, "loop", source) + positional_candidate(node, ChunkKind::Loop, source) }, // ── Blocks ── "unsafe_block" | "async_block" | "const_block" | "block_expression" => make_candidate( node, - "block".to_string(), + ChunkKind::Block, + None, NameStyle::Named, None, fn_recurse(), - false, source, ), @@ -137,16 +138,18 @@ impl LangClassifier for RustClassifier { let span = line_span(node.start_position().row + 1, node.end_position().row + 1); if span > 1 { match extract_identifier(node, source) { - Some(name) => make_named_chunk(node, format!("var_{name}"), source, None), - None => group_candidate(node, "let", source), + Some(name) => { + make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None) + }, + None => group_candidate(node, ChunkKind::Let, source), } } else { - group_candidate(node, "let", source) + group_candidate(node, ChunkKind::Let, source) } }, // ── Expression statements ── - "expression_statement" => group_candidate(node, "stmts", source), + "expression_statement" => group_candidate(node, ChunkKind::Statements, source), _ => return None, }) diff --git a/crates/pi-natives/src/chunk/ast_sql.rs b/crates/pi-natives/src/chunk/ast_sql.rs index 56e3c71f7..4f5e9461e 100644 --- a/crates/pi-natives/src/chunk/ast_sql.rs +++ b/crates/pi-natives/src/chunk/ast_sql.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct SqlClassifier; @@ -40,9 +40,10 @@ fn classify_sql_statement_root<'t>(node: Node<'t>, source: &str) -> Option( source: &str, ) -> Option> { Some(match node.kind() { - "create_schema" => make_named_chunk_from( + "create_schema" => make_kind_chunk_from( range_node, node, - format!( - "schema_{}", - extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Schema, + extract_sql_identifier(node, source), source, None, ), "create_table" => make_container_chunk_from( range_node, node, - format!( - "table_{}", - extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Table, + extract_sql_object_name(node, source), source, recurse_into(node, ChunkContext::ClassBody, &[], &["column_definitions"]), ), - "create_view" => make_container_chunk_from( + "create_view" => make_named_sql_chunk_from( range_node, node, + ChunkKind::Query, format!( "view_{}", extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -87,9 +85,10 @@ fn classify_sql_root_node<'t>( source, recurse_into(node, ChunkContext::FunctionBody, &[], &["create_query"]), ), - "create_materialized_view" => make_container_chunk_from( + "create_materialized_view" => make_named_sql_chunk_from( range_node, node, + ChunkKind::Query, format!( "matview_{}", extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -100,16 +99,15 @@ fn classify_sql_root_node<'t>( "create_function" => make_container_chunk_from( range_node, node, - format!( - "fn_{}", - extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), + ChunkKind::Function, + extract_sql_object_name(node, source), source, recurse_sql_function_query(node), ), - "create_trigger" => make_named_chunk_from( + "create_trigger" => make_named_sql_chunk_from( range_node, node, + ChunkKind::Function, format!( "trigger_{}", extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -117,9 +115,10 @@ fn classify_sql_root_node<'t>( source, None, ), - "create_index" => make_named_chunk_from( + "create_index" => make_named_sql_chunk_from( range_node, node, + ChunkKind::Key, format!( "index_{}", extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -133,24 +132,19 @@ fn classify_sql_root_node<'t>( fn classify_sql_class<'t>(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "column_definition" => make_named_chunk( - node, - format!( - "field_{}", - extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()) - ), - source, - None, - ), + "column_definition" => { + make_kind_chunk(node, ChunkKind::Field, extract_sql_identifier(node, source), source, None) + }, _ => return None, }) } fn classify_sql_function<'t>(node: Node<'t>, source: &str) -> Option> { if node.kind() == "statement" { - return Some(make_container_chunk( + return Some(make_named_sql_chunk( node, - "query".to_string(), + ChunkKind::Query, + None, source, Some(recurse_self(node, ChunkContext::FunctionBody)), )); @@ -161,10 +155,11 @@ fn classify_sql_function<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> Option> { Some(match node.kind() { - "insert" => group_candidate(node, "stmts", source), - "keyword_with" => group_candidate(node, "with", source), - "cte" => make_container_chunk( + "insert" => group_candidate(node, ChunkKind::Statements, source), + "keyword_with" => group_candidate(node, ChunkKind::With, source), + "cte" => make_named_sql_chunk( node, + ChunkKind::With, format!( "cte_{}", extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string()) @@ -172,22 +167,60 @@ fn classify_sql_query_node<'t>(node: Node<'t>, source: &str) -> Option positional_candidate(node, "select", source), - "from" => make_container_chunk( + "select" => positional_candidate(node, ChunkKind::Select, source), + "from" => make_named_sql_chunk( node, + ChunkKind::Query, "from".to_string(), source, Some(recurse_self(node, ChunkContext::FunctionBody)), ), - "relation" => group_candidate(node, "relations", source), - "join" => positional_candidate(node, "join", source), - "where" => positional_candidate(node, "where", source), - "group_by" => positional_candidate(node, "group_by", source), - "order_by" => positional_candidate(node, "order_by", source), + "relation" => group_candidate(node, ChunkKind::Relations, source), + "join" => positional_candidate(node, ChunkKind::Join, source), + "where" => positional_candidate(node, ChunkKind::Where, source), + "group_by" => positional_candidate(node, ChunkKind::GroupBy, source), + "order_by" => positional_candidate(node, ChunkKind::OrderBy, source), _ => return None, }) } +fn make_named_sql_chunk<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + recurse, + source, + ) +} + +fn make_named_sql_chunk_from<'t>( + range_node: Node<'t>, + signature_node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, + recurse: Option>, +) -> RawChunkCandidate<'t> { + make_candidate( + range_node, + kind, + identifier, + NameStyle::Named, + signature_for_node(signature_node, source), + recurse, + source, + ) +} + fn recurse_sql_function_query(node: Node<'_>) -> Option> { let body = child_by_kind(node, &["function_body"])?; recurse_into(body, ChunkContext::FunctionBody, &[], &["statement"]) diff --git a/crates/pi-natives/src/chunk/ast_svelte.rs b/crates/pi-natives/src/chunk/ast_svelte.rs index 0997d733e..420bdbb1e 100644 --- a/crates/pi-natives/src/chunk/ast_svelte.rs +++ b/crates/pi-natives/src/chunk/ast_svelte.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct SvelteClassifier; @@ -35,15 +35,15 @@ fn classify_svelte_node<'t>( "snippet_statement" => Some(classify_snippet_statement(node, source)), "if_statement" => Some(classify_if_statement(node, source)), "else_if_statement" => Some(classify_else_if_statement(node, source)), - "else_statement" => Some(make_block_chunk(node, "else", source)), + "else_statement" => Some(classify_else_statement(node, source)), "each_statement" => Some(classify_each_statement(node, source)), "await_statement" => Some(classify_await_statement(node, source)), "then_statement" => Some(classify_then_statement(node, source)), "catch_statement" => Some(classify_catch_statement(node, source)), "render_expr" => Some(classify_render_expr(node, source)), - "html_interpolation" => Some(group_candidate(node, "html", source)), - "interpolation" => Some(group_candidate(node, "interpolation", source)), - "expression" => Some(group_candidate(node, "expr", source)), + "html_interpolation" => Some(group_candidate(node, ChunkKind::Html, source)), + "interpolation" => Some(group_candidate(node, ChunkKind::Interpolation, source)), + "expression" => Some(group_candidate(node, ChunkKind::Expression, source)), "element" if include_plain_elements || element_has_structure(node) => { classify_element(node, source) }, @@ -52,103 +52,145 @@ fn classify_svelte_node<'t>( } fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "module", source) + let kind = if has_attribute(node, "module", source) || attribute_value(node, "context", source).as_deref() == Some("module") { - "script_module".to_string() + ChunkKind::ScriptModule } else { - "script".to_string() + ChunkKind::Script }; // The grammar exposes script contents as a single `raw_text` child, so the // element boundary is the most truthful chunk. - make_named_chunk(node, name, source, None) + positional_candidate(node, kind, source) } fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "scoped", source) { - "style_scoped".to_string() + let kind = if has_attribute(node, "scoped", source) { + ChunkKind::StyleScoped } else { - "style".to_string() + ChunkKind::Style }; - make_named_chunk(node, name, source, None) + positional_candidate(node, kind, source) } fn classify_snippet_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = child_by_kind(node, &["snippet_start_expr"]) + let identifier = child_by_kind(node, &["snippet_start_expr"]) .and_then(|start| child_by_kind(start, &["snippet_name"])) - .and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte()))) - .map_or_else(|| "snippet".to_string(), |name| format!("snippet_{name}")); - make_block_chunk(node, &name, source) -} - -fn classify_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "if_start_expr", &["raw_text_expr"], "if"); - make_block_chunk(node, &name, source) -} - -fn classify_else_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "else_if_expr", &["raw_text_expr"], "else_if"); - make_block_chunk(node, &name, source) -} - -fn classify_each_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "each_start_expr", &["raw_text_each"], "each"); - make_block_chunk(node, &name, source) -} - -fn classify_await_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "await_start_expr", &["raw_text_expr"], "await"); - make_block_chunk(node, &name, source) -} - -fn classify_then_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "then_expr", &["raw_text_expr"], "then"); - make_block_chunk(node, &name, source) -} - -fn classify_catch_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = block_expr_name(node, source, "catch_expr", &["raw_text_expr"], "catch"); - make_block_chunk(node, &name, source) -} - -fn classify_render_expr<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = child_by_kind(node, &["snippet_name"]) - .and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte()))) - .map_or_else(|| "render".to_string(), |name| format!("render_{name}")); - make_named_chunk(node, name, source, None) -} - -fn classify_element<'t>(node: Node<'t>, source: &str) -> Option> { - let tag_name = extract_markup_tag_name(node, source)?; - Some(make_container_chunk( + .and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte()))); + force_container(make_container_chunk( node, - format!("tag_{tag_name}"), + ChunkKind::Snippet, + identifier, source, Some(recurse_self(node, ChunkContext::ClassBody)), )) } -fn make_block_chunk<'t>(node: Node<'t>, name: &str, source: &str) -> RawChunkCandidate<'t> { - make_container_chunk( +fn classify_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let identifier = block_expr_identifier(node, source, "if_start_expr", &["raw_text_expr"]); + force_container(make_container_chunk( node, - name.to_string(), + ChunkKind::If, + identifier, source, Some(recurse_self(node, ChunkContext::ClassBody)), - ) + )) } -fn block_expr_name( +fn classify_else_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let identifier = block_expr_identifier(node, source, "else_if_expr", &["raw_text_expr"]) + .map_or_else(|| "if".to_string(), |expr| format!("if_{expr}")); + make_named_container_chunk(node, ChunkKind::Else, identifier, source) +} + +fn classify_else_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + force_container(make_container_chunk( + node, + ChunkKind::Else, + None, + source, + Some(recurse_self(node, ChunkContext::ClassBody)), + )) +} + +fn classify_each_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let expr = block_expr_identifier(node, source, "each_start_expr", &["raw_text_each"]); + let id = expr.map_or_else(|| "each".to_string(), |expr| format!("each_{expr}")); + make_named_container_chunk(node, ChunkKind::Loop, id, source) +} + +fn classify_await_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let expr = block_expr_identifier(node, source, "await_start_expr", &["raw_text_expr"]); + let id = expr.map_or_else(|| "await".to_string(), |expr| format!("await_{expr}")); + make_named_container_chunk(node, ChunkKind::With, id, source) +} + +fn classify_then_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let expr = block_expr_identifier(node, source, "then_expr", &["raw_text_expr"]); + let id = expr.map_or_else(|| "then".to_string(), |expr| format!("then_{expr}")); + make_named_container_chunk(node, ChunkKind::After, id, source) +} + +fn classify_catch_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let identifier = block_expr_identifier(node, source, "catch_expr", &["raw_text_expr"]); + force_container(make_container_chunk( + node, + ChunkKind::Catch, + identifier, + source, + Some(recurse_self(node, ChunkContext::ClassBody)), + )) +} + +fn classify_render_expr<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { + let identifier = child_by_kind(node, &["snippet_name"]) + .and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte()))); + make_kind_chunk(node, ChunkKind::Render, identifier, source, None) +} + +fn classify_element<'t>(node: Node<'t>, source: &str) -> Option> { + let tag_name = extract_markup_tag_name(node, source)?; + Some(force_container(make_container_chunk( + node, + ChunkKind::Tag, + Some(tag_name), + source, + Some(recurse_self(node, ChunkContext::ClassBody)), + ))) +} + +fn make_named_container_chunk<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, +) -> RawChunkCandidate<'t> { + force_container(make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + Some(recurse_self(node, ChunkContext::ClassBody)), + source, + )) +} + +const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> { + candidate.force_recurse = true; + candidate +} + +fn block_expr_identifier( node: Node<'_>, source: &str, header_kind: &str, expr_kinds: &[&str], - prefix: &str, -) -> String { +) -> Option { child_by_kind(node, &[header_kind]) .and_then(|header| child_by_kind(header, expr_kinds)) .and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte()))) - .map_or_else(|| prefix.to_string(), |expr| format!("{prefix}_{expr}")) } fn element_has_structure(node: Node<'_>) -> bool { diff --git a/crates/pi-natives/src/chunk/ast_tlaplus.rs b/crates/pi-natives/src/chunk/ast_tlaplus.rs index 4404902ad..db4bb22f6 100644 --- a/crates/pi-natives/src/chunk/ast_tlaplus.rs +++ b/crates/pi-natives/src/chunk/ast_tlaplus.rs @@ -11,9 +11,10 @@ use super::{ classify::LangClassifier, common::{ ChunkContext, RawChunkCandidate, RecurseSpec, child_by_kind, extract_identifier, - group_candidate, make_container_chunk, make_container_chunk_from, make_named_chunk, + group_candidate, make_container_chunk, make_container_chunk_from, make_kind_chunk, positional_candidate, recurse_self, sanitize_identifier, }, + kind::ChunkKind, types::ChunkNode, }; @@ -24,25 +25,32 @@ impl LangClassifier for TlaplusClassifier { match node.kind() { "module" => Some(make_container_chunk( node, - prefixed(node, "mod", source), + ChunkKind::Module, + tla_identifier(node, source), source, Some(recurse_self(node, ChunkContext::Root)), )), "variable_declaration" | "constant_declaration" | "recursive_declaration" => { - Some(group_candidate(node, "decls", source)) - }, - "operator_definition" => { - Some(make_named_chunk(node, prefixed(node, "operator", source), source, None)) + Some(group_candidate(node, ChunkKind::Declarations, source)) }, + "operator_definition" => Some(make_kind_chunk( + node, + ChunkKind::Operator, + tla_identifier(node, source), + source, + None, + )), "module_definition" => Some(make_container_chunk( node, - prefixed(node, "mod", source), + ChunkKind::Module, + tla_identifier(node, source), source, Some(recurse_self(node, ChunkContext::Root)), )), "pcal_algorithm" => Some(make_container_chunk( node, - prefixed(node, "algo", source), + ChunkKind::Algo, + tla_identifier(node, source), source, recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody), )), @@ -50,7 +58,8 @@ impl LangClassifier for TlaplusClassifier { make_container_chunk_from( node, algorithm, - prefixed(algorithm, "algo", source), + ChunkKind::Algo, + tla_identifier(algorithm, source), source, recurse_child(algorithm, "pcal_algorithm_body", ChunkContext::ClassBody), ) @@ -63,17 +72,19 @@ impl LangClassifier for TlaplusClassifier { match node.kind() { "pcal_procedure" => Some(make_container_chunk( node, - prefixed(node, "proc", source), + ChunkKind::Proc, + tla_identifier(node, source), source, recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody), )), "pcal_process" => Some(make_container_chunk( node, - prefixed(node, "process", source), + ChunkKind::Process, + tla_identifier(node, source), source, recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody), )), - "pcal_var_decls" => Some(group_candidate(node, "decls", source)), + "pcal_var_decls" => Some(group_candidate(node, ChunkKind::Declarations, source)), _ => None, } } @@ -81,12 +92,12 @@ impl LangClassifier for TlaplusClassifier { fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option> { match node.kind() { // PlusCal control flow - "pcal_if" => Some(positional_candidate(node, "if", source)), - "pcal_while" => Some(positional_candidate(node, "loop", source)), - "pcal_either" => Some(positional_candidate(node, "either", source)), - "pcal_with" => Some(positional_candidate(node, "with", source)), + "pcal_if" => Some(positional_candidate(node, ChunkKind::If, source)), + "pcal_while" => Some(positional_candidate(node, ChunkKind::Loop, source)), + "pcal_either" => Some(positional_candidate(node, ChunkKind::Either, source)), + "pcal_with" => Some(positional_candidate(node, ChunkKind::With, source)), // PlusCal assignments - "pcal_assign" => Some(group_candidate(node, "stmts", source)), + "pcal_assign" => Some(group_candidate(node, ChunkKind::Statements, source)), _ => None, } } @@ -108,11 +119,10 @@ impl LangClassifier for TlaplusClassifier { parent: &RawChunkCandidate<'_>, _children: &[RawChunkCandidate<'_>], ) -> bool { - let name = parent.base_name.as_str(); - name.starts_with("mod_") - || name.starts_with("algo_") - || name.starts_with("proc_") - || name.starts_with("process_") + matches!( + parent.kind, + ChunkKind::Module | ChunkKind::Algo | ChunkKind::Proc | ChunkKind::Process + ) } fn post_process( @@ -174,14 +184,11 @@ impl LangClassifier for TlaplusClassifier { } } -fn prefixed(node: Node<'_>, prefix: &str, source: &str) -> String { - let name = extract_identifier(node, source) - .or_else(|| { - child_by_kind(node, &["identifier"]) - .and_then(|child| sanitize_identifier(child.utf8_text(source.as_bytes()).ok()?)) - }) - .unwrap_or_else(|| "anonymous".to_string()); - format!("{prefix}_{name}") +fn tla_identifier(node: Node<'_>, source: &str) -> Option { + extract_identifier(node, source).or_else(|| { + child_by_kind(node, &["identifier"]) + .and_then(|child| sanitize_identifier(child.utf8_text(source.as_bytes()).ok()?)) + }) } fn recurse_child<'tree>( @@ -255,7 +262,8 @@ fn translation_chunk( let checksum = super::chunk_checksum(&source.as_bytes()[start_byte as usize..end_byte as usize]); ChunkNode { path, - name: format!("translation_{}", range.start_line), + identifier: Some(range.start_line.to_string()), + kind: ChunkKind::Translation, leaf: true, parent_path, children: Vec::new(), @@ -299,10 +307,6 @@ fn byte_range_for_lines(source: &str, start_line: u32, end_line: u32) -> (u32, u } if byte == b'\n' { current_line += 1; - if current_line > end_line { - end_byte = byte_index + 1; - break; - } } } diff --git a/crates/pi-natives/src/chunk/ast_vue.rs b/crates/pi-natives/src/chunk/ast_vue.rs index e12fa70f6..087103995 100644 --- a/crates/pi-natives/src/chunk/ast_vue.rs +++ b/crates/pi-natives/src/chunk/ast_vue.rs @@ -2,7 +2,7 @@ use tree_sitter::Node; -use super::{classify::LangClassifier, common::*}; +use super::{classify::LangClassifier, common::*, kind::ChunkKind}; pub struct VueClassifier; @@ -43,59 +43,58 @@ fn classify_nested_node<'t>(node: Node<'t>, source: &str) -> Option classify_start_tag(node, source), "directive_attribute" => Some(classify_directive_attribute(node, source)), "attribute" => Some(classify_attribute(node, source)), - "interpolation" => Some(make_named_chunk(node, "expr".to_string(), source, None)), - "text" => Some(group_candidate(node, "text", source)), - "raw_text" => Some(group_candidate(node, "text", source)), + "interpolation" => Some(make_kind_chunk(node, ChunkKind::Expression, None, source, None)), + "text" => Some(group_candidate(node, ChunkKind::Text, source)), + "raw_text" => Some(group_candidate(node, ChunkKind::Text, source)), _ => None, } } fn classify_template_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = extract_slot_name(node, source) - .map_or_else(|| "template".to_string(), |slot_name| format!("slot_{slot_name}")); - make_container_chunk(node, name, source, Some(recurse_self(node, ChunkContext::ClassBody))) + let recurse = Some(recurse_self(node, ChunkContext::ClassBody)); + if let Some(slot_name) = extract_slot_name(node, source) { + force_container(make_container_chunk(node, ChunkKind::Slot, Some(slot_name), source, recurse)) + } else { + force_container(make_container_chunk(node, ChunkKind::Template, None, source, recurse)) + } } fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "setup", source) { - "script_setup".to_string() + let kind = if has_attribute(node, "setup", source) { + ChunkKind::ScriptSetup } else if attribute_value(node, "context", source).as_deref() == Some("module") { - "script_module".to_string() + ChunkKind::ScriptModule } else { - "script".to_string() + ChunkKind::Script }; // tree-sitter-vue exposes script bodies as `raw_text`, not injected JS/TS. - make_named_chunk(node, name, source, None) + positional_candidate(node, kind, source) } fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { - let name = if has_attribute(node, "scoped", source) { - "style_scoped".to_string() + let kind = if has_attribute(node, "scoped", source) { + ChunkKind::StyleScoped } else { - "style".to_string() + ChunkKind::Style }; // Styles are likewise exposed as `raw_text`, so preserve only the SFC block. - make_named_chunk(node, name, source, None) + positional_candidate(node, kind, source) } fn classify_custom_block<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let tag_name = extract_markup_tag_name(node, source).unwrap_or_else(|| "anonymous".to_string()); - make_container_chunk( - node, - format!("custom_{tag_name}"), - source, - Some(recurse_self(node, ChunkContext::ClassBody)), - ) + make_named_container_chunk(node, ChunkKind::Custom, tag_name, source) } fn classify_element<'t>(node: Node<'t>, source: &str) -> Option> { let tag_name = extract_markup_tag_name(node, source)?; - Some(make_container_chunk( + Some(force_container(make_container_chunk( node, - format!("tag_{tag_name}"), + ChunkKind::Tag, + Some(tag_name), source, Some(recurse_self(node, ChunkContext::ClassBody)), - )) + ))) } fn classify_start_tag<'t>(node: Node<'t>, source: &str) -> Option> { @@ -108,19 +107,14 @@ fn classify_start_tag<'t>(node: Node<'t>, source: &str) -> Option(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { let name = child_by_kind(node, &["attribute_name"]) .and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte()))) .unwrap_or_else(|| "attr".to_string()); - make_named_chunk(node, format!("attr_{name}"), source, None) + make_kind_chunk(node, ChunkKind::Attr, Some(name), source, None) } fn classify_directive_attribute<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> { @@ -131,16 +125,75 @@ fn classify_directive_attribute<'t>(node: Node<'t>, source: &str) -> RawChunkCan .filter(|mods| !mods.is_empty()) .map(|mods| format!("_{mods}")) .unwrap_or_default(); - let chunk_name = if raw.starts_with('@') { - format!("on_{directive_name}{modifier_suffix}") + if raw.starts_with('@') { + make_named_leaf_chunk( + node, + ChunkKind::Directive, + format!("on_{directive_name}{modifier_suffix}"), + source, + ) } else if raw.starts_with(':') { - format!("bind_{directive_name}{modifier_suffix}") + make_named_leaf_chunk( + node, + ChunkKind::Directive, + format!("bind_{directive_name}{modifier_suffix}"), + source, + ) } else if raw.starts_with('#') { - format!("slot_{directive_name}{modifier_suffix}") + make_kind_chunk( + node, + ChunkKind::Slot, + Some(format!("{directive_name}{modifier_suffix}")), + source, + None, + ) } else { - format!("dir_{directive_name}{modifier_suffix}") - }; - make_named_chunk(node, chunk_name, source, None) + make_named_leaf_chunk( + node, + ChunkKind::Directive, + format!("dir_{directive_name}{modifier_suffix}"), + source, + ) + } +} + +fn make_named_leaf_chunk<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, +) -> RawChunkCandidate<'t> { + make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + None, + source, + ) +} + +fn make_named_container_chunk<'t>( + node: Node<'t>, + kind: ChunkKind, + identifier: impl Into>, + source: &str, +) -> RawChunkCandidate<'t> { + force_container(make_candidate( + node, + kind, + identifier, + NameStyle::Named, + signature_for_node(node, source), + Some(recurse_self(node, ChunkContext::ClassBody)), + source, + )) +} + +const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> { + candidate.force_recurse = true; + candidate } fn extract_markup_tag_name(node: Node<'_>, source: &str) -> Option { diff --git a/crates/pi-natives/src/chunk/common.rs b/crates/pi-natives/src/chunk/common.rs index 2a57b6b3b..74acadc85 100644 --- a/crates/pi-natives/src/chunk/common.rs +++ b/crates/pi-natives/src/chunk/common.rs @@ -6,7 +6,10 @@ use tree_sitter::Node; -use super::types::ChunkNode; +use super::{ + kind::{ChunkKind, SummaryStyle}, + types::ChunkNode, +}; use crate::env_uint; // ── Configuration (environment overrides) ──────────────────────────────── @@ -19,6 +22,12 @@ env_uint! { pub static MIN_RECURSE_SAVINGS: usize = "PI_CHUNK_MIN_SAVINGS" or 4 => [1, usize::MAX]; } +/// Always recurse into named (non-group) chunks when children exist, +/// regardless of the leaf threshold. Disabled via `PI_CHUNK_ALWAYS_RECURSE=0`. +pub static ALWAYS_RECURSE: std::sync::LazyLock = std::sync::LazyLock::new(|| { + std::env::var("PI_CHUNK_ALWAYS_RECURSE").map_or(true, |v| !matches!(v.as_str(), "0" | "false")) +}); + // ── Internal types ─────────────────────────────────────────────────────── #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -43,7 +52,8 @@ pub struct RecurseSpec<'tree> { #[derive(Clone, Debug)] pub struct RawChunkCandidate<'tree> { - pub base_name: String, + pub identifier: Option, + pub kind: ChunkKind, pub name_style: NameStyle, pub range_start_byte: usize, pub range_end_byte: usize, @@ -88,20 +98,21 @@ const fn end_row_as_line(start: tree_sitter::Point, end: tree_sitter::Point) -> pub fn make_candidate<'tree>( node: Node<'tree>, - base_name: String, + kind: ChunkKind, + identifier: impl Into>, name_style: NameStyle, signature: Option, recurse: Option>, - force_recurse: bool, source: &str, ) -> RawChunkCandidate<'tree> { + let identifier = identifier.into(); let start = node.start_position(); let end = node.end_position(); - let base_name = canonical_chunk_name(node, base_name.as_str(), source); - let summary = summary_for_node(node, base_name.as_str(), signature.as_deref(), source); + let summary = summary_for_node(node, kind, identifier.as_deref(), signature.as_deref(), source); let start_byte = node.start_byte(); RawChunkCandidate { - base_name, + identifier, + kind, name_style, range_start_byte: start_byte, range_end_byte: node.end_byte(), @@ -109,96 +120,99 @@ pub fn make_candidate<'tree>( range_start_line: start.row + 1, range_end_line: end_row_as_line(start, end), signature: summary, - error: name_style == NameStyle::Error, - groupable: matches!(name_style, NameStyle::Group), + error: kind == ChunkKind::Error, + groupable: kind.traits().groupable, has_leading_comment: false, - force_recurse, + force_recurse: kind.traits().container, recurse, } } pub fn group_candidate<'tree>( node: Node<'tree>, - base_name: &str, + kind: ChunkKind, source: &str, ) -> RawChunkCandidate<'tree> { - make_candidate(node, base_name.to_string(), NameStyle::Group, None, None, false, source) + make_candidate(node, kind, None, NameStyle::Group, None, None, source) } pub fn positional_candidate<'tree>( node: Node<'tree>, - base_name: &str, + kind: ChunkKind, source: &str, ) -> RawChunkCandidate<'tree> { - make_candidate(node, base_name.to_string(), NameStyle::Named, None, None, false, source) + make_candidate(node, kind, None, NameStyle::Named, None, None, source) } pub fn named_candidate<'tree>( node: Node<'tree>, - prefix: &str, + kind: ChunkKind, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { - make_named_chunk(node, prefixed_name(prefix, node, source), source, recurse) + make_kind_chunk(node, kind, extract_identifier(node, source), source, recurse) } pub fn container_candidate<'tree>( node: Node<'tree>, - prefix: &str, + kind: ChunkKind, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { - make_container_chunk(node, prefixed_name(prefix, node, source), source, recurse) + make_kind_chunk(node, kind, extract_identifier(node, source), source, recurse) } -pub fn make_named_chunk<'tree>( +pub fn make_kind_chunk<'tree>( node: Node<'tree>, - name: String, + kind: ChunkKind, + identifier: Option, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { make_candidate( node, - name, + kind, + identifier, NameStyle::Named, signature_for_node(node, source), recurse, - false, source, ) } -pub fn make_named_chunk_from<'tree>( +pub fn make_kind_chunk_from<'tree>( range_node: Node<'tree>, signature_node: Node<'tree>, - name: String, + kind: ChunkKind, + identifier: Option, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { make_candidate( range_node, - name, + kind, + identifier, NameStyle::Named, signature_for_node(signature_node, source), recurse, - false, source, ) } pub fn make_container_chunk<'tree>( node: Node<'tree>, - name: String, + kind: ChunkKind, + identifier: Option, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { make_candidate( node, - name, + kind, + identifier, NameStyle::Named, signature_for_node(node, source), recurse, - true, source, ) } @@ -206,17 +220,18 @@ pub fn make_container_chunk<'tree>( pub fn make_container_chunk_from<'tree>( range_node: Node<'tree>, signature_node: Node<'tree>, - name: String, + kind: ChunkKind, + identifier: Option, source: &str, recurse: Option>, ) -> RawChunkCandidate<'tree> { make_candidate( range_node, - name, + kind, + identifier, NameStyle::Named, signature_for_node(signature_node, source), recurse, - true, source, ) } @@ -231,12 +246,9 @@ pub fn prefixed_name(prefix: &str, node: Node<'_>, source: &str) -> String { /// Derive a semantic name from a node's kind and/or identifier. pub fn infer_named_candidate<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> { - let kind_prefix = sanitize_node_kind(node.kind()); - let name = match extract_identifier(node, source) { - Some(id) => format!("{kind_prefix}_{id}"), - None => kind_prefix, - }; - make_named_chunk(node, name, source, None) + let kind_name = sanitize_node_kind(node.kind()); + let kind = ChunkKind::from_sanitized_kind(kind_name.as_str()); + make_kind_chunk(node, kind, extract_identifier(node, source), source, None) } // ── Tree navigation helpers ────────────────────────────────────────────── @@ -634,67 +646,6 @@ pub fn signature_for_node(node: Node<'_>, source: &str) -> Option { // ── Summary / canonical naming ─────────────────────────────────────────── -fn is_function_like_kind(kind: &str) -> bool { - matches!( - kind, - "function_declaration" - | "function_definition" - | "function_item" - | "procedure_declaration" - | "overloaded_procedure_declaration" - | "function_definition_header" - | "test_declaration" - | "method_definition" - | "method_signature" - | "abstract_method_signature" - | "method_declaration" - | "protocol_function_declaration" - | "method" - | "singleton_method" - ) -} - -fn is_variable_decl_kind(kind: &str) -> bool { - matches!( - kind, - "lexical_declaration" - | "variable_declaration" - | "const_declaration" - | "var_declaration" - | "let_declaration" - | "short_var_declaration" - ) -} - -fn canonical_chunk_name(node: Node<'_>, base_name: &str, source: &str) -> String { - if is_function_like_kind(node.kind()) - && !base_name.starts_with("fn_") - && base_name != "constructor" - && let Some(name) = extract_identifier(node, source) - { - return format!("fn_{name}"); - } - if is_variable_decl_kind(node.kind()) - && !base_name.starts_with("var_") - && !base_name.starts_with("fn_") - && !base_name.starts_with("class_") - && let Some(name) = extract_single_declarator_name(node, source) - { - return format!("var_{name}"); - } - if (base_name == "expression" || base_name == "expr") - && let Some(name) = extract_identifier(node, source) - { - return format!("expr_{name}"); - } - if base_name == "return" - && let Some(name) = extract_identifier(node, source) - { - return format!("ret_{name}"); - } - base_name.to_string() -} - fn normalize_summary_text(summary: &str) -> Option { let summary = collapse_whitespace(summary.trim()) .trim_end_matches('{') @@ -709,22 +660,29 @@ fn normalize_summary_text(summary: &str) -> Option { } } -fn summarize_function_node(canonical_name: &str, raw_signature: &str) -> String { - let name = canonical_name.strip_prefix("fn_").unwrap_or(canonical_name); +fn summarize_function_node( + kind: ChunkKind, + identifier: Option<&str>, + raw_signature: &str, +) -> String { + let name = identifier.unwrap_or_else(|| kind.prefix()); let tail = function_signature(raw_signature) .or_else(|| python_function_signature(raw_signature)) .or_else(|| rust_function_signature(raw_signature)) .unwrap_or_else(|| raw_signature.to_string()); let tail = tail.replacen("): ", ") → ", 1); - format!("fn {name}{tail}") + format!("{} {name}{tail}", kind.prefix()) } -fn summarize_variable_node(node: Node<'_>, canonical_name: &str, source: &str) -> Option { +fn summarize_variable_node( + node: Node<'_>, + kind: ChunkKind, + identifier: Option<&str>, + source: &str, +) -> Option { let header = normalized_header(source, node.start_byte(), node.end_byte()); let keyword = header.split_whitespace().next()?; - let name = canonical_name - .strip_prefix("var_") - .unwrap_or(canonical_name); + let name = identifier.unwrap_or_else(|| kind.prefix()); Some(format!("{keyword} {name}")) } @@ -734,20 +692,22 @@ fn summarize_statement_node(node: Node<'_>, source: &str) -> Option { pub fn summary_for_node( node: Node<'_>, - canonical_name: &str, + kind: ChunkKind, + identifier: Option<&str>, raw_signature: Option<&str>, source: &str, ) -> Option { - if canonical_name == "imports" { - return Some("imports".to_string()); - } - if canonical_name.starts_with("fn_") - && let Some(signature) = raw_signature - { - return Some(summarize_function_node(canonical_name, signature)); - } - if canonical_name.starts_with("var_") { - return summarize_variable_node(node, canonical_name, source); + match kind.traits().summary { + SummaryStyle::Imports => return Some("imports".to_string()), + SummaryStyle::Function => { + if let Some(signature) = raw_signature { + return Some(summarize_function_node(kind, identifier, signature)); + } + }, + SummaryStyle::Variable => { + return summarize_variable_node(node, kind, identifier, source); + }, + SummaryStyle::Default => {}, } if matches!( node.kind(), diff --git a/crates/pi-natives/src/chunk/defaults.rs b/crates/pi-natives/src/chunk/defaults.rs index 7fc7d560f..c6a8edba2 100644 --- a/crates/pi-natives/src/chunk/defaults.rs +++ b/crates/pi-natives/src/chunk/defaults.rs @@ -6,40 +6,32 @@ use tree_sitter::Node; -use super::common::*; - -// ── Root-level default ────────────────────────────────────────────────── +use super::{common::*, kind::ChunkKind}; pub fn classify_root_default<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> { infer_named_candidate(node, source) } -// ── Class-level default ───────────────────────────────────────────────── - pub fn classify_class_default<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> { infer_named_candidate(node, source) } -// ── Function-level default ────────────────────────────────────────────── - pub fn classify_function_default<'tree>( node: Node<'tree>, source: &str, ) -> RawChunkCandidate<'tree> { let kind_name = sanitize_node_kind(node.kind()); - group_candidate(node, &kind_name, source) + group_candidate(node, ChunkKind::from_sanitized_kind(kind_name.as_str()), source) } -// ── Variable declaration classification (shared) ──────────────────────── - pub fn classify_var_decl<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> { if let Some(candidate) = promote_assigned_expression(node, node, source) { return candidate; } if let Some(name) = extract_single_declarator_name(node, source) { - return make_named_chunk(node, format!("var_{name}"), source, None); + return make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None); } - group_candidate(node, "decls", source) + group_candidate(node, ChunkKind::Declarations, source) } pub fn promote_assigned_expression<'tree>( @@ -62,14 +54,22 @@ pub fn promote_assigned_expression<'tree>( match value.kind() { "arrow_function" | "function_expression" | "function" => { let recurse = recurse_body(value, ChunkContext::FunctionBody); - Some(make_named_chunk_from(range_node, value, format!("fn_{name}"), source, recurse)) + Some(make_kind_chunk_from( + range_node, + value, + ChunkKind::Function, + Some(name), + source, + recurse, + )) }, "class" | "class_expression" => { let recurse = recurse_class(value); Some(make_container_chunk_from( range_node, value, - format!("class_{name}"), + ChunkKind::Class, + Some(name), source, recurse, )) diff --git a/crates/pi-natives/src/chunk/edit.rs b/crates/pi-natives/src/chunk/edit.rs index ca9be0737..462ea4393 100644 --- a/crates/pi-natives/src/chunk/edit.rs +++ b/crates/pi-natives/src/chunk/edit.rs @@ -5,9 +5,10 @@ use crate::chunk::{ denormalize_from_tabs, detect_file_indent_char, detect_file_indent_step, normalize_leading_whitespace_char, reindent_inserted_block, strip_content_prefixes, }, + kind::ChunkKind, resolve::{ - ParsedSelector, chunk_region_range, chunk_supports_region, resolve_chunk_selector, - resolve_chunk_with_crc, sanitize_chunk_selector, sanitize_crc, split_selector_crc_and_region, + ParsedSelector, chunk_region_range, resolve_chunk_selector, resolve_chunk_with_crc, + sanitize_chunk_selector, sanitize_crc, split_selector_crc_and_region, }, state::{ChunkState, ChunkStateInner}, types::{ @@ -309,11 +310,6 @@ fn resolve_edit_target( validate_batch_crc(resolved.chunk, resolved.crc.as_deref(), requires_checksum)?; } let chunk = resolved.chunk.clone(); - if let Some(r) = region - && !chunk_supports_region(&chunk, r) - { - return Err(format!("Chunk \"{}\" does not support @{}.", chunk.path, r.as_str())); - } Ok(ResolvedEditTarget { chunk, region }) } @@ -341,7 +337,7 @@ fn apply_replace( let anchor = target.chunk; let (region_start, region_end) = match target.region { None => (anchor.start_byte as usize, anchor.end_byte as usize), - Some(r) => chunk_region_range(&anchor, r)?, + Some(r) => chunk_region_range(&anchor, r), }; // Scoped find/replace: locate a literal substring inside the chunk and replace @@ -461,7 +457,7 @@ fn apply_delete( let anchor = target.chunk; if let Some(r) = target.region { - let (range_start, range_end) = chunk_region_range(&anchor, r)?; + let (range_start, range_end) = chunk_region_range(&anchor, r); state.source = replace_byte_range(&state.source, range_start, range_end, ""); } else { let offsets = line_offsets(&state.source); @@ -934,11 +930,12 @@ fn chunk_slice(text: &str, chunk: &ChunkNode) -> String { .join("\n") } -fn is_container_like_chunk(chunk: &ChunkNode) -> bool { +const fn is_container_like_chunk(chunk: &ChunkNode) -> bool { + let traits = chunk.kind.traits(); !chunk.leaf - || ["class_", "type_", "interface_", "enum_", "struct_", "impl_", "trait_", "mod_"] - .iter() - .any(|prefix| chunk.name.starts_with(prefix)) + || traits.container + || traits.has_addressable_members + || traits.always_preserve_children } fn go_receiver_belongs_to_type(source: &str, chunk: &ChunkNode, type_name: &str) -> bool { @@ -952,11 +949,14 @@ fn go_receiver_belongs_to_type(source: &str, chunk: &ChunkNode, type_name: &str) } fn owned_container_end_line(state: &ChunkStateInner, anchor: &ChunkNode) -> u32 { - if state.language != "go" || !anchor.path.starts_with("type_") { + if state.language != "go" || anchor.kind != ChunkKind::Type { return anchor.end_line; } - let type_name = anchor.path.trim_start_matches("type_"); + let type_name = anchor + .identifier + .as_deref() + .unwrap_or_else(|| anchor.kind.prefix()); let mut owned_end_line = anchor.end_line; let mut top_level_chunks = state .tree @@ -977,7 +977,7 @@ fn owned_container_end_line(state: &ChunkStateInner, anchor: &ChunkNode) -> u32 if chunk.start_line < owned_end_line { continue; } - if chunk.name.starts_with("fn_") + if chunk.kind == ChunkKind::Function && go_receiver_belongs_to_type(&state.source, chunk, type_name) { owned_end_line = chunk.end_line; @@ -1018,7 +1018,7 @@ fn body_insertion_point( at_end: bool, file_indent_char: char, file_indent_step: usize, -) -> Result { +) -> InsertionPoint { let offsets = line_offsets(&state.source); let indent = compute_insert_indent(state, anchor, true, file_indent_char, file_indent_step); if at_end { @@ -1034,13 +1034,13 @@ fn body_insertion_point( } else { last_child.indent_char.repeat(last_child.indent as usize) }; - return Ok(InsertionPoint { + return InsertionPoint { offset: line_end_offset(&offsets, last_child.end_line, &state.source), indent: child_indent, - }); + }; } - let (_, body_end) = chunk_region_range(anchor, ChunkRegion::Inner)?; - return Ok(InsertionPoint { offset: body_end, indent }); + let (_, body_end) = chunk_region_range(anchor, ChunkRegion::Inner); + return InsertionPoint { offset: body_end, indent }; } if let Some(first_child_path) = anchor.children.first() @@ -1050,13 +1050,13 @@ fn body_insertion_point( .iter() .find(|chunk| &chunk.path == first_child_path) { - return Ok(InsertionPoint { + return InsertionPoint { offset: line_start_offset(&offsets, first_child.start_line, &state.source), indent, - }); + }; } - let (body_start, _) = chunk_region_range(anchor, ChunkRegion::Inner)?; - Ok(InsertionPoint { offset: body_start, indent }) + let (body_start, _) = chunk_region_range(anchor, ChunkRegion::Inner); + InsertionPoint { offset: body_start, indent } } fn resolve_insertion_point( @@ -1080,13 +1080,13 @@ fn resolve_insertion_point( // Inner first-child position (Some(ChunkRegion::Inner), ChunkEditOp::Before | ChunkEditOp::Prepend) | (Some(ChunkRegion::Head), ChunkEditOp::After | ChunkEditOp::Append) => Ok(( - body_insertion_point(state, anchor, false, file_indent_char, file_indent_step)?, + body_insertion_point(state, anchor, false, file_indent_char, file_indent_step), InsertPosition::FirstChild, )), // Inner last-child position (Some(ChunkRegion::Inner), ChunkEditOp::After | ChunkEditOp::Append) | (Some(ChunkRegion::Tail), ChunkEditOp::Before | ChunkEditOp::Prepend) => Ok(( - body_insertion_point(state, anchor, true, file_indent_char, file_indent_step)?, + body_insertion_point(state, anchor, true, file_indent_char, file_indent_step), InsertPosition::LastChild, )), (_, ChunkEditOp::Replace | ChunkEditOp::Delete) => { @@ -1207,35 +1207,22 @@ fn container_has_interior_content(state: &ChunkStateInner, anchor: &ChunkNode) - /// children (methods) want blank line spacing. Containers whose children are /// all packed declarations (struct fields, enum variants) are tightly packed. fn children_want_blank_line_spacing(state: &ChunkStateInner, anchor: &ChunkNode) -> bool { - // Root children are always top-level declarations, separated by blank lines. if anchor.path.is_empty() { return true; } - // If the container has no chunk children, fall back to spaced (preserves - // existing behavior for containers with interior content but no parsed - // children). if anchor.children.is_empty() { return true; } - // Packed children are declarations that belong tightly together without - // blank line separators: struct fields, enum variants, etc. let all_packed = anchor.children.iter().all(|child_path| { state .tree .chunks .iter() - .any(|c| c.path == *child_path && is_packed_child(&c.name)) + .any(|c| c.path == *child_path && c.kind.traits().packed) }); !all_packed } -/// Returns true if a chunk name indicates a packed (tightly-spaced) child. -/// These are declarations like struct fields and enum variants that don't -/// need blank line separators between them. -fn is_packed_child(name: &str) -> bool { - name.starts_with("field_") || name.starts_with("variant_") -} - /// Returns true if sibling insertions around `anchor` should have blank line /// spacing. Checks whether the anchor's parent container uses spaced or packed /// layout. @@ -2088,7 +2075,12 @@ mod tests { .tree .chunks .iter() - .find(|c| c.name.starts_with("stmts")) + .find(|c| { + c.path + .rsplit('.') + .next() + .is_some_and(|leaf| leaf.starts_with("stmts")) + }) .expect("stmts chunk should exist"); assert!(stmts.group, "stmts chunk should be marked as group"); @@ -2419,7 +2411,59 @@ mod tests { assert_eq!( result.diff_after, "class Server {\n start() {\n return 42;\n }\n}\n", - "nested body replace should produce correct 2-level indent" + "4-space: nested body replace should produce correct 2-level indent" + ); + } + + #[test] + fn nested_body_replace_preserves_correct_indentation_2space() { + // 2-space file: method body at 2 levels of indent. + let source = "class Server {\n start() {\n work();\n }\n}\n"; + let state = state_for(source, "typescript"); + let chunk = state + .inner() + .chunk("class_Server.fn_start") + .expect("fn_start"); + + let result = apply_single_edit(&state, "test.ts", EditOperation { + op: ChunkEditOp::Replace, + sel: Some(format!("class_Server.fn_start#{}@inner", chunk.checksum)), + crc: None, + region: None, + content: Some("\treturn 42;\n".to_owned()), + find: None, + }); + + assert_eq!( + result.diff_after, "class Server {\n start() {\n return 42;\n }\n}\n", + "2-space: nested body replace should produce correct 2-level indent" + ); + } + + #[test] + fn nested_body_replace_with_excess_tabs_corrected() { + // Agent accidentally includes base padding (2 tabs instead of 1). + // Correction mechanism should strip common indent and produce correct output. + let source = "class Server {\n start() {\n work();\n }\n}\n"; + let state = state_for(source, "typescript"); + let chunk = state + .inner() + .chunk("class_Server.fn_start") + .expect("fn_start"); + + let result = apply_single_edit(&state, "test.ts", EditOperation { + op: ChunkEditOp::Replace, + sel: Some(format!("class_Server.fn_start#{}@inner", chunk.checksum)), + crc: None, + region: None, + content: Some("\t\tif (x) {\n\t\t\ty();\n\t\t}\n".to_owned()), + find: None, + }); + + assert_eq!( + result.diff_after, + "class Server {\n start() {\n if (x) {\n y();\n }\n }\n}\n", + "2-space: excess tabs should be corrected via dedent" ); } @@ -2492,7 +2536,12 @@ mod tests { .tree .chunks .iter() - .find(|c| c.name.starts_with("list")) + .find(|c| { + c.path + .rsplit('.') + .next() + .is_some_and(|leaf| leaf.starts_with("list")) + }) .expect("list chunk"); let result = apply_single_edit(&state, "test.md", EditOperation { diff --git a/crates/pi-natives/src/chunk/indent.rs b/crates/pi-natives/src/chunk/indent.rs index 5cc130b3f..733ee3191 100644 --- a/crates/pi-natives/src/chunk/indent.rs +++ b/crates/pi-natives/src/chunk/indent.rs @@ -509,7 +509,7 @@ fn hashline_prefix_len(line: &str) -> Option { #[cfg(test)] mod tests { use super::*; - use crate::chunk::types::ChunkNode; + use crate::chunk::{kind::ChunkKind, types::ChunkNode}; fn chunk( path: &str, @@ -518,9 +518,18 @@ mod tests { indent: u32, indent_char: &str, ) -> ChunkNode { + let kind = match path.split_once('_').map_or(path, |(prefix, _)| prefix) { + "fn" => ChunkKind::Function, + "class" => ChunkKind::Class, + "stmts" => ChunkKind::Statements, + _ => ChunkKind::Chunk, + }; ChunkNode { path: path.to_owned(), - name: path.to_owned(), + identifier: path + .split_once('_') + .and_then(|(_, identifier)| (!identifier.is_empty()).then_some(identifier.to_owned())), + kind, leaf: children.is_empty(), parent_path: parent_path.map(str::to_owned), children: children.iter().map(|child| (*child).to_owned()).collect(), diff --git a/crates/pi-natives/src/chunk/kind.rs b/crates/pi-natives/src/chunk/kind.rs new file mode 100644 index 000000000..9461a3b36 --- /dev/null +++ b/crates/pi-natives/src/chunk/kind.rs @@ -0,0 +1,675 @@ +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum ChunkKind { + Add, + After, + Alias, + Algo, + Arg, + Argv, + Array, + At, + Attr, + AttrExpr, + Attrs, + Block, + BlockIf, + BlockLocals, + Body, + Case, + Cases, + Catch, + Cell, + Class, + Clause, + Cmd, + Code, + Cond, + Constructor, + Contract, + Copy, + Custom, + Declarations, + Decl, + DefaultExport, + Define, + Directive, + Either, + Elif, + Else, + Enum, + Env, + Error, + Except, + Exports, + Expose, + Expression, + Field, + Fields, + File, + Frame, + Function, + For, + ForIn, + ForOf, + Form, + Frontmatter, + Group, + GroupBy, + Headers, + Healthcheck, + Html, + Hunks, + If, + Iface, + Impl, + Imports, + Includes, + InlineFragment, + Install, + Interface, + Interpolation, + Item, + Join, + Key, + KeyScripts, + Label, + Let, + List, + Loop, + Macro, + Map, + Markdown, + Match, + Method, + Methods, + Module, + Mustache, + Object, + Operation, + Operator, + Option, + Options, + OrderBy, + Parameters, + Preamble, + Proc, + Process, + Project, + Proto, + Py, + Python, + Query, + Receive, + Recipe, + Relations, + Render, + Return, + Root, + Rule, + Schema, + Script, + ScriptModule, + ScriptSetup, + Section, + Select, + Setting, + Shebang, + Shell, + Slot, + Snippet, + Source, + Stage, + StaticInit, + Statements, + Struct, + Style, + StyleScoped, + Switch, + Table, + Tag, + Target, + Template, + Text, + Trait, + Translation, + Try, + Ts, + Type, + Typescript, + Union, + User, + Val, + Variable, + Variant, + Variants, + VersionGate, + When, + Where, + While, + With, + Workdir, + Chunk, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SummaryStyle { + Function, + Variable, + Imports, + Default, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ChunkTraits { + pub groupable: bool, + pub packed: bool, + pub addressable_leaf: bool, + pub always_preserve_children: bool, + pub has_addressable_members: bool, + pub summary: SummaryStyle, + pub container: bool, +} + +const DEFAULT_TRAITS: ChunkTraits = ChunkTraits { + groupable: false, + packed: false, + addressable_leaf: false, + always_preserve_children: false, + has_addressable_members: false, + summary: SummaryStyle::Default, + container: false, +}; + +const GROUP_TRAITS: ChunkTraits = ChunkTraits { groupable: true, ..DEFAULT_TRAITS }; + +const CONTAINER_TRAITS: ChunkTraits = ChunkTraits { container: true, ..DEFAULT_TRAITS }; + +const ADDRESSABLE_CONTAINER_TRAITS: ChunkTraits = + ChunkTraits { container: true, has_addressable_members: true, ..DEFAULT_TRAITS }; + +const PRESERVE_CHILDREN_TRAITS: ChunkTraits = + ChunkTraits { container: true, always_preserve_children: true, ..DEFAULT_TRAITS }; + +const PACKED_LEAF_TRAITS: ChunkTraits = + ChunkTraits { packed: true, addressable_leaf: true, ..DEFAULT_TRAITS }; + +const FUNCTION_TRAITS: ChunkTraits = + ChunkTraits { summary: SummaryStyle::Function, ..DEFAULT_TRAITS }; + +const VARIABLE_TRAITS: ChunkTraits = + ChunkTraits { summary: SummaryStyle::Variable, ..DEFAULT_TRAITS }; + +const IMPORTS_TRAITS: ChunkTraits = + ChunkTraits { groupable: true, summary: SummaryStyle::Imports, ..DEFAULT_TRAITS }; + +impl ChunkKind { + pub const fn prefix(self) -> &'static str { + match self { + Self::Add => "add", + Self::After => "after", + Self::Alias => "alias", + Self::Algo => "algo", + Self::Arg => "arg", + Self::Argv => "argv", + Self::Array => "array", + Self::At => "at", + Self::Attr => "attr", + Self::AttrExpr => "attr_expr", + Self::Attrs => "attrs", + Self::Block => "block", + Self::BlockIf => "block_if", + Self::BlockLocals => "block_locals", + Self::Body => "body", + Self::Case => "case", + Self::Cases => "cases", + Self::Catch => "catch", + Self::Cell => "cell", + Self::Class => "class", + Self::Clause => "clause", + Self::Cmd => "cmd", + Self::Code => "code", + Self::Cond => "cond", + Self::Constructor => "constructor", + Self::Contract => "contract", + Self::Copy => "copy", + Self::Custom => "custom", + Self::Declarations => "decls", + Self::Decl => "decl", + Self::DefaultExport => "default_export", + Self::Define => "define", + Self::Directive => "directive", + Self::Either => "either", + Self::Elif => "elif", + Self::Else => "else", + Self::Enum => "enum", + Self::Env => "env", + Self::Error => "error", + Self::Except => "except", + Self::Exports => "exports", + Self::Expose => "expose", + Self::Expression => "expr", + Self::Field => "field", + Self::Fields => "fields", + Self::File => "file", + Self::Frame => "frame", + Self::Function => "fn", + Self::For => "for", + Self::ForIn => "for_in", + Self::ForOf => "for_of", + Self::Form => "form", + Self::Frontmatter => "frontmatter", + Self::Group => "group", + Self::GroupBy => "group_by", + Self::Headers => "headers", + Self::Healthcheck => "healthcheck", + Self::Html => "html", + Self::Hunks => "hunks", + Self::If => "if", + Self::Iface => "iface", + Self::Impl => "impl", + Self::Imports => "imports", + Self::Includes => "includes", + Self::InlineFragment => "inline_fragment", + Self::Install => "install", + Self::Interface => "interface", + Self::Interpolation => "interpolation", + Self::Item => "item", + Self::Join => "join", + Self::Key => "key", + Self::KeyScripts => "key_scripts", + Self::Label => "label", + Self::Let => "let", + Self::List => "list", + Self::Loop => "loop", + Self::Macro => "macro", + Self::Map => "map", + Self::Markdown => "markdown", + Self::Match => "match", + Self::Method => "meth", + Self::Methods => "methods", + Self::Module => "mod", + Self::Mustache => "mustache", + Self::Object => "object", + Self::Operation => "operation", + Self::Operator => "operator", + Self::Option => "option", + Self::Options => "options", + Self::OrderBy => "order_by", + Self::Parameters => "params", + Self::Preamble => "preamble", + Self::Proc => "proc", + Self::Process => "process", + Self::Project => "project", + Self::Proto => "proto", + Self::Py => "py", + Self::Python => "python", + Self::Query => "query", + Self::Receive => "receive", + Self::Recipe => "recipe", + Self::Relations => "relations", + Self::Render => "render", + Self::Return => "ret", + Self::Root => "root", + Self::Rule => "rule", + Self::Schema => "schema", + Self::Script => "script", + Self::ScriptModule => "script_module", + Self::ScriptSetup => "script_setup", + Self::Section => "section", + Self::Select => "select", + Self::Setting => "setting", + Self::Shebang => "shebang", + Self::Shell => "shell", + Self::Slot => "slot", + Self::Snippet => "snippet", + Self::Source => "source", + Self::Stage => "stage", + Self::StaticInit => "static_init", + Self::Statements => "stmts", + Self::Struct => "struct", + Self::Style => "style", + Self::StyleScoped => "style_scoped", + Self::Switch => "switch", + Self::Table => "table", + Self::Tag => "tag", + Self::Target => "target", + Self::Template => "template", + Self::Text => "text", + Self::Trait => "trait", + Self::Translation => "translation", + Self::Try => "try", + Self::Ts => "ts", + Self::Type => "type", + Self::Typescript => "typescript", + Self::Union => "union", + Self::User => "user", + Self::Val => "val", + Self::Variable => "var", + Self::Variant => "variant", + Self::Variants => "variants", + Self::VersionGate => "version_gate", + Self::When => "when", + Self::Where => "where", + Self::While => "while", + Self::With => "with", + Self::Workdir => "workdir", + Self::Chunk => "chunk", + } + } + + pub const fn traits(self) -> &'static ChunkTraits { + match self { + Self::Add => &GROUP_TRAITS, + Self::After => &GROUP_TRAITS, + Self::Alias => &DEFAULT_TRAITS, + Self::Algo => &CONTAINER_TRAITS, + Self::Arg => &DEFAULT_TRAITS, + Self::Argv => &DEFAULT_TRAITS, + Self::Array => &DEFAULT_TRAITS, + Self::At => &DEFAULT_TRAITS, + Self::Attr => &DEFAULT_TRAITS, + Self::AttrExpr => &DEFAULT_TRAITS, + Self::Attrs => &CONTAINER_TRAITS, + Self::Block => &DEFAULT_TRAITS, + Self::BlockIf => &DEFAULT_TRAITS, + Self::BlockLocals => &DEFAULT_TRAITS, + Self::Body => &DEFAULT_TRAITS, + Self::Case => &DEFAULT_TRAITS, + Self::Cases => &GROUP_TRAITS, + Self::Catch => &DEFAULT_TRAITS, + Self::Cell => &CONTAINER_TRAITS, + Self::Class => &CONTAINER_TRAITS, + Self::Clause => &DEFAULT_TRAITS, + Self::Cmd => &GROUP_TRAITS, + Self::Code => &GROUP_TRAITS, + Self::Cond => &DEFAULT_TRAITS, + Self::Constructor => &FUNCTION_TRAITS, + Self::Contract => &DEFAULT_TRAITS, + Self::Copy => &GROUP_TRAITS, + Self::Custom => &DEFAULT_TRAITS, + Self::Declarations => &GROUP_TRAITS, + Self::Decl => &DEFAULT_TRAITS, + Self::DefaultExport => &DEFAULT_TRAITS, + Self::Define => &DEFAULT_TRAITS, + Self::Directive => &DEFAULT_TRAITS, + Self::Either => &DEFAULT_TRAITS, + Self::Elif => &DEFAULT_TRAITS, + Self::Else => &DEFAULT_TRAITS, + Self::Enum => &ADDRESSABLE_CONTAINER_TRAITS, + Self::Env => &DEFAULT_TRAITS, + Self::Error => &DEFAULT_TRAITS, + Self::Except => &DEFAULT_TRAITS, + Self::Exports => &GROUP_TRAITS, + Self::Expose => &DEFAULT_TRAITS, + Self::Expression => &DEFAULT_TRAITS, + Self::Field => &PACKED_LEAF_TRAITS, + Self::Fields => &GROUP_TRAITS, + Self::File => &DEFAULT_TRAITS, + Self::Frame => &DEFAULT_TRAITS, + Self::Function => &FUNCTION_TRAITS, + Self::For => &DEFAULT_TRAITS, + Self::ForIn => &DEFAULT_TRAITS, + Self::ForOf => &DEFAULT_TRAITS, + Self::Form => &DEFAULT_TRAITS, + Self::Frontmatter => &DEFAULT_TRAITS, + Self::Group => &DEFAULT_TRAITS, + Self::GroupBy => &DEFAULT_TRAITS, + Self::Headers => &GROUP_TRAITS, + Self::Healthcheck => &DEFAULT_TRAITS, + Self::Html => &GROUP_TRAITS, + Self::Hunks => &GROUP_TRAITS, + Self::If => &DEFAULT_TRAITS, + Self::Iface => &PRESERVE_CHILDREN_TRAITS, + Self::Impl => &CONTAINER_TRAITS, + Self::Imports => &IMPORTS_TRAITS, + Self::Includes => &GROUP_TRAITS, + Self::InlineFragment => &DEFAULT_TRAITS, + Self::Install => &DEFAULT_TRAITS, + Self::Interface => &PRESERVE_CHILDREN_TRAITS, + Self::Interpolation => &GROUP_TRAITS, + Self::Item => &DEFAULT_TRAITS, + Self::Join => &DEFAULT_TRAITS, + Self::Key => &DEFAULT_TRAITS, + Self::KeyScripts => &DEFAULT_TRAITS, + Self::Label => &DEFAULT_TRAITS, + Self::Let => &DEFAULT_TRAITS, + Self::List => &DEFAULT_TRAITS, + Self::Loop => &DEFAULT_TRAITS, + Self::Macro => &DEFAULT_TRAITS, + Self::Map => &DEFAULT_TRAITS, + Self::Markdown => &DEFAULT_TRAITS, + Self::Match => &DEFAULT_TRAITS, + Self::Method => &DEFAULT_TRAITS, + Self::Methods => &GROUP_TRAITS, + Self::Module => &CONTAINER_TRAITS, + Self::Mustache => &DEFAULT_TRAITS, + Self::Object => &DEFAULT_TRAITS, + Self::Operation => &DEFAULT_TRAITS, + Self::Operator => &DEFAULT_TRAITS, + Self::Option => &DEFAULT_TRAITS, + Self::Options => &GROUP_TRAITS, + Self::OrderBy => &DEFAULT_TRAITS, + Self::Parameters => &GROUP_TRAITS, + Self::Preamble => &DEFAULT_TRAITS, + Self::Proc => &CONTAINER_TRAITS, + Self::Process => &CONTAINER_TRAITS, + Self::Project => &DEFAULT_TRAITS, + Self::Proto => &DEFAULT_TRAITS, + Self::Py => &DEFAULT_TRAITS, + Self::Python => &DEFAULT_TRAITS, + Self::Query => &DEFAULT_TRAITS, + Self::Receive => &DEFAULT_TRAITS, + Self::Recipe => &DEFAULT_TRAITS, + Self::Relations => &DEFAULT_TRAITS, + Self::Render => &DEFAULT_TRAITS, + Self::Return => &DEFAULT_TRAITS, + Self::Root => &DEFAULT_TRAITS, + Self::Rule => &DEFAULT_TRAITS, + Self::Schema => &DEFAULT_TRAITS, + Self::Script => &DEFAULT_TRAITS, + Self::ScriptModule => &DEFAULT_TRAITS, + Self::ScriptSetup => &DEFAULT_TRAITS, + Self::Section => &DEFAULT_TRAITS, + Self::Select => &DEFAULT_TRAITS, + Self::Setting => &DEFAULT_TRAITS, + Self::Shebang => &DEFAULT_TRAITS, + Self::Shell => &DEFAULT_TRAITS, + Self::Slot => &DEFAULT_TRAITS, + Self::Snippet => &DEFAULT_TRAITS, + Self::Source => &DEFAULT_TRAITS, + Self::Stage => &DEFAULT_TRAITS, + Self::StaticInit => &DEFAULT_TRAITS, + Self::Statements => &GROUP_TRAITS, + Self::Struct => &ADDRESSABLE_CONTAINER_TRAITS, + Self::Style => &DEFAULT_TRAITS, + Self::StyleScoped => &DEFAULT_TRAITS, + Self::Switch => &DEFAULT_TRAITS, + Self::Table => &DEFAULT_TRAITS, + Self::Tag => &DEFAULT_TRAITS, + Self::Target => &DEFAULT_TRAITS, + Self::Template => &DEFAULT_TRAITS, + Self::Text => &GROUP_TRAITS, + Self::Trait => &PRESERVE_CHILDREN_TRAITS, + Self::Translation => &DEFAULT_TRAITS, + Self::Try => &DEFAULT_TRAITS, + Self::Ts => &DEFAULT_TRAITS, + Self::Type => &ADDRESSABLE_CONTAINER_TRAITS, + Self::Typescript => &DEFAULT_TRAITS, + Self::Union => &DEFAULT_TRAITS, + Self::User => &DEFAULT_TRAITS, + Self::Val => &DEFAULT_TRAITS, + Self::Variable => &VARIABLE_TRAITS, + Self::Variant => &PACKED_LEAF_TRAITS, + Self::Variants => &DEFAULT_TRAITS, + Self::VersionGate => &DEFAULT_TRAITS, + Self::When => &DEFAULT_TRAITS, + Self::Where => &DEFAULT_TRAITS, + Self::While => &DEFAULT_TRAITS, + Self::With => &GROUP_TRAITS, + Self::Workdir => &DEFAULT_TRAITS, + Self::Chunk => &DEFAULT_TRAITS, + } + } + + pub fn path_segment(self, identifier: Option<&str>) -> String { + match identifier { + Some(identifier) => format!("{}_{identifier}", self.prefix()), + None => self.prefix().to_string(), + } + } + + pub fn from_sanitized_kind(kind: &str) -> Self { + match kind { + "add" => Self::Add, + "after" => Self::After, + "alias" => Self::Alias, + "algo" => Self::Algo, + "arg" => Self::Arg, + "argv" => Self::Argv, + "array" => Self::Array, + "at" => Self::At, + "attr" => Self::Attr, + "attr_expr" => Self::AttrExpr, + "attrs" => Self::Attrs, + "block" => Self::Block, + "block_if" => Self::BlockIf, + "block_locals" => Self::BlockLocals, + "body" => Self::Body, + "case" => Self::Case, + "cases" => Self::Cases, + "catch" => Self::Catch, + "cell" => Self::Cell, + "class" => Self::Class, + "clause" => Self::Clause, + "cmd" => Self::Cmd, + "code" => Self::Code, + "cond" => Self::Cond, + "constructor" => Self::Constructor, + "contract" => Self::Contract, + "copy" => Self::Copy, + "custom" => Self::Custom, + "decls" => Self::Declarations, + "decl" => Self::Decl, + "default_export" => Self::DefaultExport, + "define" => Self::Define, + "directive" => Self::Directive, + "either" => Self::Either, + "elif" => Self::Elif, + "else" => Self::Else, + "enum" => Self::Enum, + "env" => Self::Env, + "error" => Self::Error, + "except" => Self::Except, + "exports" => Self::Exports, + "expose" => Self::Expose, + "expr" => Self::Expression, + "field" => Self::Field, + "fields" => Self::Fields, + "file" => Self::File, + "frame" => Self::Frame, + "fn" => Self::Function, + "for" => Self::For, + "for_in" => Self::ForIn, + "for_of" => Self::ForOf, + "form" => Self::Form, + "frontmatter" => Self::Frontmatter, + "group" => Self::Group, + "group_by" => Self::GroupBy, + "headers" => Self::Headers, + "healthcheck" => Self::Healthcheck, + "html" => Self::Html, + "hunks" => Self::Hunks, + "if" => Self::If, + "iface" => Self::Iface, + "impl" => Self::Impl, + "imports" => Self::Imports, + "includes" => Self::Includes, + "inline_fragment" => Self::InlineFragment, + "install" => Self::Install, + "interface" => Self::Interface, + "interpolation" => Self::Interpolation, + "item" => Self::Item, + "join" => Self::Join, + "key" => Self::Key, + "key_scripts" => Self::KeyScripts, + "label" => Self::Label, + "let" => Self::Let, + "list" => Self::List, + "loop" => Self::Loop, + "macro" => Self::Macro, + "map" => Self::Map, + "markdown" => Self::Markdown, + "match" => Self::Match, + "meth" => Self::Method, + "methods" => Self::Methods, + "mod" => Self::Module, + "mustache" => Self::Mustache, + "object" => Self::Object, + "operation" => Self::Operation, + "operator" => Self::Operator, + "option" => Self::Option, + "options" => Self::Options, + "order_by" => Self::OrderBy, + "params" => Self::Parameters, + "preamble" => Self::Preamble, + "proc" => Self::Proc, + "process" => Self::Process, + "project" => Self::Project, + "proto" => Self::Proto, + "py" => Self::Py, + "python" => Self::Python, + "query" => Self::Query, + "receive" => Self::Receive, + "recipe" => Self::Recipe, + "relations" => Self::Relations, + "render" => Self::Render, + "ret" => Self::Return, + "root" => Self::Root, + "rule" => Self::Rule, + "schema" => Self::Schema, + "script" => Self::Script, + "script_module" => Self::ScriptModule, + "script_setup" => Self::ScriptSetup, + "section" => Self::Section, + "select" => Self::Select, + "setting" => Self::Setting, + "shebang" => Self::Shebang, + "shell" => Self::Shell, + "slot" => Self::Slot, + "snippet" => Self::Snippet, + "source" => Self::Source, + "stage" => Self::Stage, + "static_init" => Self::StaticInit, + "stmts" => Self::Statements, + "struct" => Self::Struct, + "style" => Self::Style, + "style_scoped" => Self::StyleScoped, + "switch" => Self::Switch, + "table" => Self::Table, + "tag" => Self::Tag, + "target" => Self::Target, + "template" => Self::Template, + "text" => Self::Text, + "trait" => Self::Trait, + "translation" => Self::Translation, + "try" => Self::Try, + "ts" => Self::Ts, + "type" => Self::Type, + "typescript" => Self::Typescript, + "union" => Self::Union, + "user" => Self::User, + "val" => Self::Val, + "var" => Self::Variable, + "variant" => Self::Variant, + "variants" => Self::Variants, + "version_gate" => Self::VersionGate, + "when" => Self::When, + "where" => Self::Where, + "while" => Self::While, + "with" => Self::With, + "workdir" => Self::Workdir, + "chunk" => Self::Chunk, + _ => Self::Chunk, + } + } +} diff --git a/crates/pi-natives/src/chunk/mod.rs b/crates/pi-natives/src/chunk/mod.rs index 4b1ac77d5..8295ade4b 100644 --- a/crates/pi-natives/src/chunk/mod.rs +++ b/crates/pi-natives/src/chunk/mod.rs @@ -18,6 +18,8 @@ pub(crate) mod resolve; pub(crate) mod state; pub mod types; +pub mod kind; + // Per-language classifiers mod ast_astro; mod ast_bash_make_diff; @@ -65,6 +67,7 @@ use xxhash_rust::xxh64::xxh64; use self::{ classify::{LangClassifier, classifier_for}, common::*, + kind::ChunkKind, }; pub use self::{ state::ChunkState, @@ -76,8 +79,8 @@ use crate::{chunk::types::ChunkAnchorStyle, language::SupportLang}; /// Format one chunk anchor string for a node at `depth` using `style` and /// optional checksum omission. -#[napi(js_name = "formatAnchor")] -pub fn format_anchor_napi( +#[napi] +pub fn format_anchor( name: String, checksum: String, style: ChunkAnchorStyle, @@ -125,7 +128,8 @@ pub(crate) fn build_chunk_tree(source: &str, language: &str) -> Result ChunkTree { let mut chunks = vec![ChunkNode { path: String::new(), - name: "root".to_string(), + identifier: None, + kind: ChunkKind::Root, leaf: false, parent_path: None, children: Vec::new(), @@ -250,7 +255,8 @@ fn build_blank_line_tree( root_children.push(name.clone()); chunks.push(ChunkNode { path: name.clone(), - name: name.clone(), + identifier: Some(name.clone()), + kind: ChunkKind::Chunk, leaf: true, parent_path: Some(String::new()), children: Vec::new(), @@ -302,10 +308,11 @@ fn build_chunk( acc: &mut ChunkAccumulator, classifier: &dyn classify::LangClassifier, ) -> String { + let segment = candidate.kind.path_segment(candidate.identifier.as_deref()); let path = if parent_path.is_empty() { - candidate.base_name.clone() + segment } else { - format!("{parent_path}.{}", candidate.base_name) + format!("{parent_path}.{segment}") }; let line_count = candidate .range_end_line @@ -331,10 +338,12 @@ fn build_chunk( && recurse.is_some() && recurse_parse_errors == 0 && should_collapse_trivial_children(&candidate, &child_candidates); + let always_recurse = *ALWAYS_RECURSE && !candidate.groupable && !child_candidates.is_empty(); let should_recurse = !candidate.error && recurse.is_some() && !should_collapse && (candidate.force_recurse + || always_recurse || recurse_parse_errors > 0 || (line_count > *LEAF_THRESHOLD && recursion_narrows_scope(line_count, &child_candidates))); @@ -351,7 +360,8 @@ fn build_chunk( let (indent, indent_char) = detect_indent(source, candidate.range_start_byte); acc.chunks.push(ChunkNode { path: path.clone(), - name: candidate.base_name, + identifier: candidate.identifier, + kind: candidate.kind, leaf, parent_path: Some(parent_path.to_string()), children, @@ -432,15 +442,7 @@ fn classify_node<'tree>( classifier: &dyn LangClassifier, ) -> RawChunkCandidate<'tree> { if node.is_error() || node.kind() == "ERROR" { - return make_candidate( - node, - "".to_string(), - NameStyle::Error, - None, - None, - false, - source, - ); + return make_candidate(node, ChunkKind::Error, None, NameStyle::Error, None, None, source); } // Try language-specific classifier first, then fall back to defaults. @@ -499,7 +501,8 @@ fn group_candidates(candidates: Vec>) -> Vec>) -> Vec>) -> Vec> { let mut totals = HashMap::::new(); for candidate in &candidates { - *totals.entry(candidate.base_name.clone()).or_insert(0) += 1; + let key = candidate.kind.path_segment(candidate.identifier.as_deref()); + *totals.entry(key).or_insert(0) += 1; } let mut seen = HashMap::::new(); for candidate in &mut candidates { - let count = seen.entry(candidate.base_name.clone()).or_insert(0); + let key = candidate.kind.path_segment(candidate.identifier.as_deref()); + let count = seen.entry(key.clone()).or_insert(0); *count += 1; let occurrence = *count; - let total = *totals.get(candidate.base_name.as_str()).unwrap_or(&1); + let total = *totals.get(key.as_str()).unwrap_or(&1); - candidate.base_name = match candidate.name_style { + candidate.identifier = match candidate.name_style { NameStyle::Error => { if total > 1 { - format!("error_{occurrence}") + Some(occurrence.to_string()) } else { - "error".to_string() + None } }, NameStyle::Named => { if total > 1 { - format!("{}_{}", candidate.base_name, occurrence) + match candidate.identifier.as_deref() { + Some(identifier) => Some(format!("{identifier}_{occurrence}")), + None => Some(occurrence.to_string()), + } } else { - candidate.base_name.clone() + candidate.identifier.clone() } }, NameStyle::Group => { if total == 1 || occurrence == 1 { - candidate.base_name.clone() + candidate.identifier.clone() } else { - format!("{}_{}", candidate.base_name, occurrence) + match candidate.identifier.as_deref() { + Some(identifier) => Some(format!("{identifier}_{occurrence}")), + None => Some(occurrence.to_string()), + } } }, }; @@ -582,19 +593,11 @@ fn should_collapse_trivial_children( return false; } - let has_addressable_leaf_members = children.iter().all(|child| { - child.base_name.starts_with("field_") || child.base_name.starts_with("variant_") - }); - if has_addressable_leaf_members - && (parent.base_name.starts_with("struct_") - || parent.base_name.starts_with("enum_") - || parent.base_name.starts_with("type_")) - { + let has_addressable_leaf_members = children.iter().all(|child| child.kind.traits().packed); + if has_addressable_leaf_members && parent.kind.traits().has_addressable_members { return false; } - // Trait and interface members should always be addressable so that - // individual method signatures can be edited. - if parent.base_name.starts_with("trait_") || parent.base_name.starts_with("interface_") { + if parent.kind.traits().always_preserve_children { return false; } @@ -738,7 +741,8 @@ fn insert_preamble_chunk( ); let preamble = ChunkNode { path: "preamble".to_string(), - name: "preamble".to_string(), + identifier: None, + kind: ChunkKind::Preamble, leaf: true, parent_path: Some(String::new()), children: Vec::new(), @@ -772,7 +776,7 @@ mod tests { state::ChunkState, types::{ChunkAnchorStyle, ReadRenderParams}, }; - use crate::language::SupportLang; + use crate::{chunk::ChunkKind, language::SupportLang}; fn assert_supported_sample(language: &str, source: &str) { let tree = build_chunk_tree(source, language) @@ -1044,7 +1048,12 @@ function main(): void {{ let tree = build_chunk_tree(source, "typescript").expect("tree should build"); assert!(tree.parse_errors > 0); - assert!(tree.chunks.iter().any(|chunk| chunk.name == "error")); + assert!( + tree + .chunks + .iter() + .any(|chunk| chunk.kind == ChunkKind::Error && chunk.identifier.is_none()) + ); } #[test] @@ -1411,15 +1420,15 @@ impl Config { let attrset = tree .chunks .iter() - .find(|chunk| chunk.path == "attrset_expr") - .expect("attrset_expr chunk"); + .find(|chunk| chunk.path == "attrs") + .expect("attrs chunk"); assert!(!tree.fallback, "nix should use tree-sitter chunking"); assert!(!attrset.leaf, "top-level attrset should recurse into bindings"); assert!( attrset .children .iter() - .any(|child| child == "attrset_expr.attr_hello"), + .any(|child| child == "attrs.attr_hello"), "expected attr_hello child, got {:?}", attrset.children ); @@ -1427,7 +1436,7 @@ impl Config { attrset .children .iter() - .any(|child| child == "attrset_expr.attr_nested"), + .any(|child| child == "attrs.attr_nested"), "expected attr_nested child, got {:?}", attrset.children ); @@ -1624,7 +1633,7 @@ impl Config { .expect("listing should succeed"); assert!(result.text.contains("sample.ts chunks:")); assert!(result.text.contains("fn_run#")); - assert!(result.text.contains("regions: outer, head, inner, tail")); + // Region listing removed — all chunks accept all regions now. assert!(!result.text.contains("return 1")); } diff --git a/crates/pi-natives/src/chunk/render.rs b/crates/pi-natives/src/chunk/render.rs index 01d1c0643..182ca813d 100644 --- a/crates/pi-natives/src/chunk/render.rs +++ b/crates/pi-natives/src/chunk/render.rs @@ -275,13 +275,13 @@ fn chunk_body_anchor_indent( }) } -const fn chunk_anchor_label(chunk: &ChunkNode, style: ChunkAnchorStyle) -> &str { +fn chunk_anchor_label(chunk: &ChunkNode, style: ChunkAnchorStyle) -> String { match style { - ChunkAnchorStyle::Full | ChunkAnchorStyle::FullOmit => chunk.path.as_str(), + ChunkAnchorStyle::Full | ChunkAnchorStyle::FullOmit => chunk.path.clone(), ChunkAnchorStyle::Kind | ChunkAnchorStyle::KindOmit | ChunkAnchorStyle::Bare - | ChunkAnchorStyle::None => chunk.name.as_str(), + | ChunkAnchorStyle::None => chunk.kind.path_segment(chunk.identifier.as_deref()), } } @@ -700,7 +700,10 @@ fn emit_chunk_subtree( ); let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum); let anchor_label = chunk_anchor_label(chunk, style); - push_meta(ctx, style.render(&anchor_indent, anchor_label, chunk.checksum.as_str())); + push_meta( + ctx, + style.render(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str()), + ); return; }, Some(ChunkFocusMode::Container | ChunkFocusMode::Expanded) => { @@ -730,7 +733,7 @@ fn emit_chunk_subtree( ); let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum); let anchor_label = chunk_anchor_label(chunk, style); - push_meta(ctx, style.render(&anchor_indent, anchor_label, chunk.checksum.as_str())); + push_meta(ctx, style.render(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str())); } if !has_kids { if ctx.show_leaf_preview @@ -784,7 +787,10 @@ fn emit_chunk_subtree( ); let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum); let anchor_label = chunk_anchor_label(chunk, style); - push_meta(ctx, style.render_close(&anchor_indent, anchor_label, chunk.checksum.as_str())); + push_meta( + ctx, + style.render_close(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str()), + ); } return; } diff --git a/crates/pi-natives/src/chunk/resolve.rs b/crates/pi-natives/src/chunk/resolve.rs index 1c8f1888d..ea785073f 100644 --- a/crates/pi-natives/src/chunk/resolve.rs +++ b/crates/pi-natives/src/chunk/resolve.rs @@ -5,8 +5,6 @@ use crate::chunk::{ types::{ChunkNode, ChunkRegion, ChunkTree}, }; -const CHUNK_NAME_PREFIXES: &[&str] = - &["fn_", "var_", "class_", "stmts_", "type_", "interface_", "enum_", "const_"]; const CHECKSUM_ALPHABET: &str = "ZPMQVRWSNKTXJBYH"; pub struct ResolvedChunk<'a> { @@ -196,39 +194,15 @@ fn root_chunk(state: &ChunkStateInner) -> Result<&ChunkNode, String> { .ok_or_else(|| "Chunk tree is missing the root chunk".to_owned()) } -pub const fn chunk_supports_region(chunk: &ChunkNode, region: ChunkRegion) -> bool { +pub fn chunk_region_range(chunk: &ChunkNode, region: ChunkRegion) -> (usize, usize) { + let start = chunk.start_byte as usize; + let end = chunk.end_byte as usize; + let pro_end = chunk.prologue_end_byte.map_or(start, |b| b as usize); + let epi_start = chunk.epilogue_start_byte.map_or(end, |b| b as usize); match region { - ChunkRegion::Head | ChunkRegion::Inner | ChunkRegion::Tail => { - chunk.prologue_end_byte.is_some() && chunk.epilogue_start_byte.is_some() - }, - } -} - -pub fn chunk_region_range( - chunk: &ChunkNode, - region: ChunkRegion, -) -> Result<(usize, usize), String> { - match region { - ChunkRegion::Head => Ok(( - chunk.start_byte as usize, - chunk - .prologue_end_byte - .ok_or_else(|| format!("Chunk \"{}\" does not support @head.", chunk.path))? as usize, - )), - ChunkRegion::Inner => Ok(( - chunk - .prologue_end_byte - .ok_or_else(|| format!("Chunk \"{}\" does not support @inner.", chunk.path))? as usize, - chunk - .epilogue_start_byte - .ok_or_else(|| format!("Chunk \"{}\" does not support @inner.", chunk.path))? as usize, - )), - ChunkRegion::Tail => Ok(( - chunk - .epilogue_start_byte - .ok_or_else(|| format!("Chunk \"{}\" does not support @tail.", chunk.path))? as usize, - chunk.end_byte as usize, - )), + ChunkRegion::Head => (start, pro_end), + ChunkRegion::Inner => (pro_end, epi_start), + ChunkRegion::Tail => (epi_start, end), } } @@ -289,12 +263,14 @@ fn resolve_chunk_selector_impl<'a>( } if !cleaned.contains('.') { - let prefixed = CHUNK_NAME_PREFIXES - .iter() - .map(|prefix| format!("{prefix}{cleaned}")) - .collect::>(); - let prefixed_matches = - collect_unique_matches(prefixed.iter().flat_map(|name| state.chunks_by_leaf(name))); + let prefixed_matches = collect_unique_matches(state.tree.chunks.iter().filter(|chunk| { + if chunk.path.is_empty() { + return false; + } + let leaf = chunk.path.rsplit('.').next().unwrap_or(chunk.path.as_str()); + let expected = chunk.kind.path_segment(Some(cleaned)); + leaf == expected || leaf == cleaned + })); if !prefixed_matches.is_empty() { return resolve_matches( prefixed_matches, @@ -472,17 +448,12 @@ fn kind_path_matches(candidate: &ChunkNode, kind_segments: &[&str]) -> bool { .zip(path_segments) .all(|(kind, segment)| { segment == *kind - || segment.starts_with(&format!("{kind}_")) - || strip_known_chunk_prefix(segment) == Some(*kind) + || segment + .split_once('_') + .is_some_and(|(prefix, identifier)| prefix == *kind || identifier == *kind) }) } -fn strip_known_chunk_prefix(segment: &str) -> Option<&str> { - CHUNK_NAME_PREFIXES - .iter() - .find_map(|prefix| segment.strip_prefix(prefix)) -} - /// Format a chunk path with its CRC suffix, e.g. `fn_start#ABCD`. fn format_chunk_ref(tree: &ChunkTree, path: &str) -> String { if let Some(chunk) = find_chunk_by_path(tree, path) { @@ -678,6 +649,7 @@ fn chunk_read_path_separator_index(value: &str) -> Option { #[cfg(test)] mod tests { use super::*; + use crate::chunk::kind::ChunkKind; fn chunk( path: &str, @@ -685,26 +657,36 @@ mod tests { parent_path: Option<&str>, children: Vec<&str>, ) -> ChunkNode { + let leaf = path.rsplit('.').next().unwrap_or(path); + let kind = match leaf.split_once('_').map_or(leaf, |(prefix, _)| prefix) { + "fn" => ChunkKind::Function, + "class" => ChunkKind::Class, + "try" => ChunkKind::Try, + _ => ChunkKind::Chunk, + }; ChunkNode { - path: path.to_owned(), - name: path.rsplit('.').next().unwrap_or(path).to_owned(), - leaf: children.is_empty(), - parent_path: parent_path.map(str::to_owned), - children: children.into_iter().map(str::to_owned).collect(), - signature: None, - start_line: 1, - end_line: 1, - line_count: 1, - start_byte: 0, - end_byte: 0, + path: path.to_owned(), + identifier: leaf + .split_once('_') + .and_then(|(_, identifier)| (!identifier.is_empty()).then_some(identifier.to_owned())), + kind, + leaf: children.is_empty(), + parent_path: parent_path.map(str::to_owned), + children: children.into_iter().map(str::to_owned).collect(), + signature: None, + start_line: 1, + end_line: 1, + line_count: 1, + start_byte: 0, + end_byte: 0, checksum_start_byte: 0, - prologue_end_byte: None, + prologue_end_byte: None, epilogue_start_byte: None, - checksum: checksum.to_owned(), - error: false, - indent: 0, - indent_char: " ".to_owned(), - group: false, + checksum: checksum.to_owned(), + error: false, + indent: 0, + indent_char: " ".to_owned(), + group: false, } } diff --git a/crates/pi-natives/src/chunk/state.rs b/crates/pi-natives/src/chunk/state.rs index 7efdf2e39..2b7934e75 100644 --- a/crates/pi-natives/src/chunk/state.rs +++ b/crates/pi-natives/src/chunk/state.rs @@ -11,8 +11,8 @@ use super::{ build_chunk_tree, indent::{detect_file_indent_char, detect_file_indent_step, normalize_to_tabs}, resolve::{ - ParsedSelector, chunk_region_range, chunk_supports_region, format_region_ref, - resolve_chunk_selector, resolve_chunk_with_crc, split_selector_crc_and_region, + ParsedSelector, chunk_region_range, format_region_ref, resolve_chunk_selector, + resolve_chunk_with_crc, split_selector_crc_and_region, }, }; use crate::chunk::types::{ @@ -273,8 +273,8 @@ impl ChunkState { } /// Look up [`ChunkInfo`] for a chunk selector path. - #[napi(js_name = "chunk")] - pub fn chunk_info_for_path(&self, chunk_path: String) -> Option { + #[napi] + pub fn chunk(&self, chunk_path: String) -> Option { let mut warnings = Vec::new(); resolve_chunk_selector(self.inner(), Some(chunk_path.as_str()), &mut warnings) .ok() @@ -413,14 +413,9 @@ impl ChunkState { if selector.as_deref() == Some("?") { let mut lines = vec![format!("{} chunks:", params.display_path)]; for chunk in self.inner.chunks().filter(|chunk| !chunk.path.is_empty()) { - let supported_regions = if chunk_supports_region(chunk, ChunkRegion::Inner) { - "outer, head, inner, tail" - } else { - "outer" - }; lines.push(format!( - " {}#{} L{}-L{} regions: {}", - chunk.path, chunk.checksum, chunk.start_line, chunk.end_line, supported_regions + " {}#{} L{}-L{}", + chunk.path, chunk.checksum, chunk.start_line, chunk.end_line, )); } return Ok(ReadResult { text: lines.join("\n"), chunk: None }); @@ -448,24 +443,6 @@ impl ChunkState { // selector and loses the region suffix. let selector_ref = format_region_ref(chunk, region); - if let Some(r) = region - && !chunk_supports_region(chunk, r) - { - return Ok(ReadResult { - text: format!( - "{}:{}\n\nChunk \"{}\" does not support @{}.", - params.display_path, - chunk.path, - chunk.path, - r.as_str(), - ), - chunk: Some(ChunkReadTarget { - status: ChunkReadStatus::UnsupportedRegion, - selector: selector_ref, - }), - }); - } - if let Some(absolute_line_range) = params.absolute_line_range { let req_start = absolute_line_range.start_line; let req_end = absolute_line_range.end_line; @@ -492,18 +469,7 @@ impl ChunkState { if let Some(target_region) = region { let masked_source = mask_chunk_display_source(self.inner.source(), self.inner.language()); - let (start, end) = match chunk_region_range(chunk, target_region) { - Ok(range) => range, - Err(err) => { - return Ok(ReadResult { - text: format!("{}\n\n{}", params.display_path, err), - chunk: Some(ChunkReadTarget { - status: ChunkReadStatus::UnsupportedRegion, - selector: selector_ref, - }), - }); - }, - }; + let (start, end) = chunk_region_range(chunk, target_region); let tab_replacement = params.tab_replacement.as_deref().unwrap_or(" "); let normalize_indent = params.normalize_indent.unwrap_or(false).then(|| { ( @@ -598,7 +564,7 @@ fn normalize_language(language: &str) -> String { fn chunk_info(chunk: &ChunkNode) -> ChunkInfo { ChunkInfo { path: chunk.path.clone(), - name: chunk.name.clone(), + identifier: chunk.identifier.clone(), checksum: chunk.checksum.clone(), start_line: chunk.start_line, end_line: chunk.end_line, diff --git a/crates/pi-natives/src/chunk/types.rs b/crates/pi-natives/src/chunk/types.rs index 03b4899aa..ca91b88e4 100644 --- a/crates/pi-natives/src/chunk/types.rs +++ b/crates/pi-natives/src/chunk/types.rs @@ -7,7 +7,7 @@ use crate::chunk::{kind::ChunkKind, state::ChunkState}; #[derive(Clone)] pub struct ChunkNode { pub path: String, - pub name: String, + pub identifier: Option, pub kind: ChunkKind, pub leaf: bool, pub parent_path: Option, @@ -58,8 +58,8 @@ pub struct ChunkTree { pub struct ChunkInfo { /// Chunk selector path within the tree. pub path: String, - /// Short display name for the chunk (e.g. symbol or region label). - pub name: String, + /// Bare chunk identifier (without kind prefix), if available. + pub identifier: Option, /// Stable checksum anchor for this chunk. pub checksum: String, /// 1-based start line in the source file (inclusive). diff --git a/crates/pi-natives/src/grep.rs b/crates/pi-natives/src/grep.rs index e7c89929f..d676b55e0 100644 --- a/crates/pi-natives/src/grep.rs +++ b/crates/pi-natives/src/grep.rs @@ -1390,7 +1390,7 @@ fn grep_sync( let use_gitignore = options.gitignore.unwrap_or(true); let use_cache = options.cache.unwrap_or(false); let glob_set = glob_util::try_compile_glob(options.glob.as_deref(), true)?; - let type_filter = resolve_type_filter(options.r#type.as_deref()); + let type_filter = resolve_type_filter(options.type_filter.as_deref()); let params = SearchParams { context_before, diff --git a/packages/natives/CHANGELOG.md b/packages/natives/CHANGELOG.md index 069d7a8bb..ef61e3cb7 100644 --- a/packages/natives/CHANGELOG.md +++ b/packages/natives/CHANGELOG.md @@ -1,7 +1,6 @@ # Changelog ## [Unreleased] - ### Breaking Changes - Changed `ChunkRegion` enum values from `Container`, `Prologue`, `Body`, `Epilogue` to `Head`, `Inner`, `Tail` with updated semantics for region targeting @@ -28,6 +27,7 @@ ### Changed +- Changed `ChunkInfo.name` field to optional `identifier` field — now provides bare chunk identifier without kind prefix instead of display name - Updated `region` parameter documentation in `EditOperation` to clarify full chunk targeting when omitted instead of container-scoped default - Updated `ChunkEditOp` documentation to reflect region-scoped semantics — operations now target specific regions rather than chunk structure positions - Changed `ChunkEditOp.Replace` documentation to clarify substring replacement via `find` parameter instead of line-based replacement diff --git a/packages/natives/native/index.d.ts b/packages/natives/native/index.d.ts index 584156a96..1135a234a 100644 --- a/packages/natives/native/index.d.ts +++ b/packages/natives/native/index.d.ts @@ -412,8 +412,8 @@ export declare enum ChunkFocusMode { export interface ChunkInfo { /** Chunk selector path within the tree. */ path: string - /** Short display name for the chunk (e.g. symbol or region label). */ - name: string + /** Bare chunk identifier (without kind prefix), if available. */ + identifier?: string /** Stable checksum anchor for this chunk. */ checksum: string /** 1-based start line in the source file (inclusive). */