Handle LSP requests for hearing about file saving of specific files

This commit is contained in:
MinusGix 2022-07-05 02:58:58 -05:00
parent 5646bdb235
commit a904656f10
4 changed files with 132 additions and 12 deletions

1
Cargo.lock generated
View File

@ -2302,6 +2302,7 @@ dependencies = [
"crossbeam-channel",
"directories",
"git2",
"globset",
"grep-matcher",
"grep-regex",
"grep-searcher",

View File

@ -11,6 +11,7 @@ grep-searcher = "0.1.8"
grep-matcher = "0.1.5"
grep-regex = "0.1.9"
ignore = "0.4.18"
globset = "0.4.9"
reqwest = { version = "0.11", features = ["blocking", "json", "socks"] }
wasmer = "2.1.1"
wasmer-wasi = "2.1.1"

View File

@ -629,11 +629,13 @@ fn handle_request(&self, id: RequestId, rpc: ProxyRequest) {
}
}
Save { rev, buffer_id } => {
let mut buffers = self.buffers.lock();
let buffer = buffers.get_mut(&buffer_id).unwrap();
let resp = buffer.save(rev).map(|_r| json!({}));
self.lsp.lock().save_buffer(buffer);
self.respond(id, resp);
if let Some(workspace) = self.workspace.lock().as_ref() {
let mut buffers = self.buffers.lock();
let buffer = buffers.get_mut(&buffer_id).unwrap();
let resp = buffer.save(rev).map(|_r| json!({}));
self.lsp.lock().save_buffer(buffer, workspace);
self.respond(id, resp);
}
}
SaveBufferAs {
buffer_id,

View File

@ -2,6 +2,7 @@
collections::HashMap,
io::BufRead,
io::{BufReader, BufWriter, Write},
path::Path,
process::{self, Child, ChildStdout, Command, Stdio},
sync::{mpsc::channel, Arc},
thread,
@ -52,6 +53,37 @@ pub struct LspState {
pub server_capabilities: Option<ServerCapabilities>,
pub opened_documents: HashMap<BufferId, Url>,
pub is_initialized: bool,
pub did_save_capabilities: Vec<DidSaveCapability>,
}
pub struct DocumentFilter {
/// The document must have this language id, if it exists
pub language_id: Option<String>,
/// The document's path must match this glob, if it exists
pub pattern: Option<globset::GlobMatcher>,
// TODO: URI Scheme from lsp-types document filter
}
impl DocumentFilter {
/// Constructs a document filter from the LSP version
/// This ignores any fields that are badly constructed
fn from_lsp_filter_loose(filter: lsp_types::DocumentFilter) -> DocumentFilter {
DocumentFilter {
language_id: filter.language,
// TODO: clean this up
pattern: filter
.pattern
.as_deref()
.map(globset::Glob::new)
.and_then(Result::ok)
.map(|x| globset::Glob::compile_matcher(&x)),
}
}
}
pub struct DidSaveCapability {
/// A filter on what documents this applies to
filter: DocumentFilter,
/// Whether we are supposed to include the text when sending a didSave event
include_text: bool,
}
#[derive(Clone)]
@ -135,10 +167,53 @@ pub fn new_buffer(
}
}
pub fn save_buffer(&self, buffer: &Buffer) {
if let Some(client) = self.clients.get(&buffer.language_id) {
let uri = client.get_uri(buffer);
client.send_did_save(uri);
pub fn save_buffer(&self, buffer: &Buffer, workspace_path: &Path) {
for (client_language_id, client) in self.clients.iter() {
// Get rid of the workspace path prefix so that it can be used with the filters
let buffer_path = buffer
.path
.strip_prefix(workspace_path)
.unwrap_or(&buffer.path);
let mut passed_filter = client_language_id == &buffer.language_id;
let mut include_text = false;
if !passed_filter {
let lsp_state = client.state.lock();
// TODO: Should we iterate in reverse order so that later capabilities
// can overwrite old ones?
// Find the first capability that wants this file, if any.
for cap in &lsp_state.did_save_capabilities {
if let Some(language_id) = &cap.filter.language_id {
if language_id != &buffer.language_id {
continue;
}
}
if let Some(pattern) = &cap.filter.pattern {
if !pattern.is_match(buffer_path) {
continue;
}
}
passed_filter = true;
include_text = cap.include_text;
break;
}
// Get rid of the mutex guard
drop(lsp_state);
}
if passed_filter {
let uri = client.get_uri(buffer);
let text = if include_text {
Some(buffer.get_document())
} else {
None
};
client.send_did_save(uri, text);
}
}
}
@ -457,6 +532,7 @@ pub fn new(
server_capabilities: None,
opened_documents: HashMap::new(),
is_initialized: false,
did_save_capabilities: Vec::new(),
})),
});
@ -577,7 +653,7 @@ pub fn handle_message(&self, message: &str) {
}
}
pub fn handle_request(&self, method: &str, id: Id, _params: Params) {
pub fn handle_request(&self, method: &str, id: Id, params: Params) {
match method {
"window/workDoneProgress/create" => {
// Token is ignored as the workProgress Widget is always working
@ -585,6 +661,41 @@ pub fn handle_request(&self, method: &str, id: Id, _params: Params) {
// probably store the token
self.send_success_response(id, &json!({}));
}
"client/registerCapability" => {
if let Ok(registrations) =
serde_json::from_value::<RegistrationParams>(json!(params))
{
for registration in registrations.registrations {
match registration.method.as_str() {
"textDocument/didSave" => {
if let Some(options) = registration.register_options {
if let Ok(options) = serde_json::from_value::<TextDocumentSaveRegistrationOptions>(options) {
if let Some(selectors) = options.text_document_registration_options.document_selector {
// TODO: is false a reasonable default?
let include_text = options.include_text.unwrap_or(false);
let mut lsp_state = self.state.lock();
// Add each selector our did save filtering
for selector in selectors {
let filter = DocumentFilter::from_lsp_filter_loose(selector);
let cap = DidSaveCapability {
filter,
include_text,
};
lsp_state.did_save_capabilities.push(cap);
}
}
}
}
// TODO: report error?
}
_ => println!("Received unhandled client/registerCapability request {}", registration.method),
}
}
}
}
method => {
println!("Received unhandled request {method}");
}
@ -740,10 +851,10 @@ pub fn send_did_open(
self.send_notification("textDocument/didOpen", params);
}
pub fn send_did_save(&self, uri: Url) {
pub fn send_did_save(&self, uri: Url, text: Option<String>) {
let params = DidSaveTextDocumentParams {
text_document: TextDocumentIdentifier { uri },
text: None,
text,
};
let params = Params::from(serde_json::to_value(params).unwrap());
self.send_notification("textDocument/didSave", params);
@ -759,6 +870,11 @@ pub fn send_initialize<CB>(&self, root_uri: Option<Url>, on_init: CB)
{
let client_capabilities = ClientCapabilities {
text_document: Some(TextDocumentClientCapabilities {
synchronization: Some(TextDocumentSyncClientCapabilities {
did_save: Some(true),
dynamic_registration: Some(true),
..Default::default()
}),
completion: Some(CompletionClientCapabilities {
completion_item: Some(CompletionItemCapability {
snippet_support: Some(true),