Skip to main content

oak_resolver/
lib.rs

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