use super::*;
#[derive(Debug, Clone)]
pub(crate) struct Document {
text: String,
version: i32,
}
pub(crate) enum Outbound {
Diagnostics {
uri: Uri,
version: i32,
diags: Vec<LspDiagnostic>,
findings: Arc<Vec<Diagnostic>>,
},
RelintAll,
}
pub(crate) struct GlobalState {
documents: HashMap<Uri, Document>,
findings: HashMap<Uri, (i32, Arc<Vec<Diagnostic>>)>,
rename_anchors: HashMap<Uri, RenameAnchor>,
config_cache: HashMap<PathBuf, ResolvedSettings>,
editor_settings: EditorSettings,
sender: Sender<Message>,
lint_tx: Sender<LintMsg>,
read_tx: Sender<ReadJob>,
read_spawner: Spawner,
}
impl GlobalState {
pub(crate) fn new(
sender: Sender<Message>,
lint_tx: Sender<LintMsg>,
read_tx: Sender<ReadJob>,
read_spawner: Spawner,
editor_settings: EditorSettings,
) -> Self {
Self {
documents: HashMap::new(),
findings: HashMap::new(),
rename_anchors: HashMap::new(),
config_cache: HashMap::new(),
editor_settings,
sender,
lint_tx,
read_tx,
read_spawner,
}
}
pub(crate) fn on_request(&mut self, req: Request) {
match req.method.as_str() {
Formatting::METHOD => self.on_formatting(req),
RangeFormatting::METHOD => self.on_range_formatting(req),
CodeActionRequest::METHOD => self.on_code_action(req),
HoverRequest::METHOD => self.on_hover(req),
SignatureHelpRequest::METHOD => self.on_signature_help(req),
Completion::METHOD => self.on_completion(req),
ResolveCompletionItem::METHOD => self.on_resolve_completion(req),
GotoDefinition::METHOD => self.on_definition(req),
References::METHOD => self.on_references(req),
DocumentHighlightRequest::METHOD => self.on_document_highlight(req),
DocumentSymbolRequest::METHOD => self.on_document_symbol(req),
FoldingRangeRequest::METHOD => self.on_folding_range(req),
SemanticTokensFullRequest::METHOD => self.on_semantic_tokens(req),
PrepareRenameRequest::METHOD => self.on_prepare_rename(req),
Rename::METHOD => self.on_rename(req),
WillRenameFiles::METHOD => self.on_will_rename_files(req),
WorkspaceSymbolRequest::METHOD => self.on_workspace_symbol(req),
_ => {
let resp = Response::new_err(
req.id,
ErrorCode::MethodNotFound as i32,
format!("unhandled method: {}", req.method),
);
let _ = self.sender.send(Message::Response(resp));
}
}
}
fn on_formatting(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentFormattingParams>(Formatting::METHOD) else {
self.respond_err(id, "invalid formatting params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let Ok(settings) = self.resolve_settings(&uri) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::Format {
id,
path,
text,
style: settings.style,
sender: self.sender.clone(),
});
}
fn on_range_formatting(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentRangeFormattingParams>(RangeFormatting::METHOD)
else {
self.respond_err(id, "invalid range formatting params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let Ok(settings) = self.resolve_settings(&uri) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::FormatRange {
id,
path,
text,
range: params.range,
style: settings.style,
sender: self.sender.clone(),
});
}
fn on_code_action(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<CodeActionParams>(CodeActionRequest::METHOD) else {
self.respond_err(id, "invalid code action params");
return;
};
let uri = params.text_document.uri;
let Some((text, version)) = self
.documents
.get(&uri)
.map(|d| (d.text.clone(), d.version))
else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let range = params.range;
let sender = self.sender.clone();
if let Some((cached_version, findings)) = self.findings.get(&uri)
&& *cached_version == version
{
let findings = Arc::clone(findings);
self.read_spawner.spawn(move || {
let actions = code_actions_from_findings(&findings, &text, &uri, range);
let _ = sender.send(Message::Response(Response::new_ok(id, actions)));
});
return;
}
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
let lint = self
.resolve_settings(&uri)
.map(|s| s.lint)
.unwrap_or_default();
self.read_spawner.spawn(move || {
let actions = compute_code_actions(&text, &path, &lint, &uri, range);
let _ = sender.send(Message::Response(Response::new_ok(id, actions)));
});
}
fn on_hover(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<HoverParams>(HoverRequest::METHOD) else {
self.respond_err(id, "invalid hover params");
return;
};
let uri = params.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::Hover {
id,
path,
text,
position,
sender: self.sender.clone(),
});
}
fn on_signature_help(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<SignatureHelpParams>(SignatureHelpRequest::METHOD)
else {
self.respond_err(id, "invalid signatureHelp params");
return;
};
let uri = params.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::SignatureHelp {
id,
path,
text,
position,
sender: self.sender.clone(),
});
}
fn on_completion(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<CompletionParams>(Completion::METHOD) else {
self.respond_err(id, "invalid completion params");
return;
};
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::Completion {
id,
path,
text,
position,
sender: self.sender.clone(),
});
}
fn on_resolve_completion(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, item)) = req.extract::<CompletionItem>(ResolveCompletionItem::METHOD) else {
self.respond_err(id, "invalid completionItem/resolve params");
return;
};
self.dispatch_read(ReadJob::ResolveCompletion {
id,
item: Box::new(item),
sender: self.sender.clone(),
});
}
fn on_definition(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<GotoDefinitionParams>(GotoDefinition::METHOD) else {
self.respond_err(id, "invalid definition params");
return;
};
let uri = params.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::Definition {
id,
path,
uri,
text,
position,
sender: self.sender.clone(),
});
}
fn on_references(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<ReferenceParams>(References::METHOD) else {
self.respond_err(id, "invalid references params");
return;
};
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let include_declaration = params.context.include_declaration;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::References {
id,
path,
uri,
text,
position,
include_declaration,
sender: self.sender.clone(),
});
}
fn on_document_highlight(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<DocumentHighlightParams>(DocumentHighlightRequest::METHOD)
else {
self.respond_err(id, "invalid documentHighlight params");
return;
};
let uri = params.text_document_position_params.text_document.uri;
let position = params.text_document_position_params.position;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let sender = self.sender.clone();
self.read_spawner.spawn(move || {
let line_index = LineIndex::new(&text);
let offset = line_index.position_to_byte(position).min(text.len());
let result = compute_document_highlights(&text, offset).map(|highlights| {
highlights
.into_iter()
.map(|(range, kind)| DocumentHighlight {
range: text_range_to_lsp_range(&line_index, range),
kind: Some(kind),
})
.collect::<Vec<_>>()
});
let _ = sender.send(Message::Response(Response::new_ok(id, result)));
});
}
fn on_document_symbol(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<DocumentSymbolParams>(DocumentSymbolRequest::METHOD)
else {
self.respond_err(id, "invalid documentSymbol params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let sender = self.sender.clone();
self.read_spawner.spawn(move || {
let symbols = compute_document_symbols(&text);
let response = DocumentSymbolResponse::Nested(symbols);
let _ = sender.send(Message::Response(Response::new_ok(id, response)));
});
}
fn on_folding_range(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<FoldingRangeParams>(FoldingRangeRequest::METHOD) else {
self.respond_err(id, "invalid foldingRange params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let sender = self.sender.clone();
self.read_spawner.spawn(move || {
let ranges = compute_folding_ranges(&text);
let _ = sender.send(Message::Response(Response::new_ok(id, ranges)));
});
}
fn on_semantic_tokens(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<SemanticTokensParams>(SemanticTokensFullRequest::METHOD)
else {
self.respond_err(id, "invalid semanticTokens params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let sender = self.sender.clone();
self.read_spawner.spawn(move || {
let tokens = compute_semantic_tokens(&text);
let result = SemanticTokensResult::Tokens(tokens);
let _ = sender.send(Message::Response(Response::new_ok(id, result)));
});
}
fn on_prepare_rename(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) =
req.extract::<TextDocumentPositionParams>(PrepareRenameRequest::METHOD)
else {
self.respond_err(id, "invalid prepareRename params");
return;
};
let uri = params.text_document.uri;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let line_index = LineIndex::new(&text);
let offset = line_index.position_to_byte(params.position).min(text.len());
match compute_prepare_rename(&text, offset) {
Some(prepared) => {
self.rename_anchors.insert(uri, prepared.anchor);
let response = PrepareRenameResponse::RangeWithPlaceholder {
range: prepared.range,
placeholder: prepared.placeholder,
};
self.respond_ok(id, serde_json::to_value(response).unwrap_or_default());
}
None => {
self.rename_anchors.remove(&uri);
self.respond_ok(id, serde_json::Value::Null);
}
}
}
fn on_rename(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<RenameParams>(Rename::METHOD) else {
self.respond_err(id, "invalid rename params");
return;
};
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let new_name = params.new_name;
let Some(text) = self.documents.get(&uri).map(|d| d.text.clone()) else {
self.respond_ok(id, serde_json::Value::Null);
return;
};
let offset = self
.rename_anchors
.get(&uri)
.and_then(|anchor| rename_cursor_offset(&text, anchor))
.unwrap_or_else(|| {
let line_index = LineIndex::new(&text);
line_index.position_to_byte(position).min(text.len())
});
self.rename_anchors.remove(&uri);
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
self.dispatch_read(ReadJob::Rename {
id,
path,
uri,
text,
offset,
new_name,
sender: self.sender.clone(),
});
}
fn on_will_rename_files(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<RenameFilesParams>(WillRenameFiles::METHOD) else {
self.respond_err(id, "invalid willRenameFiles params");
return;
};
let renames = file_renames_to_paths(¶ms);
if renames.is_empty() {
self.respond_ok(id, serde_json::Value::Null);
return;
}
self.dispatch_read(ReadJob::WillRenameFiles {
id,
renames,
sender: self.sender.clone(),
});
}
fn on_workspace_symbol(&mut self, req: Request) {
let id = req.id.clone();
let Ok((_, params)) = req.extract::<WorkspaceSymbolParams>(WorkspaceSymbolRequest::METHOD)
else {
self.respond_err(id, "invalid workspaceSymbol params");
return;
};
self.dispatch_read(ReadJob::WorkspaceSymbol {
id,
query: params.query,
sender: self.sender.clone(),
});
}
fn dispatch_read(&self, job: ReadJob) {
if let Err(crossbeam_channel::SendError(job)) = self.read_tx.send(job) {
let (id, sender) = match job {
ReadJob::Format { id, sender, .. } => (id, sender),
ReadJob::FormatRange { id, sender, .. } => (id, sender),
ReadJob::Hover { id, sender, .. } => (id, sender),
ReadJob::Completion { id, sender, .. } => (id, sender),
ReadJob::SignatureHelp { id, sender, .. } => (id, sender),
ReadJob::ResolveCompletion { id, sender, .. } => (id, sender),
ReadJob::Definition { id, sender, .. } => (id, sender),
ReadJob::References { id, sender, .. } => (id, sender),
ReadJob::Rename { id, sender, .. } => (id, sender),
ReadJob::WillRenameFiles { id, sender, .. } => (id, sender),
ReadJob::WorkspaceSymbol { id, sender, .. } => (id, sender),
};
let _ = sender.send(Message::Response(Response::new_ok(
id,
serde_json::Value::Null,
)));
}
}
pub(crate) fn on_notification(&mut self, not: Notification) {
match not.method.as_str() {
DidOpenTextDocument::METHOD => {
if let Ok(params) =
not.extract::<DidOpenTextDocumentParams>(DidOpenTextDocument::METHOD)
{
let uri = params.text_document.uri;
self.documents.insert(
uri.clone(),
Document {
text: params.text_document.text,
version: params.text_document.version,
},
);
self.send_lint(uri);
}
}
DidChangeTextDocument::METHOD => {
if let Ok(mut params) =
not.extract::<DidChangeTextDocumentParams>(DidChangeTextDocument::METHOD)
&& let Some(change) = params.content_changes.pop()
{
let uri = params.text_document.uri;
self.documents.insert(
uri.clone(),
Document {
text: change.text,
version: params.text_document.version,
},
);
self.send_lint(uri);
}
}
DidCloseTextDocument::METHOD => {
if let Ok(params) =
not.extract::<DidCloseTextDocumentParams>(DidCloseTextDocument::METHOD)
{
let uri = params.text_document.uri;
self.documents.remove(&uri);
self.findings.remove(&uri);
self.rename_anchors.remove(&uri);
self.publish(uri, Vec::new(), None);
}
}
DidRenameFiles::METHOD => {
if let Ok(params) = not.extract::<RenameFilesParams>(DidRenameFiles::METHOD) {
let renames = file_renames_to_paths(¶ms);
if !renames.is_empty() {
let _ = self.lint_tx.send(LintMsg::RenameFiles { renames });
}
}
}
DidChangeConfiguration::METHOD => {
if let Ok(params) =
not.extract::<DidChangeConfigurationParams>(DidChangeConfiguration::METHOD)
{
let updated = EditorSettings::from_client_value(¶ms.settings);
if updated != self.editor_settings {
self.editor_settings = updated;
self.config_cache.clear();
}
}
}
_ => {}
}
}
pub(crate) fn on_outbound(&mut self, ob: Outbound) {
match ob {
Outbound::Diagnostics {
uri,
version,
diags,
findings,
} => {
if matches!(self.documents.get(&uri), Some(d) if d.version == version) {
self.findings.insert(uri.clone(), (version, findings));
self.publish(uri, diags, Some(version));
}
}
Outbound::RelintAll => {
let uris: Vec<Uri> = self.documents.keys().cloned().collect();
for uri in uris {
self.send_lint(uri);
}
}
}
}
fn send_lint(&mut self, uri: Uri) {
let Some(doc) = self.documents.get(&uri) else {
return;
};
let text = doc.text.clone();
let version = doc.version;
let path = uri::to_path(&uri).unwrap_or_else(|| PathBuf::from("untitled.R"));
let (lint_config, index_config) = match self.resolve_settings(&uri) {
Ok(s) => (s.lint, s.index),
Err(_) => (LintConfig::default(), IndexConfig::default()),
};
let _ = self.lint_tx.send(LintMsg::Request(Box::new(LintRequest {
uri,
path,
text,
version,
lint_config,
index_config,
})));
}
fn resolve_settings(&mut self, uri: &Uri) -> Result<ResolvedSettings, ConfigResolveError> {
let path = uri::to_path(uri).ok_or(ConfigResolveError::NonFileUri)?;
let anchor = path
.parent()
.ok_or(ConfigResolveError::NoParentDirectory)?
.to_path_buf();
if let Some(s) = self.config_cache.get(&anchor) {
return Ok(s.clone());
}
let (config, source) = Config::resolve(None, false, &anchor)
.map_err(|err| ConfigResolveError::Config(err.to_string()))?;
let style = resolve_format_style(&config, source.is_some(), &self.editor_settings);
let mut index = config.index;
index.remote_url = std::env::var("ARITY_REMOTE_URL")
.ok()
.filter(|s| !s.is_empty());
let resolved = ResolvedSettings {
style,
lint: config.lint,
index,
};
self.config_cache.insert(anchor, resolved.clone());
Ok(resolved)
}
fn publish(&self, uri: Uri, diagnostics: Vec<LspDiagnostic>, version: Option<i32>) {
let params = PublishDiagnosticsParams {
uri,
diagnostics,
version,
};
let not = Notification::new(PublishDiagnostics::METHOD.to_string(), params);
let _ = self.sender.send(Message::Notification(not));
}
fn respond_ok(&self, id: RequestId, value: serde_json::Value) {
let _ = self
.sender
.send(Message::Response(Response::new_ok(id, value)));
}
fn respond_err(&self, id: RequestId, message: &str) {
let resp = Response::new_err(id, ErrorCode::InvalidParams as i32, message.to_string());
let _ = self.sender.send(Message::Response(resp));
}
}