code-moniker-workspace 0.6.0

Workspace model, ports, snapshots, linkage, and change analysis for code-moniker.
Documentation
use std::path::PathBuf;

use code_moniker_core::core::code_graph::{DefRecord, RefRecord};
use code_moniker_core::core::moniker::Moniker;
use code_moniker_core::core::uri::{UriConfig, from_uri};
use code_moniker_core::lang::Lang;

use crate::environment;
use crate::snapshot::{SourceId, SymbolId, SymbolLocation};
use crate::source::CodeIndexMaterial;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NormalizedSource {
	pub id: SourceId,
	pub uri: String,
	pub language: Lang,
	pub rel_path: PathBuf,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NormalizedSymbol {
	pub id: SymbolId,
	pub source: NormalizedSource,
	pub identity: String,
}

pub struct CodeIndexSymbolProvider<'a> {
	material: &'a CodeIndexMaterial,
}

impl<'a> CodeIndexSymbolProvider<'a> {
	pub fn new(material: &'a CodeIndexMaterial) -> Self {
		Self { material }
	}

	pub fn source_at(&self, file_idx: usize) -> Option<NormalizedSource> {
		let file = self.material.files.get(file_idx)?;
		Some(NormalizedSource {
			id: file.source_id,
			uri: file.source_uri.clone(),
			language: file.lang,
			rel_path: file.rel_path.clone(),
		})
	}

	pub fn symbol_at(&self, loc: SymbolLocation) -> Option<NormalizedSymbol> {
		let file = self.material.files.get(loc.file)?;
		let def = file.graph.defs().nth(loc.symbol)?;
		let source = self.source_at(loc.file)?;
		Some(NormalizedSymbol {
			id: file.identity.symbol_id(loc.file, loc.symbol),
			source,
			identity: self.material.identity.moniker_uri(&def.moniker),
		})
	}

	pub fn symbol_for_moniker(&self, moniker: &Moniker) -> Option<NormalizedSymbol> {
		let id = self.material.symbols_by_moniker.get(moniker)?;
		let (file, symbol) = self.material.identity.symbol_location(id)?;
		self.symbol_at(SymbolLocation { file, symbol })
	}

	pub fn identity_for_moniker(&self, moniker: &Moniker) -> String {
		self.material.identity.moniker_uri(moniker)
	}
}

pub fn is_navigable_def(lang: Lang, def: &DefRecord) -> bool {
	lang.kind_spec(&def_kind(def)).is_some()
}

pub fn def_kind(def: &DefRecord) -> String {
	std::str::from_utf8(&def.kind).unwrap_or("?").to_string()
}

pub fn ref_kind(reference: &RefRecord) -> String {
	std::str::from_utf8(&reference.kind)
		.unwrap_or("?")
		.to_string()
}

pub fn last_name(moniker: &Moniker) -> String {
	moniker
		.as_view()
		.segments()
		.last()
		.and_then(|s| std::str::from_utf8(s.name).ok())
		.unwrap_or(".")
		.to_string()
}

pub fn compact_moniker(moniker: &Moniker) -> String {
	environment::compact_moniker(moniker, crate::DEFAULT_IDENTITY_SCHEME)
}

pub fn compact_identity(identity: &str, scheme: &str) -> Option<String> {
	let moniker = from_uri(identity, &UriConfig { scheme }).ok()?;
	Some(environment::compact_moniker(&moniker, scheme))
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn compact_identity_handles_complex_callable_signatures() {
		let identity = concat!(
			"code+moniker://./lang:rs/dir:crates/dir:cli/dir:src/module:mcp/",
			"module:tools/module:notes/fn:render_notes_result(scheme:&str,",
			"request:&NoteRequest,result:&NotesResult,",
			"next:Option<&code_moniker_query::QueryCursor>)"
		);
		let parsed = from_uri(
			identity,
			&UriConfig {
				scheme: "code+moniker://",
			},
		);
		assert!(parsed.is_ok(), "{parsed:?}");
		assert_eq!(
			compact_identity(identity, "code+moniker://").as_deref(),
			Some(
				"rs:crates/cli/src/mcp.tools.notes.fn:render_notes_result(scheme:&str,request:&NoteRequest,result:&NotesResult,next:Option<&code_moniker_query::QueryCursor>)"
			)
		);
	}
}