mirror of
https://github.com/ilyakooo0/helix.git
synced 2024-12-01 03:15:33 +03:00
Add support for LSP DidChangeWatchedFiles (#7665)
* Add initial support for LSP DidChangeWatchedFiles * Move file event Handler to helix-lsp * Simplify file event handling * Refactor file event handling * Block on future within LSP file event handler * Fully qualify uses of the file_event::Handler type * Rename ops field to options * Revert newline removal from helix-view/Cargo.toml * Ensure file event Handler is cleaned up when lsp client is shutdown
This commit is contained in:
parent
8977123f25
commit
5c41f22c2a
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -51,9 +51,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.0.1"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@ -126,13 +126,12 @@ checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bstr"
|
name = "bstr"
|
||||||
version = "1.4.0"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"once_cell",
|
"regex-automata",
|
||||||
"regex-automata 0.1.10",
|
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1149,11 +1148,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "globset"
|
name = "globset"
|
||||||
version = "0.4.10"
|
version = "0.4.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
|
checksum = "1391ab1f92ffcc08911957149833e682aa3fe252b9f45f966d2ef972274c97df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 0.7.20",
|
"aho-corasick 1.0.2",
|
||||||
"bstr",
|
"bstr",
|
||||||
"fnv",
|
"fnv",
|
||||||
"log",
|
"log",
|
||||||
@ -1292,6 +1291,7 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"globset",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-loader",
|
"helix-loader",
|
||||||
"helix-parsec",
|
"helix-parsec",
|
||||||
@ -1878,25 +1878,19 @@ version = "1.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.0.1",
|
"aho-corasick 1.0.2",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-automata 0.3.2",
|
"regex-automata",
|
||||||
"regex-syntax 0.7.3",
|
"regex-syntax 0.7.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-automata"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
|
checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick 1.0.1",
|
"aho-corasick 1.0.2",
|
||||||
"memchr",
|
"memchr",
|
||||||
"regex-syntax 0.7.3",
|
"regex-syntax 0.7.3",
|
||||||
]
|
]
|
||||||
|
@ -19,6 +19,7 @@ helix-parsec = { version = "0.6", path = "../helix-parsec" }
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
futures-executor = "0.3"
|
futures-executor = "0.3"
|
||||||
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
|
||||||
|
globset = "0.4.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lsp-types = { version = "0.94" }
|
lsp-types = { version = "0.94" }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
@ -544,6 +544,10 @@ impl Client {
|
|||||||
normalizes_line_endings: Some(false),
|
normalizes_line_endings: Some(false),
|
||||||
change_annotation_support: None,
|
change_annotation_support: None,
|
||||||
}),
|
}),
|
||||||
|
did_change_watched_files: Some(lsp::DidChangeWatchedFilesClientCapabilities {
|
||||||
|
dynamic_registration: Some(true),
|
||||||
|
relative_pattern_support: Some(false),
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
text_document: Some(lsp::TextDocumentClientCapabilities {
|
text_document: Some(lsp::TextDocumentClientCapabilities {
|
||||||
@ -1453,4 +1457,13 @@ impl Client {
|
|||||||
|
|
||||||
Some(self.call::<lsp::request::ExecuteCommand>(params))
|
Some(self.call::<lsp::request::ExecuteCommand>(params))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn did_change_watched_files(
|
||||||
|
&self,
|
||||||
|
changes: Vec<lsp::FileEvent>,
|
||||||
|
) -> impl Future<Output = std::result::Result<(), Error>> {
|
||||||
|
self.notify::<lsp::notification::DidChangeWatchedFiles>(lsp::DidChangeWatchedFilesParams {
|
||||||
|
changes,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
193
helix-lsp/src/file_event.rs
Normal file
193
helix-lsp/src/file_event.rs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
use std::{collections::HashMap, path::PathBuf, sync::Weak};
|
||||||
|
|
||||||
|
use globset::{GlobBuilder, GlobSetBuilder};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::{lsp, Client};
|
||||||
|
|
||||||
|
enum Event {
|
||||||
|
FileChanged {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
Register {
|
||||||
|
client_id: usize,
|
||||||
|
client: Weak<Client>,
|
||||||
|
registration_id: String,
|
||||||
|
options: lsp::DidChangeWatchedFilesRegistrationOptions,
|
||||||
|
},
|
||||||
|
Unregister {
|
||||||
|
client_id: usize,
|
||||||
|
registration_id: String,
|
||||||
|
},
|
||||||
|
RemoveClient {
|
||||||
|
client_id: usize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ClientState {
|
||||||
|
client: Weak<Client>,
|
||||||
|
registered: HashMap<String, globset::GlobSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Handler uses a dedicated tokio task to respond to file change events by
|
||||||
|
/// forwarding changes to LSPs that have registered for notifications with a
|
||||||
|
/// matching glob.
|
||||||
|
///
|
||||||
|
/// When an LSP registers for the DidChangeWatchedFiles notification, the
|
||||||
|
/// Handler is notified by sending the registration details in addition to a
|
||||||
|
/// weak reference to the LSP client. This is done so that the Handler can have
|
||||||
|
/// access to the client without preventing the client from being dropped if it
|
||||||
|
/// is closed and the Handler isn't properly notified.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Handler {
|
||||||
|
tx: mpsc::UnboundedSender<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Handler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
tokio::spawn(Self::run(rx));
|
||||||
|
Self { tx }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(
|
||||||
|
&self,
|
||||||
|
client_id: usize,
|
||||||
|
client: Weak<Client>,
|
||||||
|
registration_id: String,
|
||||||
|
options: lsp::DidChangeWatchedFilesRegistrationOptions,
|
||||||
|
) {
|
||||||
|
let _ = self.tx.send(Event::Register {
|
||||||
|
client_id,
|
||||||
|
client,
|
||||||
|
registration_id,
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unregister(&self, client_id: usize, registration_id: String) {
|
||||||
|
let _ = self.tx.send(Event::Unregister {
|
||||||
|
client_id,
|
||||||
|
registration_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_changed(&self, path: PathBuf) {
|
||||||
|
let _ = self.tx.send(Event::FileChanged { path });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_client(&self, client_id: usize) {
|
||||||
|
let _ = self.tx.send(Event::RemoveClient { client_id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(mut rx: mpsc::UnboundedReceiver<Event>) {
|
||||||
|
let mut state: HashMap<usize, ClientState> = HashMap::new();
|
||||||
|
while let Some(event) = rx.recv().await {
|
||||||
|
match event {
|
||||||
|
Event::FileChanged { path } => {
|
||||||
|
log::debug!("Received file event for {:?}", &path);
|
||||||
|
|
||||||
|
state.retain(|id, client_state| {
|
||||||
|
if !client_state
|
||||||
|
.registered
|
||||||
|
.values()
|
||||||
|
.any(|glob| glob.is_match(&path))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let Some(client) = client_state.client.upgrade() else {
|
||||||
|
log::warn!("LSP client was dropped: {id}");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Ok(uri) = lsp::Url::from_file_path(&path) else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
log::debug!(
|
||||||
|
"Sending didChangeWatchedFiles notification to client '{}'",
|
||||||
|
client.name()
|
||||||
|
);
|
||||||
|
if let Err(err) = crate::block_on(client
|
||||||
|
.did_change_watched_files(vec![lsp::FileEvent {
|
||||||
|
uri,
|
||||||
|
// We currently always send the CHANGED state
|
||||||
|
// since we don't actually have more context at
|
||||||
|
// the moment.
|
||||||
|
typ: lsp::FileChangeType::CHANGED,
|
||||||
|
}]))
|
||||||
|
{
|
||||||
|
log::warn!("Failed to send didChangeWatchedFiles notification to client: {err}");
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Event::Register {
|
||||||
|
client_id,
|
||||||
|
client,
|
||||||
|
registration_id,
|
||||||
|
options: ops,
|
||||||
|
} => {
|
||||||
|
log::debug!(
|
||||||
|
"Registering didChangeWatchedFiles for client '{}' with id '{}'",
|
||||||
|
client_id,
|
||||||
|
registration_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut entry = state.entry(client_id).or_insert_with(ClientState::default);
|
||||||
|
entry.client = client;
|
||||||
|
|
||||||
|
let mut builder = GlobSetBuilder::new();
|
||||||
|
for watcher in ops.watchers {
|
||||||
|
if let lsp::GlobPattern::String(pattern) = watcher.glob_pattern {
|
||||||
|
if let Ok(glob) = GlobBuilder::new(&pattern).build() {
|
||||||
|
builder.add(glob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match builder.build() {
|
||||||
|
Ok(globset) => {
|
||||||
|
entry.registered.insert(registration_id, globset);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
// Remove any old state for that registration id and
|
||||||
|
// remove the entire client if it's now empty.
|
||||||
|
entry.registered.remove(®istration_id);
|
||||||
|
if entry.registered.is_empty() {
|
||||||
|
state.remove(&client_id);
|
||||||
|
}
|
||||||
|
log::warn!(
|
||||||
|
"Unable to build globset for LSP didChangeWatchedFiles {err}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Unregister {
|
||||||
|
client_id,
|
||||||
|
registration_id,
|
||||||
|
} => {
|
||||||
|
log::debug!(
|
||||||
|
"Unregistering didChangeWatchedFiles with id '{}' for client '{}'",
|
||||||
|
registration_id,
|
||||||
|
client_id
|
||||||
|
);
|
||||||
|
if let Some(client_state) = state.get_mut(&client_id) {
|
||||||
|
client_state.registered.remove(®istration_id);
|
||||||
|
if client_state.registered.is_empty() {
|
||||||
|
state.remove(&client_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::RemoveClient { client_id } => {
|
||||||
|
log::debug!("Removing LSP client: {client_id}");
|
||||||
|
state.remove(&client_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
mod client;
|
mod client;
|
||||||
|
pub mod file_event;
|
||||||
pub mod jsonrpc;
|
pub mod jsonrpc;
|
||||||
pub mod snippet;
|
pub mod snippet;
|
||||||
mod transport;
|
mod transport;
|
||||||
@ -547,6 +548,7 @@ pub enum MethodCall {
|
|||||||
WorkspaceFolders,
|
WorkspaceFolders,
|
||||||
WorkspaceConfiguration(lsp::ConfigurationParams),
|
WorkspaceConfiguration(lsp::ConfigurationParams),
|
||||||
RegisterCapability(lsp::RegistrationParams),
|
RegisterCapability(lsp::RegistrationParams),
|
||||||
|
UnregisterCapability(lsp::UnregistrationParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MethodCall {
|
impl MethodCall {
|
||||||
@ -570,6 +572,10 @@ impl MethodCall {
|
|||||||
let params: lsp::RegistrationParams = params.parse()?;
|
let params: lsp::RegistrationParams = params.parse()?;
|
||||||
Self::RegisterCapability(params)
|
Self::RegisterCapability(params)
|
||||||
}
|
}
|
||||||
|
lsp::request::UnregisterCapability::METHOD => {
|
||||||
|
let params: lsp::UnregistrationParams = params.parse()?;
|
||||||
|
Self::UnregisterCapability(params)
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Unhandled);
|
return Err(Error::Unhandled);
|
||||||
}
|
}
|
||||||
@ -629,6 +635,7 @@ pub struct Registry {
|
|||||||
syn_loader: Arc<helix_core::syntax::Loader>,
|
syn_loader: Arc<helix_core::syntax::Loader>,
|
||||||
counter: usize,
|
counter: usize,
|
||||||
pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
|
pub incoming: SelectAll<UnboundedReceiverStream<(usize, Call)>>,
|
||||||
|
pub file_event_handler: file_event::Handler,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Registry {
|
impl Registry {
|
||||||
@ -638,6 +645,7 @@ impl Registry {
|
|||||||
syn_loader,
|
syn_loader,
|
||||||
counter: 0,
|
counter: 0,
|
||||||
incoming: SelectAll::new(),
|
incoming: SelectAll::new(),
|
||||||
|
file_event_handler: file_event::Handler::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,6 +658,7 @@ impl Registry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_by_id(&mut self, id: usize) {
|
pub fn remove_by_id(&mut self, id: usize) {
|
||||||
|
self.file_event_handler.remove_client(id);
|
||||||
self.inner.retain(|_, language_servers| {
|
self.inner.retain(|_, language_servers| {
|
||||||
language_servers.retain(|ls| id != ls.id());
|
language_servers.retain(|ls| id != ls.id());
|
||||||
!language_servers.is_empty()
|
!language_servers.is_empty()
|
||||||
@ -715,6 +724,7 @@ impl Registry {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
for old_client in old_clients {
|
for old_client in old_clients {
|
||||||
|
self.file_event_handler.remove_client(old_client.id());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = old_client.force_shutdown().await;
|
let _ = old_client.force_shutdown().await;
|
||||||
});
|
});
|
||||||
@ -731,6 +741,7 @@ impl Registry {
|
|||||||
pub fn stop(&mut self, name: &str) {
|
pub fn stop(&mut self, name: &str) {
|
||||||
if let Some(clients) = self.inner.remove(name) {
|
if let Some(clients) = self.inner.remove(name) {
|
||||||
for client in clients {
|
for client in clients {
|
||||||
|
self.file_event_handler.remove_client(client.id());
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = client.force_shutdown().await;
|
let _ = client.force_shutdown().await;
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,11 @@ use helix_core::{
|
|||||||
path::get_relative_path,
|
path::get_relative_path,
|
||||||
pos_at_coords, syntax, Selection,
|
pos_at_coords, syntax, Selection,
|
||||||
};
|
};
|
||||||
use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
|
use helix_lsp::{
|
||||||
|
lsp::{self, notification::Notification},
|
||||||
|
util::lsp_pos_to_pos,
|
||||||
|
LspProgressMap,
|
||||||
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
align_view,
|
align_view,
|
||||||
document::DocumentSavedEventResult,
|
document::DocumentSavedEventResult,
|
||||||
@ -1080,17 +1084,65 @@ impl Application {
|
|||||||
.collect();
|
.collect();
|
||||||
Ok(json!(result))
|
Ok(json!(result))
|
||||||
}
|
}
|
||||||
Ok(MethodCall::RegisterCapability(_params)) => {
|
Ok(MethodCall::RegisterCapability(params)) => {
|
||||||
log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
|
if let Some(client) = self
|
||||||
// Language Servers based on the `vscode-languageserver-node` library often send
|
.editor
|
||||||
// client/registerCapability even though we do not enable dynamic registration
|
.language_servers
|
||||||
// for any capabilities. We should send a MethodNotFound JSONRPC error in this
|
.iter_clients()
|
||||||
// case but that rejects the registration promise in the server which causes an
|
.find(|client| client.id() == server_id)
|
||||||
// exit. So we work around this by ignoring the request and sending back an OK
|
{
|
||||||
// response.
|
for reg in params.registrations {
|
||||||
|
match reg.method.as_str() {
|
||||||
|
lsp::notification::DidChangeWatchedFiles::METHOD => {
|
||||||
|
let Some(options) = reg.register_options else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let ops: lsp::DidChangeWatchedFilesRegistrationOptions =
|
||||||
|
match serde_json::from_value(options) {
|
||||||
|
Ok(ops) => ops,
|
||||||
|
Err(err) => {
|
||||||
|
log::warn!("Failed to deserialize DidChangeWatchedFilesRegistrationOptions: {err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.editor.language_servers.file_event_handler.register(
|
||||||
|
client.id(),
|
||||||
|
Arc::downgrade(client),
|
||||||
|
reg.id,
|
||||||
|
ops,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Language Servers based on the `vscode-languageserver-node` library often send
|
||||||
|
// client/registerCapability even though we do not enable dynamic registration
|
||||||
|
// for most capabilities. We should send a MethodNotFound JSONRPC error in this
|
||||||
|
// case but that rejects the registration promise in the server which causes an
|
||||||
|
// exit. So we work around this by ignoring the request and sending back an OK
|
||||||
|
// response.
|
||||||
|
log::warn!("Ignoring a client/registerCapability request because dynamic capability registration is not enabled. Please report this upstream to the language server");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(serde_json::Value::Null)
|
Ok(serde_json::Value::Null)
|
||||||
}
|
}
|
||||||
|
Ok(MethodCall::UnregisterCapability(params)) => {
|
||||||
|
for unreg in params.unregisterations {
|
||||||
|
match unreg.method.as_str() {
|
||||||
|
lsp::notification::DidChangeWatchedFiles::METHOD => {
|
||||||
|
self.editor
|
||||||
|
.language_servers
|
||||||
|
.file_event_handler
|
||||||
|
.unregister(server_id, unreg.id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("Received unregistration request for unsupported method: {}", unreg.method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(serde_json::Value::Null)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tokio::spawn(language_server!().reply(id, reply));
|
tokio::spawn(language_server!().reply(id, reply));
|
||||||
|
@ -1283,7 +1283,14 @@ fn reload(
|
|||||||
doc.reload(view, &cx.editor.diff_providers, redraw_handle)
|
doc.reload(view, &cx.editor.diff_providers, redraw_handle)
|
||||||
.map(|_| {
|
.map(|_| {
|
||||||
view.ensure_cursor_in_view(doc, scrolloff);
|
view.ensure_cursor_in_view(doc, scrolloff);
|
||||||
})
|
})?;
|
||||||
|
if let Some(path) = doc.path() {
|
||||||
|
cx.editor
|
||||||
|
.language_servers
|
||||||
|
.file_event_handler
|
||||||
|
.file_changed(path.clone());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload_all(
|
fn reload_all(
|
||||||
@ -1324,6 +1331,12 @@ fn reload_all(
|
|||||||
|
|
||||||
let redraw_handle = cx.editor.redraw_handle.clone();
|
let redraw_handle = cx.editor.redraw_handle.clone();
|
||||||
doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
|
doc.reload(view, &cx.editor.diff_providers, redraw_handle)?;
|
||||||
|
if let Some(path) = doc.path() {
|
||||||
|
cx.editor
|
||||||
|
.language_servers
|
||||||
|
.file_event_handler
|
||||||
|
.file_changed(path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
for view_id in view_ids {
|
for view_id in view_ids {
|
||||||
let view = view_mut!(cx.editor, view_id);
|
let view = view_mut!(cx.editor, view_id);
|
||||||
|
@ -1535,7 +1535,18 @@ impl Editor {
|
|||||||
|
|
||||||
let path = path.map(|path| path.into());
|
let path = path.map(|path| path.into());
|
||||||
let doc = doc_mut!(self, &doc_id);
|
let doc = doc_mut!(self, &doc_id);
|
||||||
let future = doc.save(path, force)?;
|
let doc_save_future = doc.save(path, force)?;
|
||||||
|
|
||||||
|
// When a file is written to, notify the file event handler.
|
||||||
|
// Note: This can be removed once proper file watching is implemented.
|
||||||
|
let handler = self.language_servers.file_event_handler.clone();
|
||||||
|
let future = async move {
|
||||||
|
let res = doc_save_future.await;
|
||||||
|
if let Ok(event) = &res {
|
||||||
|
handler.file_changed(event.path.clone());
|
||||||
|
}
|
||||||
|
res
|
||||||
|
};
|
||||||
|
|
||||||
use futures_util::stream;
|
use futures_util::stream;
|
||||||
|
|
||||||
@ -1671,6 +1682,14 @@ impl Editor {
|
|||||||
&self,
|
&self,
|
||||||
timeout: Option<u64>,
|
timeout: Option<u64>,
|
||||||
) -> Result<(), tokio::time::error::Elapsed> {
|
) -> Result<(), tokio::time::error::Elapsed> {
|
||||||
|
// Remove all language servers from the file event handler.
|
||||||
|
// Note: this is non-blocking.
|
||||||
|
for client in self.language_servers.iter_clients() {
|
||||||
|
self.language_servers
|
||||||
|
.file_event_handler
|
||||||
|
.remove_client(client.id());
|
||||||
|
}
|
||||||
|
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
Duration::from_millis(timeout.unwrap_or(3000)),
|
Duration::from_millis(timeout.unwrap_or(3000)),
|
||||||
future::join_all(
|
future::join_all(
|
||||||
|
Loading…
Reference in New Issue
Block a user