Skip to main content

code_moniker_workspace/code/
symbols.rs

1use std::path::PathBuf;
2
3use code_moniker_core::core::code_graph::{DefRecord, RefRecord};
4use code_moniker_core::core::moniker::Moniker;
5use code_moniker_core::core::uri::{UriConfig, from_uri};
6use code_moniker_core::lang::Lang;
7
8use crate::environment;
9use crate::snapshot::{SourceId, SymbolId, SymbolLocation};
10use crate::source::CodeIndexMaterial;
11
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub struct NormalizedSource {
14	pub id: SourceId,
15	pub uri: String,
16	pub language: Lang,
17	pub rel_path: PathBuf,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct NormalizedSymbol {
22	pub id: SymbolId,
23	pub source: NormalizedSource,
24	pub identity: String,
25}
26
27pub struct CodeIndexSymbolProvider<'a> {
28	material: &'a CodeIndexMaterial,
29}
30
31impl<'a> CodeIndexSymbolProvider<'a> {
32	pub fn new(material: &'a CodeIndexMaterial) -> Self {
33		Self { material }
34	}
35
36	pub fn source_at(&self, file_idx: usize) -> Option<NormalizedSource> {
37		let file = self.material.files.get(file_idx)?;
38		Some(NormalizedSource {
39			id: file.source_id,
40			uri: file.source_uri.clone(),
41			language: file.lang,
42			rel_path: file.rel_path.clone(),
43		})
44	}
45
46	pub fn symbol_at(&self, loc: SymbolLocation) -> Option<NormalizedSymbol> {
47		let file = self.material.files.get(loc.file)?;
48		let def = file.graph.defs().nth(loc.symbol)?;
49		let source = self.source_at(loc.file)?;
50		Some(NormalizedSymbol {
51			id: file.identity.symbol_id(loc.file, loc.symbol),
52			source,
53			identity: self.material.identity.moniker_uri(&def.moniker),
54		})
55	}
56
57	pub fn symbol_for_moniker(&self, moniker: &Moniker) -> Option<NormalizedSymbol> {
58		let id = self.material.symbols_by_moniker.get(moniker)?;
59		let (file, symbol) = self.material.identity.symbol_location(id)?;
60		self.symbol_at(SymbolLocation { file, symbol })
61	}
62
63	pub fn identity_for_moniker(&self, moniker: &Moniker) -> String {
64		self.material.identity.moniker_uri(moniker)
65	}
66}
67
68pub fn is_navigable_def(lang: Lang, def: &DefRecord) -> bool {
69	lang.kind_spec(&def_kind(def)).is_some()
70}
71
72pub fn def_kind(def: &DefRecord) -> String {
73	std::str::from_utf8(&def.kind).unwrap_or("?").to_string()
74}
75
76pub fn ref_kind(reference: &RefRecord) -> String {
77	std::str::from_utf8(&reference.kind)
78		.unwrap_or("?")
79		.to_string()
80}
81
82pub fn last_name(moniker: &Moniker) -> String {
83	moniker
84		.as_view()
85		.segments()
86		.last()
87		.and_then(|s| std::str::from_utf8(s.name).ok())
88		.unwrap_or(".")
89		.to_string()
90}
91
92pub fn compact_moniker(moniker: &Moniker) -> String {
93	environment::compact_moniker(moniker, crate::DEFAULT_IDENTITY_SCHEME)
94}
95
96pub fn compact_identity(identity: &str, scheme: &str) -> Option<String> {
97	let moniker = from_uri(identity, &UriConfig { scheme }).ok()?;
98	Some(environment::compact_moniker(&moniker, scheme))
99}
100
101#[cfg(test)]
102mod tests {
103	use super::*;
104
105	#[test]
106	fn compact_identity_handles_complex_callable_signatures() {
107		let identity = concat!(
108			"code+moniker://./lang:rs/dir:crates/dir:cli/dir:src/module:mcp/",
109			"module:tools/module:notes/fn:render_notes_result(scheme:&str,",
110			"request:&NoteRequest,result:&NotesResult,",
111			"next:Option<&code_moniker_query::QueryCursor>)"
112		);
113		let parsed = from_uri(
114			identity,
115			&UriConfig {
116				scheme: "code+moniker://",
117			},
118		);
119		assert!(parsed.is_ok(), "{parsed:?}");
120		assert_eq!(
121			compact_identity(identity, "code+moniker://").as_deref(),
122			Some(
123				"rs:crates/cli/src/mcp.tools.notes.fn:render_notes_result(scheme:&str,request:&NoteRequest,result:&NotesResult,next:Option<&code_moniker_query::QueryCursor>)"
124			)
125		);
126	}
127}