Skip to main content

oak_resolver/
lib.rs

1#![warn(missing_docs)]
2#![doc = include_str!("readme.md")]
3
4use dashmap::DashMap;
5use oak_symbols::SymbolInformation;
6use std::path::PathBuf;
7use url::Url;
8
9use std::sync::RwLock;
10
11/// Trait for resolving module imports to file URIs.
12pub trait ModuleResolver: Send + Sync {
13    /// Resolve an import path relative to a base URI.
14    fn resolve(&self, base_uri: &str, import_path: &str) -> Option<String>;
15}
16
17/// A standard resolver that handles relative and absolute file paths.
18pub struct StandardResolver {
19    /// The list of root directories to search for modules.
20    root_dirs: RwLock<Vec<PathBuf>>,
21}
22
23impl StandardResolver {
24    /// Creates a new standard resolver with the given root directories.
25    pub fn new(root_dirs: Vec<PathBuf>) -> Self {
26        Self { root_dirs: RwLock::new(root_dirs) }
27    }
28
29    /// Update the root directories used for resolution.
30    pub fn set_root_dirs(&self, root_dirs: Vec<PathBuf>) {
31        if let Ok(mut dirs) = self.root_dirs.write() {
32            *dirs = root_dirs;
33        }
34    }
35}
36
37impl ModuleResolver for StandardResolver {
38    fn resolve(&self, base_uri: &str, import_path: &str) -> Option<String> {
39        let root_dirs = self.root_dirs.read().ok()?;
40
41        if let Ok(base_url) = Url::parse(base_uri) {
42            if let Ok(base_path) = base_url.to_file_path() {
43                let base_dir = base_path.parent()?;
44                let resolved_path = base_dir.join(import_path);
45
46                if resolved_path.exists() {
47                    return Url::from_file_path(resolved_path).ok().map(|u| u.to_string());
48                }
49            }
50        }
51
52        // Try root directories (like PYTHONPATH or node_modules logic)
53        for root in root_dirs.iter() {
54            let resolved_path = root.join(import_path);
55            if resolved_path.exists() {
56                return Url::from_file_path(resolved_path).ok().map(|u| u.to_string());
57            }
58        }
59
60        None
61    }
62}
63
64/// Global symbol table that stores symbols across the entire workspace.
65pub struct GlobalSymbolTable {
66    /// Map of URI to symbols defined in that file.
67    file_symbols: DashMap<String, Vec<SymbolInformation>>,
68    /// Map of fully qualified name to symbol information.
69    qualified_symbols: DashMap<String, SymbolInformation>,
70}
71
72impl GlobalSymbolTable {
73    /// Creates a new, empty `GlobalSymbolTable`.
74    pub fn new() -> Self {
75        Self { file_symbols: DashMap::new(), qualified_symbols: DashMap::new() }
76    }
77
78    /// Add or update symbols for a file.
79    pub fn update_file_symbols(&self, uri: String, symbols: Vec<SymbolInformation>) {
80        // Remove old qualified symbols for this file
81        if let Some((_, old_symbols)) = self.file_symbols.remove(&uri) {
82            for sym in old_symbols {
83                let fqn = self.make_qualified_name(&sym);
84                self.qualified_symbols.remove(&fqn);
85            }
86        }
87
88        // Add new symbols
89        for sym in &symbols {
90            let fqn = self.make_qualified_name(sym);
91            self.qualified_symbols.insert(fqn, sym.clone());
92        }
93        self.file_symbols.insert(uri, symbols);
94    }
95
96    fn make_qualified_name(&self, sym: &SymbolInformation) -> String {
97        match &sym.container_name {
98            Some(container) => format!("{}::{}", container, sym.name),
99            None => sym.name.clone(),
100        }
101    }
102
103    /// Lookup a symbol by its fully qualified name.
104    pub fn lookup(&self, fqn: &str) -> Option<SymbolInformation> {
105        self.qualified_symbols.get(fqn).map(|r| r.value().clone())
106    }
107
108    /// Get all symbols defined in a specific file.
109    pub fn query_file(&self, uri: &str) -> Vec<SymbolInformation> {
110        self.file_symbols.get(uri).map(|r| r.value().clone()).unwrap_or_default()
111    }
112
113    /// Find all symbols matching a query (for workspace/symbol).
114    pub fn query(&self, query: &str) -> Vec<SymbolInformation> {
115        self.qualified_symbols.iter().filter(|r| r.key().contains(query)).map(|r| r.value().clone()).collect()
116    }
117}
118
119impl Default for GlobalSymbolTable {
120    fn default() -> Self {
121        Self::new()
122    }
123}