Skip to main content

hearth_graph/symbols/
mod.rs

1//! Tree-sitter tags based symbol extraction.
2//!
3//! Symbols come from concrete syntax trees, so definition-like text in
4//! comments and strings does not pollute file outlines.
5
6use compact_str::CompactString;
7
8mod extract;
9mod index;
10mod score;
11
12pub use extract::extract_symbols;
13pub(crate) use extract::extract_symbols_from_tree;
14pub use index::{SymbolIndex, UpsertOutcome};
15
16/// Maximum number of symbols retained per file.
17///
18/// This bounds the memory used by outlines for pathological generated source.
19pub const MAX_SYMBOLS_PER_FILE: usize = 10_000;
20
21/// The kind of a named entity, derived from an `@definition.*` capture.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
23pub enum SymbolKind {
24    /// A free-standing function.
25    Function,
26    /// A function attached to a type or object.
27    Method,
28    /// A class-like declaration, including structs, enums, and unions.
29    Class,
30    /// An interface-like declaration, including traits and protocols.
31    Interface,
32    /// A module-like declaration, including namespaces and packages.
33    Module,
34    /// A macro definition.
35    Macro,
36    /// A constant definition.
37    Constant,
38    /// A type alias or other type declaration.
39    Type,
40    /// A field declaration.
41    Field,
42    /// A property declaration.
43    Property,
44    /// A Markdown heading.
45    Heading,
46}
47
48impl SymbolKind {
49    /// Maps a tags capture name to a symbol kind.
50    fn from_capture(capture: &str) -> Option<Self> {
51        match capture.strip_prefix("definition.")? {
52            "function" => Some(Self::Function),
53            "method" => Some(Self::Method),
54            "class" | "struct" | "enum" | "union" => Some(Self::Class),
55            "interface" | "trait" | "protocol" => Some(Self::Interface),
56            "module" | "namespace" | "package" => Some(Self::Module),
57            "macro" => Some(Self::Macro),
58            "constant" => Some(Self::Constant),
59            "type" => Some(Self::Type),
60            "field" => Some(Self::Field),
61            "property" => Some(Self::Property),
62            "heading" => Some(Self::Heading),
63            _ => None,
64        }
65    }
66
67    /// Returns the single-character glyph used in outline and search rows.
68    pub fn glyph(self) -> &'static str {
69        match self {
70            Self::Function => "ƒ",
71            Self::Method => "m",
72            Self::Class => "C",
73            Self::Interface => "I",
74            Self::Module => "M",
75            Self::Macro => "!",
76            Self::Constant => "c",
77            Self::Type => "T",
78            Self::Field => "f",
79            Self::Property => "p",
80            Self::Heading => "#",
81        }
82    }
83}
84
85/// A named entity in a source file.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct Symbol {
88    /// The source spelling of the symbol name.
89    pub name: CompactString,
90    /// The definition kind reported by the tags query.
91    pub kind: SymbolKind,
92    /// The 1-based line of the symbol name.
93    pub line: u32,
94    /// The 0-based character column of the symbol name.
95    pub column: u32,
96    /// The nesting depth of enclosing definitions.
97    pub depth: u16,
98    /// The byte offset where the symbol name begins.
99    pub name_start: u32,
100    /// The byte offset where the definition begins.
101    pub def_start: u32,
102    /// The exclusive byte offset where the definition ends.
103    pub def_end: u32,
104}
105
106/// Symbols extracted from one file and tied to its source content.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct FileSymbols {
109    /// Repository-relative source path.
110    pub path: CompactString,
111    /// Content hash supplied by the host.
112    pub content_hash: u64,
113    /// Symbols in source order.
114    pub symbols: Vec<Symbol>,
115}
116
117/// A symbol together with the file it lives in.
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub struct SymbolRef<'a> {
120    /// Repository-relative source path.
121    pub path: &'a str,
122    /// The referenced symbol.
123    pub symbol: &'a Symbol,
124}
125
126impl SymbolRef<'_> {
127    /// Formats a symbol-search row as `ƒ name  path:line`.
128    pub fn search_label(&self) -> String {
129        format!(
130            "{} {}  {}:{}",
131            self.symbol.kind.glyph(),
132            self.symbol.name,
133            self.path,
134            self.symbol.line
135        )
136    }
137}
138
139/// Returns the preference rank for duplicate captures of one name node.
140///
141/// Lower values are more specific and win before definition-span length.
142pub fn kind_specificity(kind: SymbolKind) -> u8 {
143    match kind {
144        SymbolKind::Method => 0,
145        SymbolKind::Property => 1,
146        SymbolKind::Field => 2,
147        SymbolKind::Constant => 3,
148        SymbolKind::Macro => 4,
149        SymbolKind::Function => 5,
150        SymbolKind::Interface => 6,
151        SymbolKind::Class => 7,
152        SymbolKind::Type => 8,
153        SymbolKind::Module => 9,
154        SymbolKind::Heading => 10,
155    }
156}
157
158/// Returns the ordering rank for choosing a likely jump destination.
159///
160/// Lower values represent definitions users are more likely to seek.
161pub fn jump_priority(kind: SymbolKind) -> u8 {
162    match kind {
163        SymbolKind::Class | SymbolKind::Interface | SymbolKind::Type => 0,
164        SymbolKind::Function | SymbolKind::Method | SymbolKind::Macro => 1,
165        SymbolKind::Constant | SymbolKind::Module => 2,
166        SymbolKind::Field | SymbolKind::Property | SymbolKind::Heading => 3,
167    }
168}