@@ -23,10 +23,10 @@ use futures::{
stream::FuturesUnordered,
AsyncWriteExt, Future, FutureExt, StreamExt,
};
-use globset::{Glob, GlobSet, GlobSetBuilder};
+use globset::{Glob, GlobBuilder, GlobMatcher, GlobSet, GlobSetBuilder};
use gpui::{
- AppContext, AsyncAppContext, Context, Entity, EventEmitter, Model, ModelContext, PromptLevel,
- Task, WeakModel,
+ AppContext, AsyncAppContext, Entity, EventEmitter, Model, ModelContext, PromptLevel, Task,
+ WeakModel,
};
use http_client::HttpClient;
use itertools::Itertools as _;
@@ -43,12 +43,13 @@ use language::{
Unclipped,
};
use lsp::{
- CodeActionKind, CompletionContext, DiagnosticSeverity, DiagnosticTag,
- DidChangeWatchedFilesRegistrationOptions, Edit, FileSystemWatcher, InsertTextFormat,
- LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
- LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf,
- ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url, WorkDoneProgressCancelParams,
- WorkspaceFolder,
+ notification::DidRenameFiles, CodeActionKind, CompletionContext, DiagnosticSeverity,
+ DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, Edit, FileOperationFilter,
+ FileOperationPatternKind, FileOperationRegistrationOptions, FileRename, FileSystemWatcher,
+ InsertTextFormat, LanguageServer, LanguageServerBinary, LanguageServerBinaryOptions,
+ LanguageServerId, LanguageServerName, LspRequestFuture, MessageActionItem, MessageType, OneOf,
+ RenameFilesParams, ServerHealthStatus, ServerStatus, SymbolKind, TextEdit, Url,
+ WillRenameFiles, WorkDoneProgressCancelParams, WorkspaceFolder,
};
use node_runtime::read_package_installed_version;
use parking_lot::{Mutex, RwLock};
@@ -139,7 +140,9 @@ pub struct LocalLspStore {
pub language_servers: HashMap<LanguageServerId, LanguageServerState>,
buffers_being_formatted: HashSet<BufferId>,
last_workspace_edits_by_language_server: HashMap<LanguageServerId, ProjectTransaction>,
- language_server_watched_paths: HashMap<LanguageServerId, Model<LanguageServerWatchedPaths>>,
+ language_server_watched_paths: HashMap<LanguageServerId, LanguageServerWatchedPaths>,
+ language_server_paths_watched_for_rename:
+ HashMap<LanguageServerId, RenamePathsWatchedForServer>,
language_server_watcher_registrations:
HashMap<LanguageServerId, HashMap<String, Vec<FileSystemWatcher>>>,
supplementary_language_servers:
@@ -899,6 +902,7 @@ impl LspStore {
language_servers: Default::default(),
last_workspace_edits_by_language_server: Default::default(),
language_server_watched_paths: Default::default(),
+ language_server_paths_watched_for_rename: Default::default(),
language_server_watcher_registrations: Default::default(),
current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(),
buffers_being_formatted: Default::default(),
@@ -4332,6 +4336,112 @@ impl LspStore {
.map(|(key, value)| (*key, value))
}
+ pub(super) fn did_rename_entry(
+ &self,
+ worktree_id: WorktreeId,
+ old_path: &Path,
+ new_path: &Path,
+ is_dir: bool,
+ ) {
+ maybe!({
+ let local_store = self.as_local()?;
+
+ let old_uri = lsp::Url::from_file_path(old_path).ok().map(String::from)?;
+ let new_uri = lsp::Url::from_file_path(new_path).ok().map(String::from)?;
+
+ for language_server in self.language_servers_for_worktree(worktree_id) {
+ let Some(filter) = local_store
+ .language_server_paths_watched_for_rename
+ .get(&language_server.server_id())
+ else {
+ continue;
+ };
+
+ if filter.should_send_did_rename(&old_uri, is_dir) {
+ language_server
+ .notify::<DidRenameFiles>(RenameFilesParams {
+ files: vec![FileRename {
+ old_uri: old_uri.clone(),
+ new_uri: new_uri.clone(),
+ }],
+ })
+ .log_err();
+ }
+ }
+ Some(())
+ });
+ }
+
+ pub(super) fn will_rename_entry(
+ this: WeakModel<Self>,
+ worktree_id: WorktreeId,
+ old_path: &Path,
+ new_path: &Path,
+ is_dir: bool,
+ cx: AsyncAppContext,
+ ) -> Task<()> {
+ let old_uri = lsp::Url::from_file_path(old_path).ok().map(String::from);
+ let new_uri = lsp::Url::from_file_path(new_path).ok().map(String::from);
+ cx.spawn(move |mut cx| async move {
+ let mut tasks = vec![];
+ this.update(&mut cx, |this, cx| {
+ let local_store = this.as_local()?;
+ let old_uri = old_uri?;
+ let new_uri = new_uri?;
+ for language_server in this.language_servers_for_worktree(worktree_id) {
+ let Some(filter) = local_store
+ .language_server_paths_watched_for_rename
+ .get(&language_server.server_id())
+ else {
+ continue;
+ };
+ let Some(adapter) =
+ this.language_server_adapter_for_id(language_server.server_id())
+ else {
+ continue;
+ };
+ if filter.should_send_will_rename(&old_uri, is_dir) {
+ let apply_edit = cx.spawn({
+ let old_uri = old_uri.clone();
+ let new_uri = new_uri.clone();
+ let language_server = language_server.clone();
+ |this, mut cx| async move {
+ let edit = language_server
+ .request::<WillRenameFiles>(RenameFilesParams {
+ files: vec![FileRename { old_uri, new_uri }],
+ })
+ .log_err()
+ .await
+ .flatten()?;
+
+ Self::deserialize_workspace_edit(
+ this.upgrade()?,
+ edit,
+ false,
+ adapter.clone(),
+ language_server.clone(),
+ &mut cx,
+ )
+ .await
+ .ok();
+ Some(())
+ }
+ });
+ tasks.push(apply_edit);
+ }
+ }
+ Some(())
+ })
+ .ok()
+ .flatten();
+ for task in tasks {
+ // Await on tasks sequentially so that the order of application of edits is deterministic
+ // (at least with regards to the order of registration of language servers)
+ task.await;
+ }
+ })
+ }
+
fn lsp_notify_abs_paths_changed(
&mut self,
server_id: LanguageServerId,
@@ -4369,6 +4479,32 @@ impl LspStore {
language_server_id: LanguageServerId,
cx: &mut ModelContext<Self>,
) {
+ let Some(watchers) = self.as_local().and_then(|local| {
+ local
+ .language_server_watcher_registrations
+ .get(&language_server_id)
+ }) else {
+ return;
+ };
+
+ let watch_builder =
+ self.rebuild_watched_paths_inner(language_server_id, watchers.values().flatten(), cx);
+ let Some(local_lsp_store) = self.as_local_mut() else {
+ return;
+ };
+ let watcher = watch_builder.build(local_lsp_store.fs.clone(), language_server_id, cx);
+ local_lsp_store
+ .language_server_watched_paths
+ .insert(language_server_id, watcher);
+
+ cx.notify();
+ }
+ fn rebuild_watched_paths_inner<'a>(
+ &'a self,
+ language_server_id: LanguageServerId,
+ watchers: impl Iterator<Item = &'a FileSystemWatcher>,
+ cx: &mut ModelContext<Self>,
+ ) -> LanguageServerWatchedPathsBuilder {
let worktrees = self
.worktree_store
.read(cx)
@@ -4380,15 +4516,6 @@ impl LspStore {
})
.collect::<Vec<_>>();
- let local_lsp_store = self.as_local_mut().unwrap();
-
- let Some(watchers) = local_lsp_store
- .language_server_watcher_registrations
- .get(&language_server_id)
- else {
- return;
- };
-
let mut worktree_globs = HashMap::default();
let mut abs_globs = HashMap::default();
log::trace!(
@@ -4406,7 +4533,7 @@ impl LspStore {
pattern: String,
},
}
- for watcher in watchers.values().flatten() {
+ for watcher in watchers {
let mut found_host = false;
for worktree in &worktrees {
let glob_is_inside_worktree = worktree.update(cx, |tree, _| {
@@ -4545,12 +4672,7 @@ impl LspStore {
watch_builder.watch_abs_path(abs_path, globset);
}
}
- let watcher = watch_builder.build(local_lsp_store.fs.clone(), language_server_id, cx);
- local_lsp_store
- .language_server_watched_paths
- .insert(language_server_id, watcher);
-
- cx.notify();
+ watch_builder
}
pub fn language_server_for_id(&self, id: LanguageServerId) -> Option<Arc<LanguageServer>> {
@@ -6650,6 +6772,23 @@ impl LspStore {
simulate_disk_based_diagnostics_completion: None,
},
);
+ if let Some(file_ops_caps) = language_server
+ .capabilities()
+ .workspace
+ .as_ref()
+ .and_then(|ws| ws.file_operations.as_ref())
+ {
+ let did_rename_caps = file_ops_caps.did_rename.as_ref();
+ let will_rename_caps = file_ops_caps.will_rename.as_ref();
+ if did_rename_caps.or(will_rename_caps).is_some() {
+ let watcher = RenamePathsWatchedForServer::default()
+ .with_did_rename_patterns(did_rename_caps)
+ .with_will_rename_patterns(will_rename_caps);
+ local
+ .language_server_paths_watched_for_rename
+ .insert(server_id, watcher);
+ }
+ }
}
self.language_server_statuses.insert(
@@ -7010,7 +7149,7 @@ impl LspStore {
if let Some(watched_paths) = local
.language_server_watched_paths
.get(server_id)
- .and_then(|paths| paths.read(cx).worktree_paths.get(&worktree_id))
+ .and_then(|paths| paths.worktree_paths.get(&worktree_id))
{
let params = lsp::DidChangeWatchedFilesParams {
changes: changes
@@ -7115,7 +7254,7 @@ impl LspStore {
Ok(transaction)
}
- pub async fn deserialize_workspace_edit(
+ pub(crate) async fn deserialize_workspace_edit(
this: Model<Self>,
edit: lsp::WorkspaceEdit,
push_to_history: bool,
@@ -7515,6 +7654,84 @@ pub enum LanguageServerToQuery {
Other(LanguageServerId),
}
+#[derive(Default)]
+struct RenamePathsWatchedForServer {
+ did_rename: Vec<RenameActionPredicate>,
+ will_rename: Vec<RenameActionPredicate>,
+}
+
+impl RenamePathsWatchedForServer {
+ fn with_did_rename_patterns(
+ mut self,
+ did_rename: Option<&FileOperationRegistrationOptions>,
+ ) -> Self {
+ if let Some(did_rename) = did_rename {
+ self.did_rename = did_rename
+ .filters
+ .iter()
+ .filter_map(|filter| filter.try_into().log_err())
+ .collect();
+ }
+ self
+ }
+ fn with_will_rename_patterns(
+ mut self,
+ will_rename: Option<&FileOperationRegistrationOptions>,
+ ) -> Self {
+ if let Some(will_rename) = will_rename {
+ self.will_rename = will_rename
+ .filters
+ .iter()
+ .filter_map(|filter| filter.try_into().log_err())
+ .collect();
+ }
+ self
+ }
+
+ fn should_send_did_rename(&self, path: &str, is_dir: bool) -> bool {
+ self.did_rename.iter().any(|pred| pred.eval(path, is_dir))
+ }
+ fn should_send_will_rename(&self, path: &str, is_dir: bool) -> bool {
+ self.will_rename.iter().any(|pred| pred.eval(path, is_dir))
+ }
+}
+
+impl TryFrom<&FileOperationFilter> for RenameActionPredicate {
+ type Error = globset::Error;
+ fn try_from(ops: &FileOperationFilter) -> Result<Self, globset::Error> {
+ Ok(Self {
+ kind: ops.pattern.matches.clone(),
+ glob: GlobBuilder::new(&ops.pattern.glob)
+ .case_insensitive(
+ ops.pattern
+ .options
+ .as_ref()
+ .map_or(false, |ops| ops.ignore_case.unwrap_or(false)),
+ )
+ .build()?
+ .compile_matcher(),
+ })
+ }
+}
+struct RenameActionPredicate {
+ glob: GlobMatcher,
+ kind: Option<FileOperationPatternKind>,
+}
+
+impl RenameActionPredicate {
+ // Returns true if language server should be notified
+ fn eval(&self, path: &str, is_dir: bool) -> bool {
+ self.kind.as_ref().map_or(true, |kind| {
+ let expected_kind = if is_dir {
+ FileOperationPatternKind::Folder
+ } else {
+ FileOperationPatternKind::File
+ };
+ kind == &expected_kind
+ }) && self.glob.is_match(path)
+ }
+}
+
#[derive(Default)]
struct LanguageServerWatchedPaths {
worktree_paths: HashMap<WorktreeId, GlobSet>,
@@ -7539,78 +7756,65 @@ impl LanguageServerWatchedPathsBuilder {
fs: Arc<dyn Fs>,
language_server_id: LanguageServerId,
cx: &mut ModelContext<LspStore>,
- ) -> Model<LanguageServerWatchedPaths> {
+ ) -> LanguageServerWatchedPaths {
let project = cx.weak_model();
- cx.new_model(|cx| {
- let this_id = cx.entity_id();
- const LSP_ABS_PATH_OBSERVE: Duration = Duration::from_millis(100);
- let abs_paths = self
- .abs_paths
- .into_iter()
- .map(|(abs_path, globset)| {
- let task = cx.spawn({
- let abs_path = abs_path.clone();
- let fs = fs.clone();
-
- let lsp_store = project.clone();
- |_, mut cx| async move {
- maybe!(async move {
- let mut push_updates =
- fs.watch(&abs_path, LSP_ABS_PATH_OBSERVE).await;
- while let Some(update) = push_updates.0.next().await {
- let action = lsp_store
- .update(&mut cx, |this, cx| {
- let Some(local) = this.as_local() else {
- return ControlFlow::Break(());
- };
- let Some(watcher) = local
- .language_server_watched_paths
- .get(&language_server_id)
- else {
- return ControlFlow::Break(());
- };
- if watcher.entity_id() != this_id {
- // This watcher is no longer registered on the project, which means that we should
- // cease operations.
- return ControlFlow::Break(());
- }
- let (globs, _) = watcher
- .read(cx)
- .abs_paths
- .get(&abs_path)
- .expect(
- "Watched abs path is not registered with a watcher",
- );
- let matching_entries = update
- .into_iter()
- .filter(|event| globs.is_match(&event.path))
- .collect::<Vec<_>>();
- this.lsp_notify_abs_paths_changed(
- language_server_id,
- matching_entries,
- );
- ControlFlow::Continue(())
- })
- .ok()?;
+ const LSP_ABS_PATH_OBSERVE: Duration = Duration::from_millis(100);
+ let abs_paths = self
+ .abs_paths
+ .into_iter()
+ .map(|(abs_path, globset)| {
+ let task = cx.spawn({
+ let abs_path = abs_path.clone();
+ let fs = fs.clone();
+
+ let lsp_store = project.clone();
+ |_, mut cx| async move {
+ maybe!(async move {
+ let mut push_updates = fs.watch(&abs_path, LSP_ABS_PATH_OBSERVE).await;
+ while let Some(update) = push_updates.0.next().await {
+ let action = lsp_store
+ .update(&mut cx, |this, _| {
+ let Some(local) = this.as_local() else {
+ return ControlFlow::Break(());
+ };
+ let Some(watcher) = local
+ .language_server_watched_paths
+ .get(&language_server_id)
+ else {
+ return ControlFlow::Break(());
+ };
+ let (globs, _) = watcher.abs_paths.get(&abs_path).expect(
+ "Watched abs path is not registered with a watcher",
+ );
+ let matching_entries = update
+ .into_iter()
+ .filter(|event| globs.is_match(&event.path))
+ .collect::<Vec<_>>();
+ this.lsp_notify_abs_paths_changed(
+ language_server_id,
+ matching_entries,
+ );
+ ControlFlow::Continue(())
+ })
+ .ok()?;
- if action.is_break() {
- break;
- }
- }
- Some(())
- })
- .await;
+ if action.is_break() {
+ break;
+ }
}
- });
- (abs_path, (globset, task))
- })
- .collect();
- LanguageServerWatchedPaths {
- worktree_paths: self.worktree_paths,
- abs_paths,
- }
+ Some(())
+ })
+ .await;
+ }
+ });
+ (abs_path, (globset, task))
})
+ .collect();
+ LanguageServerWatchedPaths {
+ worktree_paths: self.worktree_paths,
+ abs_paths,
+ }
}
}
@@ -9,12 +9,16 @@ use language::{
tree_sitter_rust, tree_sitter_typescript, Diagnostic, DiagnosticSet, DiskState, FakeLspAdapter,
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
};
-use lsp::{DiagnosticSeverity, NumberOrString};
+use lsp::{
+ notification::DidRenameFiles, DiagnosticSeverity, DocumentChanges, FileOperationFilter,
+ NumberOrString, TextDocumentEdit, WillRenameFiles,
+};
use parking_lot::Mutex;
use pretty_assertions::{assert_eq, assert_matches};
use serde_json::json;
#[cfg(not(windows))]
use std::os;
+use std::{str::FromStr, sync::OnceLock};
use std::{mem, num::NonZeroU32, ops::Range, task::Poll};
use task::{ResolvedTask, TaskContext};
@@ -3915,6 +3919,135 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
);
}
+#[gpui::test]
+async fn test_lsp_rename_notifications(cx: &mut gpui::TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ "/dir",
+ json!({
+ "one.rs": "const ONE: usize = 1;",
+ "two": {
+ "two.rs": "const TWO: usize = one::ONE + one::ONE;"
+ }
+
+ }),
+ )
+ .await;
+ let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
+
+ let language_registry = project.read_with(cx, |project, _| project.languages().clone());
+ language_registry.add(rust_lang());
+ let watched_paths = lsp::FileOperationRegistrationOptions {
+ filters: vec![
+ FileOperationFilter {
+ scheme: Some("file".to_owned()),
+ pattern: lsp::FileOperationPattern {
+ glob: "**/*.rs".to_owned(),
+ matches: Some(lsp::FileOperationPatternKind::File),
+ options: None,
+ },
+ },
+ FileOperationFilter {
+ scheme: Some("file".to_owned()),
+ pattern: lsp::FileOperationPattern {
+ glob: "**/**".to_owned(),
+ matches: Some(lsp::FileOperationPatternKind::Folder),
+ options: None,
+ },
+ },
+ ],
+ };
+ let mut fake_servers = language_registry.register_fake_lsp(
+ "Rust",
+ FakeLspAdapter {
+ capabilities: lsp::ServerCapabilities {
+ workspace: Some(lsp::WorkspaceServerCapabilities {
+ workspace_folders: None,
+ file_operations: Some(lsp::WorkspaceFileOperationsServerCapabilities {
+ did_rename: Some(watched_paths.clone()),
+ will_rename: Some(watched_paths),
+ ..Default::default()
+ }),
+ }),
+ ..Default::default()
+ },
+ ..Default::default()
+ },
+ );
+
+ let _ = project
+ .update(cx, |project, cx| {
+ project.open_local_buffer("/dir/one.rs", cx)
+ })
+ .await
+ .unwrap();
+
+ let fake_server = fake_servers.next().await.unwrap();
+ let response = project.update(cx, |project, cx| {
+ let worktree = project.worktrees(cx).next().unwrap();
+ let entry = worktree.read(cx).entry_for_path("one.rs").unwrap();
+ project.rename_entry(entry.id, "three.rs".as_ref(), cx)
+ });
+ let expected_edit = lsp::WorkspaceEdit {
+ changes: None,
+ document_changes: Some(DocumentChanges::Edits({
+ vec![TextDocumentEdit {
+ edits: vec![lsp::Edit::Plain(lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 1,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 3,
+ },
+ },
+ new_text: "This is not a drill".to_owned(),
+ })],
+ text_document: lsp::OptionalVersionedTextDocumentIdentifier {
+ uri: Url::from_str("file:///dir/two/two.rs").unwrap(),
+ version: Some(1337),
+ },
+ }]
+ })),
+ change_annotations: None,
+ };
+ let resolved_workspace_edit = Arc::new(OnceLock::new());
+ fake_server
+ .handle_request::<WillRenameFiles, _, _>({
+ let resolved_workspace_edit = resolved_workspace_edit.clone();
+ let expected_edit = expected_edit.clone();
+ move |params, _| {
+ let resolved_workspace_edit = resolved_workspace_edit.clone();
+ let expected_edit = expected_edit.clone();
+ async move {
+ assert_eq!(params.files.len(), 1);
+ assert_eq!(params.files[0].old_uri, "file:///dir/one.rs");
+ assert_eq!(params.files[0].new_uri, "file:///dir/three.rs");
+ resolved_workspace_edit.set(expected_edit.clone()).unwrap();
+ Ok(Some(expected_edit))
+ }
+ }
+ })
+ .next()
+ .await
+ .unwrap();
+ let _ = response.await.unwrap();
+ fake_server
+ .handle_notification::<DidRenameFiles, _>(|params, _| {
+ assert_eq!(params.files.len(), 1);
+ assert_eq!(params.files[0].old_uri, "file:///dir/one.rs");
+ assert_eq!(params.files[0].new_uri, "file:///dir/three.rs");
+ })
+ .next()
+ .await
+ .unwrap();
+ assert_eq!(resolved_workspace_edit.get(), Some(&expected_edit));
+}
+
#[gpui::test]
async fn test_rename(cx: &mut gpui::TestAppContext) {
// hi
@@ -26,7 +26,7 @@ use text::ReplicaId;
use util::{paths::SanitizedPath, ResultExt};
use worktree::{Entry, ProjectEntryId, Worktree, WorktreeId, WorktreeSettings};
-use crate::{search::SearchQuery, ProjectPath};
+use crate::{search::SearchQuery, LspStore, ProjectPath};
struct MatchingEntry {
worktree_path: Arc<Path>,
@@ -69,7 +69,6 @@ impl EventEmitter<WorktreeStoreEvent> for WorktreeStore {}
impl WorktreeStore {
pub fn init(client: &AnyProtoClient) {
client.add_model_request_handler(Self::handle_create_project_entry);
- client.add_model_request_handler(Self::handle_rename_project_entry);
client.add_model_request_handler(Self::handle_copy_project_entry);
client.add_model_request_handler(Self::handle_delete_project_entry);
client.add_model_request_handler(Self::handle_expand_project_entry);
@@ -184,6 +183,19 @@ impl WorktreeStore {
.find_map(|worktree| worktree.read(cx).entry_for_id(entry_id))
}
+ pub fn worktree_and_entry_for_id<'a>(
+ &'a self,
+ entry_id: ProjectEntryId,
+ cx: &'a AppContext,
+ ) -> Option<(Model<Worktree>, &'a Entry)> {
+ self.worktrees().find_map(|worktree| {
+ worktree
+ .read(cx)
+ .entry_for_id(entry_id)
+ .map(|e| (worktree.clone(), e))
+ })
+ }
+
pub fn entry_for_path(&self, path: &ProjectPath, cx: &AppContext) -> Option<Entry> {
self.worktree_for_id(path.worktree_id, cx)?
.read(cx)
@@ -1004,16 +1016,56 @@ impl WorktreeStore {
}
pub async fn handle_rename_project_entry(
- this: Model<Self>,
+ this: Model<super::Project>,
envelope: TypedEnvelope<proto::RenameProjectEntry>,
mut cx: AsyncAppContext,
) -> Result<proto::ProjectEntryResponse> {
let entry_id = ProjectEntryId::from_proto(envelope.payload.entry_id);
- let worktree = this.update(&mut cx, |this, cx| {
- this.worktree_for_entry(entry_id, cx)
- .ok_or_else(|| anyhow!("worktree not found"))
- })??;
- Worktree::handle_rename_entry(worktree, envelope.payload, cx).await
+ let (worktree_id, worktree, old_path, is_dir) = this
+ .update(&mut cx, |this, cx| {
+ this.worktree_store
+ .read(cx)
+ .worktree_and_entry_for_id(entry_id, cx)
+ .map(|(worktree, entry)| {
+ (
+ worktree.read(cx).id(),
+ worktree,
+ entry.path.clone(),
+ entry.is_dir(),
+ )
+ })
+ })?
+ .ok_or_else(|| anyhow!("worktree not found"))?;
+ let (old_abs_path, new_abs_path) = {
+ let root_path = worktree.update(&mut cx, |this, _| this.abs_path())?;
+ (
+ root_path.join(&old_path),
+ root_path.join(&envelope.payload.new_path),
+ )
+ };
+ let lsp_store = this
+ .update(&mut cx, |this, _| this.lsp_store())?
+ .downgrade();
+ LspStore::will_rename_entry(
+ lsp_store,
+ worktree_id,
+ &old_abs_path,
+ &new_abs_path,
+ is_dir,
+ cx.clone(),
+ )
+ .await;
+ let response = Worktree::handle_rename_entry(worktree, envelope.payload, cx.clone()).await;
+ this.update(&mut cx, |this, cx| {
+ this.lsp_store().read(cx).did_rename_entry(
+ worktree_id,
+ &old_abs_path,
+ &new_abs_path,
+ is_dir,
+ );
+ })
+ .ok();
+ response
}
pub async fn handle_copy_project_entry(