Skip to content

Commit fc1fe5b

Browse files
committed
Add symbols imported in packages to workspace
1 parent 1e3b986 commit fc1fe5b

File tree

6 files changed

+87
-21
lines changed

6 files changed

+87
-21
lines changed

crates/ark/src/lsp/diagnostics.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ use crate::lsp::encoding::convert_tree_sitter_range_to_lsp_range;
3030
use crate::lsp::indexer;
3131
use crate::lsp::inputs::library::Library;
3232
use crate::lsp::inputs::package::Package;
33+
use crate::lsp::inputs::source_root::SourceRoot;
3334
use crate::lsp::state::WorldState;
3435
use crate::lsp::traits::node::NodeExt;
3536
use crate::lsp::traits::rope::RopeExt;
@@ -62,6 +63,9 @@ pub struct DiagnosticContext<'a> {
6263
// The set of packages that are currently installed.
6364
pub installed_packages: HashSet<String>,
6465

66+
/// Reference to source root, if any.
67+
pub root: &'a Option<SourceRoot>,
68+
6569
/// Reference to the library for looking up package exports.
6670
pub library: &'a Library,
6771

@@ -83,13 +87,14 @@ impl Default for DiagnosticsConfig {
8387
}
8488

8589
impl<'a> DiagnosticContext<'a> {
86-
pub fn new(contents: &'a Rope, library: &'a Library) -> Self {
90+
pub fn new(contents: &'a Rope, root: &'a Option<SourceRoot>, library: &'a Library) -> Self {
8791
Self {
8892
contents,
8993
document_symbols: Vec::new(),
9094
session_symbols: HashSet::new(),
9195
workspace_symbols: HashSet::new(),
9296
installed_packages: HashSet::new(),
97+
root,
9398
library,
9499
library_symbols: BTreeMap::new(),
95100
in_formula: false,
@@ -144,7 +149,7 @@ pub(crate) fn generate_diagnostics(doc: Document, state: WorldState) -> Vec<Diag
144149
return diagnostics;
145150
}
146151

147-
let mut context = DiagnosticContext::new(&doc.contents, &state.library);
152+
let mut context = DiagnosticContext::new(&doc.contents, &state.root, &state.library);
148153

149154
// Add a 'root' context for the document.
150155
context.document_symbols.push(HashMap::new());
@@ -157,6 +162,23 @@ pub(crate) fn generate_diagnostics(doc: Document, state: WorldState) -> Vec<Diag
157162
_ => {},
158163
});
159164

165+
// If this is a package, add imported symbols to workspace
166+
if let Some(SourceRoot::Package(root)) = &state.root {
167+
// Add symbols from `importFrom()` directives
168+
for import in &root.namespace.imports {
169+
context.workspace_symbols.insert(import.clone());
170+
}
171+
172+
// Add symbols from `import()` directives
173+
for package_import in &root.namespace.package_imports {
174+
if let Some(pkg) = state.library.get(package_import) {
175+
for export in &pkg.namespace.exports {
176+
context.workspace_symbols.insert(export.clone());
177+
}
178+
}
179+
}
180+
}
181+
160182
// Add per-environment session symbols
161183
for scope in state.console_scopes.iter() {
162184
for name in scope.iter() {

crates/ark/src/lsp/diagnostics_syntax.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ mod tests {
314314
fn text_diagnostics(text: &str) -> Vec<Diagnostic> {
315315
let document = Document::new(text, None);
316316
let library = Library::default();
317-
let context = DiagnosticContext::new(&document.contents, &library);
317+
let context = DiagnosticContext::new(&document.contents, &None, &library);
318318
let diagnostics = syntax_diagnostics(document.ast.root_node(), &context).unwrap();
319319
diagnostics
320320
}

crates/ark/src/lsp/inputs/library.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl Library {
6868

6969
fn load_package(&self, name: &str) -> anyhow::Result<Option<Package>> {
7070
for lib_path in self.library_paths.iter() {
71-
match Package::load(&lib_path, name)? {
71+
match Package::load_from_library(&lib_path, name)? {
7272
Some(pkg) => return Ok(Some(pkg)),
7373
None => (),
7474
}

crates/ark/src/lsp/inputs/package.rs

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,12 @@ pub struct Package {
2323
}
2424

2525
impl Package {
26-
/// Attempts to load a package from the given path and name.
27-
pub fn load(lib_path: &std::path::Path, name: &str) -> anyhow::Result<Option<Self>> {
28-
let package_path = lib_path.join(name);
29-
26+
/// Load a package from a given path.
27+
pub fn load(package_path: &std::path::Path) -> anyhow::Result<Option<Self>> {
3028
let description_path = package_path.join("DESCRIPTION");
3129
let namespace_path = package_path.join("NAMESPACE");
3230

33-
// Only consider libraries that have a folder named after the
34-
// requested package and that contains a description file
31+
// Only consider directories that contain a description file
3532
if !description_path.is_file() {
3633
return Ok(None);
3734
}
@@ -41,24 +38,42 @@ impl Package {
4138
let description_contents = fs::read_to_string(&description_path)?;
4239
let description = Description::parse(&description_contents)?;
4340

44-
if description.name != name {
45-
return Err(anyhow::anyhow!(
46-
"`Package` field in `DESCRIPTION` doesn't match folder name '{name}'"
47-
));
48-
}
49-
5041
let namespace = if namespace_path.is_file() {
5142
let namespace_contents = fs::read_to_string(&namespace_path)?;
5243
Namespace::parse(&namespace_contents)?
5344
} else {
54-
tracing::info!("Package `{name}` doesn't contain a NAMESPACE file, using defaults");
45+
tracing::info!(
46+
"Package `{name}` doesn't contain a NAMESPACE file, using defaults",
47+
name = description.name
48+
);
5549
Namespace::default()
5650
};
5751

5852
Ok(Some(Package {
59-
path: package_path,
53+
path: package_path.to_path_buf(),
6054
description,
6155
namespace,
6256
}))
6357
}
58+
59+
/// Load a package from the given library path and name.
60+
pub fn load_from_library(
61+
lib_path: &std::path::Path,
62+
name: &str,
63+
) -> anyhow::Result<Option<Self>> {
64+
let package_path = lib_path.join(name);
65+
66+
// For library packages, ensure the invariant that the package name
67+
// matches the folder name
68+
if let Some(pkg) = Self::load(&package_path)? {
69+
if pkg.description.name != name {
70+
return Err(anyhow::anyhow!(
71+
"`Package` field in `DESCRIPTION` doesn't match folder name '{name}'"
72+
));
73+
}
74+
Ok(Some(pkg))
75+
} else {
76+
Ok(None)
77+
}
78+
}
6479
}

crates/ark/src/lsp/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub(crate) struct WorldState {
5151
pub(crate) installed_packages: Vec<String>,
5252

5353
/// The root of the source tree (e.g., a package).
54-
pub(crate) _root: Option<SourceRoot>,
54+
pub(crate) root: Option<SourceRoot>,
5555

5656
/// Map of package name to package metadata for installed libraries. Lazily populated.
5757
pub(crate) library: Library,

crates/ark/src/lsp/state_handlers.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ use crate::lsp::config::GLOBAL_SETTINGS;
4545
use crate::lsp::documents::Document;
4646
use crate::lsp::encoding::get_position_encoding_kind;
4747
use crate::lsp::indexer;
48+
use crate::lsp::inputs::package::Package;
49+
use crate::lsp::inputs::source_root::SourceRoot;
4850
use crate::lsp::main_loop::DidCloseVirtualDocumentParams;
4951
use crate::lsp::main_loop::DidOpenVirtualDocumentParams;
5052
use crate::lsp::main_loop::LspState;
@@ -84,8 +86,35 @@ pub(crate) fn initialize(
8486
for folder in workspace_folders.iter() {
8587
state.workspace.folders.push(folder.uri.clone());
8688
if let Ok(path) = folder.uri.to_file_path() {
87-
if let Some(path) = path.to_str() {
88-
folders.push(path.to_string());
89+
// Try to load package from this workspace folder and set as
90+
// root if found. This means we're dealing with a package
91+
// source.
92+
if state.root.is_none() {
93+
match Package::load(&path) {
94+
Ok(Some(pkg)) => {
95+
log::info!(
96+
"Root: Loaded package `{pkg}` from {path} as project root",
97+
pkg = pkg.description.name,
98+
path = path.display()
99+
);
100+
state.root = Some(SourceRoot::Package(pkg));
101+
},
102+
Ok(None) => {
103+
log::info!(
104+
"Root: No package found at {path}, treating as folder of scripts",
105+
path = path.display()
106+
);
107+
},
108+
Err(err) => {
109+
log::warn!(
110+
"Root: Error loading package at {path}: {err}",
111+
path = path.display()
112+
);
113+
},
114+
}
115+
}
116+
if let Some(path_str) = path.to_str() {
117+
folders.push(path_str.to_string());
89118
}
90119
}
91120
}

0 commit comments

Comments
 (0)