code_moniker_workspace/code/
symbols.rs1use 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}