@@ -30,6 +30,7 @@ use crate::lsp::encoding::convert_tree_sitter_range_to_lsp_range;
3030use crate :: lsp:: indexer;
3131use crate :: lsp:: inputs:: library:: Library ;
3232use crate :: lsp:: inputs:: package:: Package ;
33+ use crate :: lsp:: inputs:: source_root:: SourceRoot ;
3334use crate :: lsp:: state:: WorldState ;
3435use crate :: lsp:: traits:: node:: NodeExt ;
3536use 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
8589impl < ' 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 ,
@@ -130,7 +135,11 @@ impl<'a> DiagnosticContext<'a> {
130135 }
131136}
132137
133- pub ( crate ) fn generate_diagnostics ( doc : Document , state : WorldState ) -> Vec < Diagnostic > {
138+ pub ( crate ) fn generate_diagnostics (
139+ doc : Document ,
140+ state : WorldState ,
141+ testthat : bool ,
142+ ) -> Vec < Diagnostic > {
134143 let mut diagnostics = Vec :: new ( ) ;
135144
136145 if !state. config . diagnostics . enable {
@@ -144,7 +153,7 @@ pub(crate) fn generate_diagnostics(doc: Document, state: WorldState) -> Vec<Diag
144153 return diagnostics;
145154 }
146155
147- let mut context = DiagnosticContext :: new ( & doc. contents , & state. library ) ;
156+ let mut context = DiagnosticContext :: new ( & doc. contents , & state. root , & state . library ) ;
148157
149158 // Add a 'root' context for the document.
150159 context. document_symbols . push ( HashMap :: new ( ) ) ;
@@ -154,9 +163,45 @@ pub(crate) fn generate_diagnostics(doc: Document, state: WorldState) -> Vec<Diag
154163 indexer:: IndexEntryData :: Function { name, arguments : _ } => {
155164 context. workspace_symbols . insert ( name. to_string ( ) ) ;
156165 } ,
166+ indexer:: IndexEntryData :: Variable { name } => {
167+ context. workspace_symbols . insert ( name. to_string ( ) ) ;
168+ } ,
157169 _ => { } ,
158170 } ) ;
159171
172+ // If this is a package, add imported symbols to workspace
173+ if let Some ( SourceRoot :: Package ( root) ) = & state. root {
174+ // Add symbols from `importFrom()` directives
175+ for import in & root. namespace . imports {
176+ context. workspace_symbols . insert ( import. clone ( ) ) ;
177+ }
178+
179+ // Add symbols from `import()` directives
180+ for package_import in & root. namespace . package_imports {
181+ if let Some ( pkg) = state. library . get ( package_import) {
182+ for export in & pkg. namespace . exports {
183+ context. workspace_symbols . insert ( export. clone ( ) ) ;
184+ }
185+ }
186+ }
187+ }
188+
189+ // Simple workaround to include testthat exports in test files. I think the
190+ // general principle would be that (a) files in `tests/testthat/` include
191+ // `testthat.R` as a preamble (note that people modify that file e.g. to add
192+ // more `library()` calls), and (b) all helper files are included in a
193+ // test-specific workspace (which is effectively the case currently as we
194+ // don't special-case how workspace inclusion works for packages). We might
195+ // want to provide a mechanism for test packages to declare this sort of
196+ // test files setup.
197+ if testthat {
198+ if let Some ( pkg) = state. library . get ( "testthat" ) {
199+ for export in & pkg. namespace . exports {
200+ context. workspace_symbols . insert ( export. clone ( ) ) ;
201+ }
202+ }
203+ }
204+
160205 // Add per-environment session symbols
161206 for scope in state. console_scopes . iter ( ) {
162207 for name in scope. iter ( ) {
@@ -1097,10 +1142,10 @@ mod tests {
10971142
10981143 use harp:: eval:: RParseEvalOptions ;
10991144 use once_cell:: sync:: Lazy ;
1145+ use tower_lsp:: lsp_types;
11001146 use tower_lsp:: lsp_types:: Position ;
11011147
11021148 use crate :: interface:: console_inputs;
1103- use crate :: lsp:: diagnostics:: generate_diagnostics;
11041149 use crate :: lsp:: documents:: Document ;
11051150 use crate :: lsp:: inputs:: library:: Library ;
11061151 use crate :: lsp:: inputs:: package:: Package ;
@@ -1113,6 +1158,10 @@ mod tests {
11131158 // Default state that includes installed packages and default scopes.
11141159 static DEFAULT_STATE : Lazy < WorldState > = Lazy :: new ( || current_state ( ) ) ;
11151160
1161+ fn generate_diagnostics ( doc : Document , state : WorldState ) -> Vec < lsp_types:: Diagnostic > {
1162+ super :: generate_diagnostics ( doc, state, false )
1163+ }
1164+
11161165 fn current_state ( ) -> WorldState {
11171166 let inputs = console_inputs ( ) . unwrap ( ) ;
11181167
0 commit comments