Skip to main content

codelens_engine/symbols/
types.rs

1use crate::db::IndexDb;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
5#[serde(rename_all = "snake_case")]
6pub enum SymbolKind {
7    File,
8    Class,
9    Interface,
10    Enum,
11    Module,
12    Method,
13    Function,
14    Property,
15    Variable,
16    TypeAlias,
17    Unknown,
18}
19
20impl SymbolKind {
21    pub fn as_label(&self) -> &'static str {
22        match self {
23            SymbolKind::File => "file",
24            SymbolKind::Class => "class",
25            SymbolKind::Interface => "interface",
26            SymbolKind::Enum => "enum",
27            SymbolKind::Module => "module",
28            SymbolKind::Method => "method",
29            SymbolKind::Function => "function",
30            SymbolKind::Property => "property",
31            SymbolKind::Variable => "variable",
32            SymbolKind::TypeAlias => "type_alias",
33            SymbolKind::Unknown => "unknown",
34        }
35    }
36
37    pub fn from_str_label(s: &str) -> SymbolKind {
38        match s {
39            "class" => SymbolKind::Class,
40            "interface" => SymbolKind::Interface,
41            "enum" => SymbolKind::Enum,
42            "module" => SymbolKind::Module,
43            "method" => SymbolKind::Method,
44            "function" => SymbolKind::Function,
45            "property" => SymbolKind::Property,
46            "variable" => SymbolKind::Variable,
47            "type_alias" => SymbolKind::TypeAlias,
48            _ => SymbolKind::Unknown,
49        }
50    }
51}
52
53#[derive(Debug, Clone, Serialize)]
54pub struct SymbolInfo {
55    pub name: String,
56    pub kind: SymbolKind,
57    pub file_path: String,
58    pub line: usize,
59    pub column: usize,
60    pub signature: String,
61    pub name_path: String,
62    pub id: String,
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub body: Option<String>,
65    #[serde(default, skip_serializing_if = "Vec::is_empty")]
66    pub children: Vec<SymbolInfo>,
67    /// Byte offsets for batch body extraction (not serialized to API output).
68    /// u32 saves 8 bytes per symbol vs usize; sufficient for files up to 4GB.
69    #[serde(skip)]
70    pub start_byte: u32,
71    #[serde(skip)]
72    pub end_byte: u32,
73}
74
75/// Construct a stable symbol ID: `{file_path}#{kind}:{name_path}`
76///
77/// Uses `String::with_capacity` to allocate the exact final size in
78/// one shot, avoiding the internal reallocation that `format!()` may
79/// do when it starts from an empty buffer and grows.
80pub fn make_symbol_id(file_path: &str, kind: &SymbolKind, name_path: &str) -> String {
81    let label = kind.as_label();
82    let mut id = String::with_capacity(file_path.len() + 1 + label.len() + 1 + name_path.len());
83    id.push_str(file_path);
84    id.push('#');
85    id.push_str(label);
86    id.push(':');
87    id.push_str(name_path);
88    id
89}
90
91/// Parse a stable symbol ID. Returns `(file_path, kind_label, name_path)` or `None`.
92pub fn parse_symbol_id(input: &str) -> Option<(&str, &str, &str)> {
93    let hash_pos = input.find('#')?;
94    let after_hash = &input[hash_pos + 1..];
95    let colon_pos = after_hash.find(':')?;
96    let file_path = &input[..hash_pos];
97    let kind = &after_hash[..colon_pos];
98    let name_path = &after_hash[colon_pos + 1..];
99    if file_path.is_empty() || kind.is_empty() || name_path.is_empty() {
100        return None;
101    }
102    Some((file_path, kind, name_path))
103}
104
105#[derive(Debug, Clone, Serialize)]
106pub struct IndexStats {
107    pub indexed_files: usize,
108    pub supported_files: usize,
109    pub stale_files: usize,
110}
111
112#[derive(Debug, Clone, Serialize)]
113pub struct RankedContextEntry {
114    pub name: String,
115    pub kind: String,
116    pub file: String,
117    pub line: usize,
118    pub signature: String,
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub body: Option<String>,
121    pub relevance_score: i32,
122}
123
124#[derive(Debug, Clone, Serialize)]
125pub struct RankedContextResult {
126    pub query: String,
127    pub symbols: Vec<RankedContextEntry>,
128    pub count: usize,
129    pub token_budget: usize,
130    pub chars_used: usize,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub(crate) struct ParsedSymbol {
135    pub name: String,
136    pub kind: SymbolKind,
137    pub file_path: String,
138    pub line: usize,
139    pub column: usize,
140    pub start_byte: u32,
141    pub end_byte: u32,
142    pub signature: String,
143    pub body: Option<String>,
144    pub name_path: String,
145    pub children: Vec<ParsedSymbol>,
146}
147
148/// Read-only DB access — either an owned read-only connection or a borrowed writer guard.
149pub(crate) enum ReadDb<'a> {
150    Owned(IndexDb),
151    Writer(std::sync::MutexGuard<'a, IndexDb>),
152}
153
154/// Intermediate result of analyzing a single file.
155/// Decouples parse phase from DB write phase, enabling:
156/// - Parallel parse (rayon) → sequential DB commit
157/// - Failure tracking without losing previously indexed data
158/// - Future: async pipeline stages
159pub(crate) struct AnalyzedFile {
160    pub relative_path: String,
161    pub mtime: i64,
162    pub content_hash: String,
163    pub size_bytes: i64,
164    pub language_ext: String,
165    pub symbols: Vec<ParsedSymbol>,
166    pub imports: Vec<crate::db::NewImport>,
167    pub calls: Vec<crate::db::NewCall>,
168}
169
170impl std::ops::Deref for ReadDb<'_> {
171    type Target = IndexDb;
172    fn deref(&self) -> &IndexDb {
173        match self {
174            ReadDb::Owned(db) => db,
175            ReadDb::Writer(guard) => guard,
176        }
177    }
178}