Skip to content

Commit 8f1d49e

Browse files
authored
Merge pull request #359 from posit-dev/feature/read-console-scopes
Send console scopes from `ReadConsole`
2 parents e976a1c + 0bf4766 commit 8f1d49e

File tree

8 files changed

+212
-74
lines changed

8 files changed

+212
-74
lines changed

crates/ark/src/interface.rs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ use crossbeam::channel::Receiver;
5656
use crossbeam::channel::Sender;
5757
use crossbeam::channel::TryRecvError;
5858
use crossbeam::select;
59+
use harp::environment::Environment;
60+
use harp::environment::R_ENVS;
5961
use harp::exec::geterrmessage;
6062
use harp::exec::r_check_stack;
6163
use harp::exec::r_sandbox;
@@ -90,6 +92,7 @@ use serde_json::json;
9092
use stdext::result::ResultOrLog;
9193
use stdext::*;
9294
use tokio::runtime::Runtime;
95+
use tokio::sync::mpsc::UnboundedSender as TokioUnboundedSender;
9396

9497
use crate::dap::dap::DapBackendEvent;
9598
use crate::dap::dap_r_main::RMainDap;
@@ -99,6 +102,8 @@ use crate::help::message::HelpReply;
99102
use crate::help::message::HelpRequest;
100103
use crate::kernel::Kernel;
101104
use crate::lsp::backend::Backend;
105+
use crate::lsp::backend::ConsoleInputs;
106+
use crate::lsp::backend::LspEvent;
102107
use crate::lsp::events::EVENTS;
103108
use crate::modules;
104109
use crate::plots::graphics_device;
@@ -271,6 +276,9 @@ pub struct RMain {
271276
lsp_runtime: Arc<Runtime>,
272277
lsp_backend: Option<Backend>,
273278

279+
/// Event channel for notifying the LSP. In principle, could be a Jupyter comm.
280+
lsp_events_tx: Option<TokioUnboundedSender<LspEvent>>,
281+
274282
dap: RMainDap,
275283

276284
/// Whether or not R itself is actively busy.
@@ -376,6 +384,7 @@ impl RMain {
376384
help_rx: None,
377385
lsp_runtime,
378386
lsp_backend: None,
387+
lsp_events_tx: None,
379388
dap: RMainDap::new(dap),
380389
is_busy: false,
381390
old_show_error_messages: None,
@@ -573,6 +582,23 @@ impl RMain {
573582
}
574583
}
575584

585+
// In the future we'll also send browser information, see
586+
// https://github.com/posit-dev/positron/issues/3001. Currently this is
587+
// a push model where we send the console inputs at each round. In the
588+
// future, a pull model would be better, this way the LSP can manage a
589+
// cache of inputs and we don't need to retraverse the environments as
590+
// often. We'd still push a `DidChangeConsoleInputs` notification from
591+
// here, but only containing high-level information such as `search()`
592+
// contents and `ls(rho)`.
593+
if !info.browser && !info.incomplete && !info.input_request {
594+
match console_inputs() {
595+
Ok(inputs) => {
596+
self.send_lsp(LspEvent::DidChangeConsoleInputs(inputs));
597+
},
598+
Err(err) => log::error!("Can't retrieve console inputs: {err:?}"),
599+
}
600+
}
601+
576602
// Signal prompt
577603
EVENTS.console_prompt.emit(());
578604

@@ -1055,8 +1081,19 @@ impl RMain {
10551081
self.lsp_backend.as_ref()
10561082
}
10571083

1058-
pub fn set_lsp_backend(&mut self, backend: Backend) {
1084+
fn send_lsp(&self, event: LspEvent) {
1085+
if let Some(ref tx) = self.lsp_events_tx {
1086+
tx.send(event).unwrap();
1087+
}
1088+
}
1089+
1090+
pub fn set_lsp_backend(
1091+
&mut self,
1092+
backend: Backend,
1093+
lsp_events_tx: TokioUnboundedSender<LspEvent>,
1094+
) {
10591095
self.lsp_backend = Some(backend);
1096+
self.lsp_events_tx = Some(lsp_events_tx);
10601097
}
10611098

10621099
pub fn call_frontend_method(&self, request: UiFrontendRequest) -> anyhow::Result<RObject> {
@@ -1254,6 +1291,25 @@ fn to_html(frame: SEXP) -> Result<String> {
12541291
}
12551292
}
12561293

1294+
// Inputs generated by `ReadConsole` for the LSP
1295+
pub(crate) fn console_inputs() -> anyhow::Result<ConsoleInputs> {
1296+
// TODO: Should send the debug environment if debugging:
1297+
// https://github.com/posit-dev/positron/issues/3001
1298+
let env = Environment::new(R_ENVS.global.into());
1299+
let scopes = env.ancestors().map(|e| e.names()).collect();
1300+
1301+
// Get the set of installed packages
1302+
let installed_packages: Vec<String> = RFunction::new("base", ".packages")
1303+
.param("all.available", true)
1304+
.call()?
1305+
.try_into()?;
1306+
1307+
Ok(ConsoleInputs {
1308+
console_scopes: scopes,
1309+
installed_packages,
1310+
})
1311+
}
1312+
12571313
// --- Frontend methods ---
12581314
// These functions are hooked up as R frontend methods. They call into our
12591315
// global `RMain` singleton.

crates/ark/src/lsp/backend.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use stdext::result::ResultOrLog;
1818
use stdext::*;
1919
use tokio::net::TcpListener;
2020
use tokio::runtime::Runtime;
21+
use tokio::sync::mpsc::unbounded_channel as tokio_unbounded_channel;
2122
use tower_lsp::jsonrpc::Result;
2223
use tower_lsp::lsp_types::request::GotoImplementationParams;
2324
use tower_lsp::lsp_types::request::GotoImplementationResponse;
@@ -61,6 +62,11 @@ macro_rules! backend_trace {
6162

6263
}
6364

65+
#[derive(Debug)]
66+
pub enum LspEvent {
67+
DidChangeConsoleInputs(ConsoleInputs),
68+
}
69+
6470
#[derive(Clone, Debug)]
6571
pub struct Backend {
6672
/// LSP client, use this for direct interaction with the client.
@@ -73,6 +79,21 @@ pub struct Backend {
7379
pub indexer_state_manager: IndexerStateManager,
7480
}
7581

82+
/// Information sent from the kernel to the LSP after each top-level evaluation.
83+
#[derive(Debug)]
84+
pub struct ConsoleInputs {
85+
/// List of console scopes, from innermost (global or debug) to outermost
86+
/// scope. Currently the scopes are vectors of symbol names. TODO: In the
87+
/// future, we should send structural information like search path, and let
88+
/// the LSP query us for the contents so that the LSP can cache the
89+
/// information.
90+
pub console_scopes: Vec<Vec<String>>,
91+
92+
/// Packages currently installed in the library path. TODO: Should send
93+
/// library paths instead and inspect and cache package information in the LSP.
94+
pub installed_packages: Vec<String>,
95+
}
96+
7697
impl Backend {
7798
pub fn with_document<T, F>(&self, path: &Path, mut callback: F) -> anyhow::Result<T>
7899
where
@@ -571,10 +592,31 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
571592
state: WorldState {
572593
documents: Arc::new(DashMap::new()),
573594
workspace: Arc::new(Mutex::new(Workspace::default())),
595+
console_scopes: Arc::new(Mutex::new(vec![])),
596+
installed_packages: Arc::new(Mutex::new(vec![])),
574597
},
575598
indexer_state_manager: IndexerStateManager::new(),
576599
};
577600

601+
let (events_tx, mut events_rx) = tokio_unbounded_channel::<LspEvent>();
602+
603+
// LSP event loop. To be integrated in our synchronising dispatcher
604+
// once implemented.
605+
tokio::spawn({
606+
let backend = backend.clone();
607+
async move {
608+
loop {
609+
match events_rx.recv().await.unwrap() {
610+
LspEvent::DidChangeConsoleInputs(inputs) => {
611+
*backend.state.console_scopes.lock() = inputs.console_scopes;
612+
*backend.state.installed_packages.lock() =
613+
inputs.installed_packages;
614+
},
615+
}
616+
}
617+
}
618+
});
619+
578620
// Forward `backend` along to `RMain`.
579621
// This also updates an outdated `backend` after a reconnect.
580622
// `RMain` should be initialized by now, since the caller of this
@@ -585,7 +627,7 @@ pub fn start_lsp(runtime: Arc<Runtime>, address: String, conn_init_tx: Sender<bo
585627
let backend = backend.clone();
586628
move || {
587629
let main = RMain::get_mut();
588-
main.set_lsp_backend(backend);
630+
main.set_lsp_backend(backend, events_tx);
589631
}
590632
});
591633

crates/ark/src/lsp/completions/completion_item.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,16 @@ use std::fs::DirEntry;
1010
use anyhow::bail;
1111
use anyhow::Result;
1212
use harp::r_symbol;
13+
use harp::utils::is_symbol_valid;
1314
use harp::utils::r_env_binding_is_active;
1415
use harp::utils::r_envir_name;
1516
use harp::utils::r_formals;
1617
use harp::utils::r_promise_force_with_rollback;
1718
use harp::utils::r_promise_is_forced;
1819
use harp::utils::r_promise_is_lazy_load_binding;
19-
use harp::utils::r_symbol_quote;
20-
use harp::utils::r_symbol_quote_invalid;
21-
use harp::utils::r_symbol_valid;
2220
use harp::utils::r_typeof;
21+
use harp::utils::sym_quote;
22+
use harp::utils::sym_quote_invalid;
2323
use libr::R_UnboundValue;
2424
use libr::Rf_findVarInFrame;
2525
use libr::Rf_isFunction;
@@ -178,7 +178,7 @@ pub(super) fn completion_item_from_function<T: AsRef<str>>(
178178
let detail = format!("{}({})", name, parameters.joined(", "));
179179
item.detail = Some(detail);
180180

181-
let insert_text = r_symbol_quote_invalid(name);
181+
let insert_text = sym_quote_invalid(name);
182182
item.insert_text_format = Some(InsertTextFormat::SNIPPET);
183183
item.insert_text = Some(format!("{insert_text}($0)"));
184184

@@ -211,8 +211,8 @@ pub(super) unsafe fn completion_item_from_data_variable(
211211

212212
if enquote {
213213
item.insert_text = Some(format!("\"{}\"", name));
214-
} else if !r_symbol_valid(name) {
215-
item.insert_text = Some(r_symbol_quote(name));
214+
} else if !is_symbol_valid(name) {
215+
item.insert_text = Some(sym_quote(name));
216216
}
217217

218218
item.detail = Some(owner.to_string());
@@ -253,8 +253,8 @@ pub(super) unsafe fn completion_item_from_object(
253253
item.detail = Some("(Object)".to_string());
254254
item.kind = Some(CompletionItemKind::STRUCT);
255255

256-
if !r_symbol_valid(name) {
257-
item.insert_text = Some(r_symbol_quote(name));
256+
if !is_symbol_valid(name) {
257+
item.insert_text = Some(sym_quote(name));
258258
}
259259

260260
Ok(item)
@@ -293,8 +293,8 @@ pub(super) unsafe fn completion_item_from_promise(
293293
item.detail = Some("Promise".to_string());
294294
item.kind = Some(CompletionItemKind::STRUCT);
295295

296-
if !r_symbol_valid(name) {
297-
item.insert_text = Some(r_symbol_quote(name));
296+
if !is_symbol_valid(name) {
297+
item.insert_text = Some(sym_quote(name));
298298
}
299299

300300
Ok(item)
@@ -310,8 +310,8 @@ pub(super) fn completion_item_from_active_binding(name: &str) -> Result<Completi
310310
item.detail = Some("Active binding".to_string());
311311
item.kind = Some(CompletionItemKind::STRUCT);
312312

313-
if !r_symbol_valid(name) {
314-
item.insert_text = Some(r_symbol_quote(name));
313+
if !is_symbol_valid(name) {
314+
item.insert_text = Some(sym_quote(name));
315315
}
316316

317317
Ok(item)
@@ -433,7 +433,7 @@ pub(super) fn completion_item_from_parameter(
433433
function: callee.to_string(),
434434
};
435435

436-
let parameter = r_symbol_quote_invalid(parameter);
436+
let parameter = sym_quote_invalid(parameter);
437437

438438
// We want to display to the user the name with the `=`
439439
let label = parameter.clone() + " = ";

crates/ark/src/lsp/completions/sources/unique/custom.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use anyhow::Result;
99
use harp::exec::RFunction;
1010
use harp::exec::RFunctionExt;
1111
use harp::object::RObject;
12-
use harp::utils::r_symbol_quote_invalid;
12+
use harp::utils::sym_quote_invalid;
1313
use harp::utils::r_typeof;
1414
use libr::R_NilValue;
1515
use libr::VECSXP;
@@ -183,7 +183,7 @@ pub fn completions_from_custom_source_impl(
183183
if enquote && !node.is_string() {
184184
item.insert_text = Some(format!("\"{value}\""));
185185
} else {
186-
let mut insert_text = r_symbol_quote_invalid(value.as_str());
186+
let mut insert_text = sym_quote_invalid(value.as_str());
187187

188188
if !append.is_empty() {
189189
insert_text = format!("{insert_text}{append}");

0 commit comments

Comments
 (0)