diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index dbc7f4b19b40d83ab6630b8ba0cb01539eaea40d..8b67bfc3c9cdf85b436e5f197e9de21f4b33cd43 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -234,7 +234,7 @@ impl ActivityIndicator { status, } => { let create_buffer = - project.update(cx, |project, cx| project.create_buffer(false, cx)); + project.update(cx, |project, cx| project.create_buffer(None, false, cx)); let status = status.clone(); let server_name = server_name.clone(); cx.spawn_in(window, async move |workspace, cx| { diff --git a/crates/agent/src/outline.rs b/crates/agent/src/outline.rs index b8b5068e06d8cf23808e24e8fddb93103420cd74..6a204e7694a3387ac4910e0c1431a67a6c9f43b5 100644 --- a/crates/agent/src/outline.rs +++ b/crates/agent/src/outline.rs @@ -179,7 +179,7 @@ mod tests { let content = "⚡".repeat(100 * 1024); // 100KB let content_len = content.len(); let buffer = project - .update(cx, |project, cx| project.create_buffer(true, cx)) + .update(cx, |project, cx| project.create_buffer(None, true, cx)) .await .expect("failed to create buffer"); diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index e60c74f6f6fcf3349b8a9503c4e3238c4acdefb5..2a0afb6db1f24172a53cb420469a9ee0b1cc9244 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -6742,12 +6742,13 @@ impl AcpThreadView { let markdown_language = markdown_language_task.await?; let buffer = project - .update(cx, |project, cx| project.create_buffer(false, cx)) + .update(cx, |project, cx| { + project.create_buffer(Some(markdown_language), false, cx) + }) .await?; buffer.update(cx, |buffer, cx| { buffer.set_text(markdown, cx); - buffer.set_language(Some(markdown_language), cx); buffer.set_capability(language::Capability::ReadWrite, cx); }); diff --git a/crates/auto_update_ui/src/auto_update_ui.rs b/crates/auto_update_ui/src/auto_update_ui.rs index 41485e5396331ea71b0fa20e0cf38c9b76c0a82e..bb300efac102172e8a36e32f06aea76f84aff0e3 100644 --- a/crates/auto_update_ui/src/auto_update_ui.rs +++ b/crates/auto_update_ui/src/auto_update_ui.rs @@ -5,7 +5,7 @@ use markdown_preview::markdown_preview_view::{MarkdownPreviewMode, MarkdownPrevi use release_channel::{AppVersion, ReleaseChannel}; use serde::Deserialize; use smol::io::AsyncReadExt; -use util::ResultExt as _; +use util::{ResultExt as _, maybe}; use workspace::Workspace; use workspace::notifications::ErrorMessagePrompt; use workspace::notifications::simple_message_notification::MessageNotification; @@ -39,14 +39,14 @@ struct ReleaseNotesBody { release_notes: String, } -fn notify_release_notes_failed_to_show_locally( +fn notify_release_notes_failed_to_show( workspace: &mut Workspace, _window: &mut Window, cx: &mut Context, ) { - struct ViewReleaseNotesLocallyError; + struct ViewReleaseNotesError; workspace.show_notification( - NotificationId::unique::(), + NotificationId::unique::(), cx, |cx| { cx.new(move |cx| { @@ -92,70 +92,71 @@ fn view_release_notes_locally( .languages .language_for_name("Markdown"); - workspace - .with_local_workspace(window, cx, move |_, window, cx| { - cx.spawn_in(window, async move |workspace, cx| { - let markdown = markdown.await.log_err(); - let response = client.get(&url, Default::default(), true).await; - let Some(mut response) = response.log_err() else { - workspace - .update_in(cx, notify_release_notes_failed_to_show_locally) - .log_err(); - return; - }; - - let mut body = Vec::new(); - response.body_mut().read_to_end(&mut body).await.ok(); - - let body: serde_json::Result = - serde_json::from_slice(body.as_slice()); - - if let Ok(body) = body { - workspace - .update_in(cx, |workspace, window, cx| { - let project = workspace.project().clone(); - let buffer = project.update(cx, |project, cx| { - project.create_local_buffer("", markdown, false, cx) - }); - buffer.update(cx, |buffer, cx| { - buffer.edit([(0..0, body.release_notes)], None, cx) - }); - let language_registry = project.read(cx).languages().clone(); - - let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); - - let editor = cx.new(|cx| { - Editor::for_multibuffer(buffer, Some(project), window, cx) - }); - let workspace_handle = workspace.weak_handle(); - let markdown_preview: Entity = - MarkdownPreviewView::new( - MarkdownPreviewMode::Default, - editor, - workspace_handle, - language_registry, - window, - cx, - ); - workspace.add_item_to_active_pane( - Box::new(markdown_preview), - None, - true, - window, - cx, - ); - cx.notify(); - }) - .log_err(); - } else { - workspace - .update_in(cx, notify_release_notes_failed_to_show_locally) - .log_err(); - } - }) - .detach(); + cx.spawn_in(window, async move |workspace, cx| { + let markdown = markdown.await.log_err(); + let response = client.get(&url, Default::default(), true).await; + let Some(mut response) = response.log_err() else { + workspace + .update_in(cx, notify_release_notes_failed_to_show) + .log_err(); + return; + }; + + let mut body = Vec::new(); + response.body_mut().read_to_end(&mut body).await.ok(); + + let body: serde_json::Result = serde_json::from_slice(body.as_slice()); + + let res: Option<()> = maybe!(async { + let body = body.ok()?; + let project = workspace + .read_with(cx, |workspace, _| workspace.project().clone()) + .ok()?; + let (language_registry, buffer) = project.update(cx, |project, cx| { + ( + project.languages().clone(), + project.create_buffer(markdown, false, cx), + ) + }); + let buffer = buffer.await.ok()?; + buffer.update(cx, |buffer, cx| { + buffer.edit([(0..0, body.release_notes)], None, cx) + }); + + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); + + let ws_handle = workspace.clone(); + workspace + .update_in(cx, |workspace, window, cx| { + let editor = + cx.new(|cx| Editor::for_multibuffer(buffer, Some(project), window, cx)); + let markdown_preview: Entity = MarkdownPreviewView::new( + MarkdownPreviewMode::Default, + editor, + ws_handle, + language_registry, + window, + cx, + ); + workspace.add_item_to_active_pane( + Box::new(markdown_preview), + None, + true, + window, + cx, + ); + cx.notify(); + }) + .ok() }) - .detach(); + .await; + if res.is_none() { + workspace + .update_in(cx, notify_release_notes_failed_to_show) + .log_err(); + } + }) + .detach(); } /// Shows a notification across all workspaces if an update was previously automatically installed diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index f869fa57a6aa80c6e0ea83016467fdddf20f3bea..e1708e04ead5a115a329673e173c33b85f98e3e2 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2502,7 +2502,7 @@ async fn test_propagate_saves_and_fs_changes( }); let new_buffer_a = project_a - .update(cx_a, |p, cx| p.create_buffer(false, cx)) + .update(cx_a, |p, cx| p.create_buffer(None, false, cx)) .await .unwrap(); diff --git a/crates/edit_prediction/src/udiff.rs b/crates/edit_prediction/src/udiff.rs index 015bef2f0a0af35b1d807ec0596ae08542ae5ba0..2582c67615a7633c5a2aef494203a1c3618f7e76 100644 --- a/crates/edit_prediction/src/udiff.rs +++ b/crates/edit_prediction/src/udiff.rs @@ -81,7 +81,9 @@ pub async fn apply_diff( Entry::Vacant(entry) => { let buffer: Entity = if status == FileStatus::Created { project - .update(cx, |project, cx| project.create_buffer(true, cx)) + .update(cx, |project, cx| { + project.create_buffer(None, true, cx) + }) .await? } else { let project_path = project diff --git a/crates/edit_prediction_ui/src/edit_prediction_ui.rs b/crates/edit_prediction_ui/src/edit_prediction_ui.rs index 81cb22523a8acd7f4ac5e967deda3a5d72e91d07..2ca852a0140651b515734dd144c868bfebe04328 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_ui.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_ui.rs @@ -173,7 +173,9 @@ fn capture_example_as_markdown( .await? } else { project - .update(cx, |project, cx| project.create_buffer(false, cx)) + .update(cx, |project, cx| { + project.create_buffer(Some(markdown_language.clone()), false, cx) + }) .await? }; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d076f1d2ae59762da886e97012105ced90f5a19e..e9012c17a22cd2082ee49ed1c2b327804275e5b3 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2929,7 +2929,7 @@ impl Editor { cx: &mut Context, ) -> Task>> { let project = workspace.project().clone(); - let create = project.update(cx, |project, cx| project.create_buffer(true, cx)); + let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx)); cx.spawn_in(window, async move |workspace, cx| { let buffer = create.await?; @@ -2976,7 +2976,7 @@ impl Editor { cx: &mut Context, ) { let project = workspace.project().clone(); - let create = project.update(cx, |project, cx| project.create_buffer(true, cx)); + let create = project.update(cx, |project, cx| project.create_buffer(None, true, cx)); cx.spawn_in(window, async move |workspace, cx| { let buffer = create.await?; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 163c4a6bb01acfff0a64b738067dbea7ba0d2a95..fc2d5e648a17568a0c242094219fa5ce71ef1859 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1116,16 +1116,13 @@ impl SerializableItem for Editor { // First create the empty buffer let buffer = project - .update(cx, |project, cx| project.create_buffer(true, cx)) + .update(cx, |project, cx| project.create_buffer(language, true, cx)) .await .context("Failed to create buffer while deserializing editor")?; // Then set the text so that the dirty bit is set correctly buffer.update(cx, |buffer, cx| { buffer.set_language_registry(language_registry); - if let Some(language) = language { - buffer.set_language(Some(language), cx); - } buffer.set_text(contents, cx); if let Some(entry) = buffer.peek_undo_stack() { buffer.forget_transaction(entry.transaction_id()); @@ -1227,7 +1224,7 @@ impl SerializableItem for Editor { .. } => window.spawn(cx, async move |cx| { let buffer = project - .update(cx, |project, cx| project.create_buffer(true, cx)) + .update(cx, |project, cx| project.create_buffer(None, true, cx)) .await .context("Failed to create buffer")?; diff --git a/crates/editor/src/rust_analyzer_ext.rs b/crates/editor/src/rust_analyzer_ext.rs index 70139214a6e9d5b4e8760ddbcbf579b26dcde51c..79bdfd660e0a08fa6bae12920be450d7651f7ee1 100644 --- a/crates/editor/src/rust_analyzer_ext.rs +++ b/crates/editor/src/rust_analyzer_ext.rs @@ -200,12 +200,13 @@ pub fn expand_macro_recursively( } let buffer = project - .update(cx, |project, cx| project.create_buffer(false, cx)) + .update(cx, |project, cx| { + project.create_buffer(Some(rust_language), false, cx) + }) .await?; workspace.update_in(cx, |workspace, window, cx| { buffer.update(cx, |buffer, cx| { buffer.set_text(macro_expansion.expansion, cx); - buffer.set_language(Some(rust_language), cx); buffer.set_capability(Capability::ReadOnly, cx); }); let multibuffer = diff --git a/crates/fs/src/fs.rs b/crates/fs/src/fs.rs index 59535f4dd84d8b5d7a67b6ca8b15e764236e8084..27fc3adf31ca2f680ad3256496969fa25dabca49 100644 --- a/crates/fs/src/fs.rs +++ b/crates/fs/src/fs.rs @@ -545,7 +545,10 @@ impl Fs for RealFs { } else if !options.ignore_if_exists { open_options.create_new(true); } - open_options.open(path).await?; + open_options + .open(path) + .await + .with_context(|| format!("Failed to create file at {:?}", path))?; Ok(()) } @@ -554,7 +557,9 @@ impl Fs for RealFs { path: &Path, content: Pin<&mut (dyn AsyncRead + Send)>, ) -> Result<()> { - let mut file = smol::fs::File::create(&path).await?; + let mut file = smol::fs::File::create(&path) + .await + .with_context(|| format!("Failed to create file at {:?}", path))?; futures::io::copy(content, &mut file).await?; Ok(()) } @@ -748,7 +753,10 @@ impl Fs for RealFs { async fn load(&self, path: &Path) -> Result { let path = path.to_path_buf(); self.executor - .spawn(async move { Ok(std::fs::read_to_string(path)?) }) + .spawn(async move { + std::fs::read_to_string(&path) + .with_context(|| format!("Failed to read file {}", path.display())) + }) .await } @@ -810,9 +818,13 @@ impl Fs for RealFs { async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> { let buffer_size = text.summary().len.min(10 * 1024); if let Some(path) = path.parent() { - self.create_dir(path).await?; + self.create_dir(path) + .await + .with_context(|| format!("Failed to create directory at {:?}", path))?; } - let file = smol::fs::File::create(path).await?; + let file = smol::fs::File::create(path) + .await + .with_context(|| format!("Failed to create file at {:?}", path))?; let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file); for chunk in text::chunks_with_line_ending(text, line_ending) { writer.write_all(chunk.as_bytes()).await?; @@ -823,7 +835,9 @@ impl Fs for RealFs { async fn write(&self, path: &Path, content: &[u8]) -> Result<()> { if let Some(path) = path.parent() { - self.create_dir(path).await?; + self.create_dir(path) + .await + .with_context(|| format!("Failed to create directory at {:?}", path))?; } let path = path.to_owned(); let contents = content.to_owned(); diff --git a/crates/keymap_editor/src/keymap_editor.rs b/crates/keymap_editor/src/keymap_editor.rs index 6149f01cefb15aa3f02fa07df88da1bfe36bb73f..adc9deaa21680042971e5ab5e1cd25dc61b1c92e 100644 --- a/crates/keymap_editor/src/keymap_editor.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -92,43 +92,38 @@ pub fn init(cx: &mut App) { window: &mut Window, cx: &mut Context, ) { - workspace - .with_local_workspace(window, cx, |workspace, window, cx| { - let existing = workspace - .active_pane() - .read(cx) - .items() - .find_map(|item| item.downcast::()); - - let keymap_editor = if let Some(existing) = existing { - workspace.activate_item(&existing, true, true, window, cx); - existing - } else { - let keymap_editor = - cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx)); - workspace.add_item_to_active_pane( - Box::new(keymap_editor.clone()), - None, - true, - window, - cx, - ); - keymap_editor - }; + let existing = workspace + .active_pane() + .read(cx) + .items() + .find_map(|item| item.downcast::()); - if let Some(filter) = filter { - keymap_editor.update(cx, |editor, cx| { - editor.filter_editor.update(cx, |editor, cx| { - editor.clear(window, cx); - editor.insert(&filter, window, cx); - }); - if !editor.has_binding_for(&filter) { - open_binding_modal_after_loading(cx) - } - }) + let keymap_editor = if let Some(existing) = existing { + workspace.activate_item(&existing, true, true, window, cx); + existing + } else { + let keymap_editor = cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx)); + workspace.add_item_to_active_pane( + Box::new(keymap_editor.clone()), + None, + true, + window, + cx, + ); + keymap_editor + }; + + if let Some(filter) = filter { + keymap_editor.update(cx, |editor, cx| { + editor.filter_editor.update(cx, |editor, cx| { + editor.clear(window, cx); + editor.insert(&filter, window, cx); + }); + if !editor.has_binding_for(&filter) { + open_binding_modal_after_loading(cx) } }) - .detach_and_log_err(cx); + } } cx.observe_new(|workspace: &mut Workspace, _window, _cx| { diff --git a/crates/language_tools/src/lsp_button.rs b/crates/language_tools/src/lsp_button.rs index 65a0ea0de119be733cfaf0a998e84f6e2dfaf73c..233aae66b6139c83c48db8a467669fc83c781e98 100644 --- a/crates/language_tools/src/lsp_button.rs +++ b/crates/language_tools/src/lsp_button.rs @@ -316,7 +316,7 @@ impl LanguageServerState { let Some(create_buffer) = workspace_for_message .update(cx, |workspace, cx| { workspace.project().update(cx, |project, cx| { - project.create_buffer(false, cx) + project.create_buffer(None, false, cx) }) }) .ok() diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 32d17d77dd64ff53e99bf840231ee21504d10cfd..cb68445be24c7d633eed173f1915660807acd4d2 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -331,6 +331,7 @@ impl RemoteBufferStore { fn create_buffer( &self, + language: Option>, project_searchable: bool, cx: &mut Context, ) -> Task>> { @@ -341,13 +342,20 @@ impl RemoteBufferStore { let response = create.await?; let buffer_id = BufferId::new(response.buffer_id)?; - this.update(cx, |this, cx| { - if !project_searchable { - this.non_searchable_buffers.insert(buffer_id); - } - this.wait_for_remote_buffer(buffer_id, cx) - })? - .await + let buffer = this + .update(cx, |this, cx| { + if !project_searchable { + this.non_searchable_buffers.insert(buffer_id); + } + this.wait_for_remote_buffer(buffer_id, cx) + })? + .await?; + if let Some(language) = language { + buffer.update(cx, |buffer, cx| { + buffer.set_language(Some(language), cx); + }); + } + Ok(buffer) }) } @@ -710,12 +718,15 @@ impl LocalBufferStore { fn create_buffer( &self, + language: Option>, project_searchable: bool, cx: &mut Context, ) -> Task>> { cx.spawn(async move |buffer_store, cx| { - let buffer = - cx.new(|cx| Buffer::local("", cx).with_language(language::PLAIN_TEXT.clone(), cx)); + let buffer = cx.new(|cx| { + Buffer::local("", cx) + .with_language(language.unwrap_or_else(|| language::PLAIN_TEXT.clone()), cx) + }); buffer_store.update(cx, |buffer_store, cx| { buffer_store.add_buffer(buffer.clone(), cx).log_err(); if !project_searchable { @@ -900,12 +911,13 @@ impl BufferStore { pub fn create_buffer( &mut self, + language: Option>, project_searchable: bool, cx: &mut Context, ) -> Task>> { match &self.state { - BufferStoreState::Local(this) => this.create_buffer(project_searchable, cx), - BufferStoreState::Remote(this) => this.create_buffer(project_searchable, cx), + BufferStoreState::Local(this) => this.create_buffer(language, project_searchable, cx), + BufferStoreState::Remote(this) => this.create_buffer(language, project_searchable, cx), } } diff --git a/crates/project/src/git_store.rs b/crates/project/src/git_store.rs index b1ee419a0d822fe9a1194121c78b7b1c5c828aac..205f1e92e4e394262901b5eb608b19ffd1e72e60 100644 --- a/crates/project/src/git_store.rs +++ b/crates/project/src/git_store.rs @@ -3966,17 +3966,18 @@ impl Repository { cx: &mut Context, ) -> Task>> { cx.spawn(async move |repository, cx| { + let git_commit_language = match language_registry { + Some(language_registry) => { + Some(language_registry.language_for_name("Git Commit").await?) + } + None => None, + }; let buffer = buffer_store - .update(cx, |buffer_store, cx| buffer_store.create_buffer(false, cx)) + .update(cx, |buffer_store, cx| { + buffer_store.create_buffer(git_commit_language, false, cx) + }) .await?; - if let Some(language_registry) = language_registry { - let git_commit_language = language_registry.language_for_name("Git Commit").await?; - buffer.update(cx, |buffer, cx| { - buffer.set_language(Some(git_commit_language), cx); - }); - } - repository.update(cx, |repository, _| { repository.commit_message_buffer = Some(buffer.clone()); })?; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f58ec9fad80a565ad33627d4e25df31219466212..e3747f85d9d895aa77f387bd16481dd163457bcb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -35,6 +35,7 @@ pub mod search_history; mod yarn; use dap::inline_value::{InlineValueLocation, VariableLookupKind, VariableScope}; +use itertools::Either; use crate::{ git_store::GitStore, @@ -198,6 +199,7 @@ pub struct Project { user_store: Entity, fs: Arc, remote_client: Option>, + // todo lw explain the client_state x remote_client matrix, its super confusing client_state: ProjectClientState, git_store: Entity, collaborators: HashMap, @@ -2727,6 +2729,19 @@ impl Project { !self.is_local() } + #[inline] + pub fn is_via_wsl_with_host_interop(&self, cx: &App) -> bool { + match &self.client_state { + ProjectClientState::Local | ProjectClientState::Shared { .. } => { + matches!( + &self.remote_client, Some(remote_client) + if remote_client.read(cx).has_wsl_interop() + ) + } + _ => false, + } + } + pub fn disable_worktree_scanner(&mut self, cx: &mut Context) { self.worktree_store.update(cx, |worktree_store, _cx| { worktree_store.disable_scanner(); @@ -2736,11 +2751,12 @@ impl Project { #[inline] pub fn create_buffer( &mut self, - searchable: bool, + language: Option>, + project_searchable: bool, cx: &mut Context, ) -> Task>> { self.buffer_store.update(cx, |buffer_store, cx| { - buffer_store.create_buffer(searchable, cx) + buffer_store.create_buffer(language, project_searchable, cx) }) } @@ -4240,6 +4256,31 @@ impl Project { }) } + /// Attempts to convert the input path to a WSL path if this is a wsl remote project and the input path is a host windows path. + pub fn try_windows_path_to_wsl( + &self, + abs_path: &Path, + cx: &App, + ) -> impl Future> + use<> { + let fut = if cfg!(windows) + && let ( + ProjectClientState::Local | ProjectClientState::Shared { .. }, + Some(remote_client), + ) = (&self.client_state, &self.remote_client) + && let RemoteConnectionOptions::Wsl(wsl) = remote_client.read(cx).connection_options() + { + Either::Left(wsl.abs_windows_path_to_wsl_path(abs_path)) + } else { + Either::Right(abs_path.to_owned()) + }; + async move { + match fut { + Either::Left(fut) => fut.await.map(Into::into), + Either::Right(path) => Ok(path), + } + } + } + pub fn find_or_create_worktree( &mut self, abs_path: impl AsRef, @@ -5183,7 +5224,7 @@ impl Project { mut cx: AsyncApp, ) -> Result { let buffer = this - .update(&mut cx, |this, cx| this.create_buffer(true, cx)) + .update(&mut cx, |this, cx| this.create_buffer(None, true, cx)) .await?; let peer_id = envelope.original_sender_id()?; diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index a840af3f22d2e5e67cf2a9e35b0254f7da693132..ac828549f0e4e40142d25365a218abb4b7fd0665 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -4152,7 +4152,7 @@ async fn test_save_file_spawns_language_server(cx: &mut gpui::TestAppContext) { ); let buffer = project - .update(cx, |this, cx| this.create_buffer(false, cx)) + .update(cx, |this, cx| this.create_buffer(None, false, cx)) .unwrap() .await; project.update(cx, |this, cx| { diff --git a/crates/project/src/worktree_store.rs b/crates/project/src/worktree_store.rs index 83a7070aa97fb68794f44aa6e20c391957f34183..754bc99ce7f112170c47f01e0e861a3fbfc45e77 100644 --- a/crates/project/src/worktree_store.rs +++ b/crates/project/src/worktree_store.rs @@ -609,9 +609,7 @@ impl WorktreeStore { scanning_enabled, cx, ) - .await; - - let worktree = worktree?; + .await?; this.update(cx, |this, cx| this.add(&worktree, cx))?; diff --git a/crates/remote/src/remote_client.rs b/crates/remote/src/remote_client.rs index 9476bc8920c1f4d847d34f171d2debb4c1ef2162..615b2b1029416294a282522117971cf7f009e701 100644 --- a/crates/remote/src/remote_client.rs +++ b/crates/remote/src/remote_client.rs @@ -899,6 +899,11 @@ impl RemoteClient { .map_or(false, |connection| connection.shares_network_interface()) } + pub fn has_wsl_interop(&self) -> bool { + self.remote_connection() + .map_or(false, |connection| connection.has_wsl_interop()) + } + pub fn build_command( &self, program: Option, diff --git a/crates/remote/src/transport/wsl.rs b/crates/remote/src/transport/wsl.rs index 18b4bcdfbdc169d11a4ec694ca2d7a7fef69289d..5721b4dca0e4588e06d1aed33496b320aa16e371 100644 --- a/crates/remote/src/transport/wsl.rs +++ b/crates/remote/src/transport/wsl.rs @@ -147,9 +147,14 @@ impl WslRemoteConnection { } async fn run_wsl_command(&self, program: &str, args: &[&str]) -> Result<()> { - run_wsl_command_impl(&self.connection_options, program, args, false) - .await - .map(|_| ()) + run_wsl_command_impl(wsl_command_impl( + &self.connection_options, + program, + args, + false, + )) + .await + .map(|_| ()) } async fn ensure_server_binary( @@ -380,15 +385,13 @@ impl RemoteConnection for WslRemoteConnection { let options = self.connection_options.clone(); async move { let wsl_src = windows_path_to_wsl_path_impl(&options, &src_path).await?; - - run_wsl_command_impl( + let command = wsl_command_impl( &options, "cp", &["-r", &wsl_src, &dest_path.to_string()], true, - ) - .await - .map_err(|e| { + ); + run_wsl_command_impl(command).await.map_err(|e| { anyhow!( "failed to upload directory {} -> {}: {}", src_path.display(), @@ -531,17 +534,34 @@ async fn sanitize_path(path: &Path) -> Result { Ok(sanitized.replace('\\', "/")) } -async fn run_wsl_command_with_output_impl( +fn run_wsl_command_with_output_impl( options: &WslConnectionOptions, program: &str, args: &[&str], -) -> Result { - match run_wsl_command_impl(options, program, args, true).await { - Ok(res) => Ok(res), - Err(exec_err) => match run_wsl_command_impl(options, program, args, false).await { +) -> impl Future> + use<> { + let exec_command = wsl_command_impl(options, program, args, true); + let command = wsl_command_impl(options, program, args, false); + async move { + match run_wsl_command_impl(exec_command).await { Ok(res) => Ok(res), - Err(e) => Err(e.context(exec_err)), - }, + Err(exec_err) => match run_wsl_command_impl(command).await { + Ok(res) => Ok(res), + Err(e) => Err(e.context(exec_err)), + }, + } + } +} + +impl WslConnectionOptions { + pub fn abs_windows_path_to_wsl_path( + &self, + source: &Path, + ) -> impl Future> + use<> { + let path_str = source.to_string_lossy(); + + let source = path_str.strip_prefix(r"\\?\").unwrap_or(&*path_str); + let source = source.replace('\\', "/"); + run_wsl_command_with_output_impl(self, "wslpath", &["-u", &source]) } } @@ -553,27 +573,23 @@ async fn windows_path_to_wsl_path_impl( run_wsl_command_with_output_impl(options, "wslpath", &["-u", &source]).await } -async fn run_wsl_command_impl( - options: &WslConnectionOptions, - program: &str, - args: &[&str], - exec: bool, -) -> Result { - let mut command = wsl_command_impl(options, program, args, exec); - let output = command - .output() - .await - .with_context(|| format!("Failed to run command '{:?}'", command))?; - - if !output.status.success() { - return Err(anyhow!( - "Command '{:?}' failed: {}", - command, - String::from_utf8_lossy(&output.stderr).trim() - )); - } +fn run_wsl_command_impl(mut command: process::Command) -> impl Future> { + async move { + let output = command + .output() + .await + .with_context(|| format!("Failed to run command '{:?}'", command))?; + + if !output.status.success() { + return Err(anyhow!( + "Command '{:?}' failed: {}", + command, + String::from_utf8_lossy(&output.stderr).trim() + )); + } - Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned()) + Ok(String::from_utf8_lossy(&output.stdout).trim().to_owned()) + } } /// Creates a new `wsl.exe` command that runs the given program with the given arguments. diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index a4a26c4a46707f9f4d4b4329441f0c6bfbe6b0dc..c309db06c82e672d2799de45a8e0d9f3f3136476 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -687,9 +687,9 @@ impl HeadlessProject { ) -> Result { let (buffer_store, buffer) = this.update(&mut cx, |this, cx| { let buffer_store = this.buffer_store.clone(); - let buffer = this - .buffer_store - .update(cx, |buffer_store, cx| buffer_store.create_buffer(true, cx)); + let buffer = this.buffer_store.update(cx, |buffer_store, cx| { + buffer_store.create_buffer(None, true, cx) + }); (buffer_store, buffer) }); diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 29c278c80ac565b8cfea229d97a6d3be36328ee5..059e50cf16acc3f296a4d6703b4c3c245932e335 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -3206,28 +3206,45 @@ impl SettingsWindow { original_window .update(cx, |workspace, window, cx| { workspace - .with_local_workspace(window, cx, |workspace, window, cx| { - let create_task = workspace.project().update(cx, |project, cx| { - project.find_or_create_worktree( - paths::config_dir().as_path(), - false, - cx, - ) - }); - let open_task = workspace.open_paths( - vec![paths::settings_file().to_path_buf()], - OpenOptions { - visible: Some(OpenVisible::None), - ..Default::default() - }, - None, - window, - cx, - ); + .with_local_or_wsl_workspace(window, cx, |workspace, window, cx| { + let project = workspace.project().clone(); cx.spawn_in(window, async move |workspace, cx| { - create_task.await.ok(); - open_task.await; + let (config_dir, settings_file) = + project.update(cx, |project, cx| { + ( + project.try_windows_path_to_wsl( + paths::config_dir().as_path(), + cx, + ), + project.try_windows_path_to_wsl( + paths::settings_file().as_path(), + cx, + ), + ) + }); + let config_dir = config_dir.await?; + let settings_file = settings_file.await?; + project + .update(cx, |project, cx| { + project.find_or_create_worktree(&config_dir, false, cx) + }) + .await + .ok(); + workspace + .update_in(cx, |workspace, window, cx| { + workspace.open_paths( + vec![settings_file], + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + None, + window, + cx, + ) + })? + .await; workspace.update_in(cx, |_, window, cx| { window.activate_window(); diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 051795c8ff9bfac8c1bca98d4de34fca9bd7e215..36f2ea4e23024a90a8b179d5d5eeb21a88ccbca4 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -361,6 +361,19 @@ impl PathStyle { } } + pub fn is_absolute(&self, path_like: &str) -> bool { + path_like.starts_with('/') + || *self == PathStyle::Windows + && (path_like.starts_with('\\') + || path_like + .chars() + .next() + .is_some_and(|c| c.is_ascii_alphabetic()) + && path_like[1..] + .strip_prefix(':') + .is_some_and(|path| path.starts_with('/') || path.starts_with('\\'))) + } + pub fn is_windows(&self) -> bool { *self == PathStyle::Windows } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5a8011bb3579f02cf6d35b764becc4178f16b68a..08e8088abf5003104a5667e74a189ac59694a809 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2392,7 +2392,7 @@ impl Workspace { cx.notify(); } - /// Call the given callback with a workspace whose project is local. + /// Call the given callback with a workspace whose project is local or remote via WSL (allowing host access). /// /// If the given workspace has a local project, then it will be passed /// to the callback. Otherwise, a new empty window will be created. @@ -2418,6 +2418,33 @@ impl Workspace { } } + /// Call the given callback with a workspace whose project is local or remote via WSL (allowing host access). + /// + /// If the given workspace has a local project, then it will be passed + /// to the callback. Otherwise, a new empty window will be created. + pub fn with_local_or_wsl_workspace( + &mut self, + window: &mut Window, + cx: &mut Context, + callback: F, + ) -> Task> + where + T: 'static, + F: 'static + FnOnce(&mut Workspace, &mut Window, &mut Context) -> T, + { + let project = self.project.read(cx); + if project.is_local() || project.is_via_wsl_with_host_interop(cx) { + Task::ready(Ok(callback(self, window, cx))) + } else { + let env = self.project.read(cx).cli_environment(cx); + let task = Self::new_local(Vec::new(), self.app_state.clone(), None, env, None, cx); + cx.spawn_in(window, async move |_vh, cx| { + let (workspace, _) = task.await?; + workspace.update(cx, callback) + }) + } + } + pub fn worktrees<'a>(&self, cx: &'a App) -> impl 'a + Iterator> { self.project.read(cx).worktrees(cx) } @@ -3075,13 +3102,7 @@ impl Workspace { cx.spawn(async move |cx| { let (worktree, path) = entry.await?; let worktree_id = worktree.read_with(cx, |t, _| t.id()); - Ok(( - worktree, - ProjectPath { - worktree_id, - path: path, - }, - )) + Ok((worktree, ProjectPath { worktree_id, path })) }) } @@ -8231,26 +8252,35 @@ pub fn create_and_open_local_file( .await?; } - let mut items = workspace + workspace .update_in(cx, |workspace, window, cx| { - workspace.with_local_workspace(window, cx, |workspace, window, cx| { - workspace.open_paths( - vec![path.to_path_buf()], - OpenOptions { - visible: Some(OpenVisible::None), - ..Default::default() - }, - None, - window, - cx, - ) + workspace.with_local_or_wsl_workspace(window, cx, |workspace, window, cx| { + let path = workspace + .project + .read_with(cx, |project, cx| project.try_windows_path_to_wsl(path, cx)); + cx.spawn_in(window, async move |workspace, cx| { + let path = path.await?; + let mut items = workspace + .update_in(cx, |workspace, window, cx| { + workspace.open_paths( + vec![path.to_path_buf()], + OpenOptions { + visible: Some(OpenVisible::None), + ..Default::default() + }, + None, + window, + cx, + ) + })? + .await; + let item = items.pop().flatten(); + item.with_context(|| format!("path {path:?} is not a file"))? + }) }) })? .await? - .await; - - let item = items.pop().flatten(); - item.with_context(|| format!("path {path:?} is not a file"))? + .await }) } diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index b4ef673a3338c632133ab21c5ff9c76ea538368e..61ac8de6251a8f43729dcbace8c27ca2c2f3f91f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -942,16 +942,15 @@ fn handle_open_request(request: OpenRequest, app_state: Arc, cx: &mut serde_json::to_string_pretty(&json_schema_value) .context("Failed to serialize JSON Schema as JSON")?; let buffer_task = workspace.update(cx, |workspace, cx| { - workspace - .project() - .update(cx, |project, cx| project.create_buffer(false, cx)) + workspace.project().update(cx, |project, cx| { + project.create_buffer(json, false, cx) + }) })?; let buffer = buffer_task.await?; workspace.update_in(cx, |workspace, window, cx| { buffer.update(cx, |buffer, cx| { - buffer.set_language(json, cx); buffer.edit([(0..0, json_schema_content)], None, cx); buffer.edit( [(0..0, format!("// {} JSON Schema\n", schema_path))], diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9bb79b78646c5d35bdb2137ee28b1a3876994c1d..5c67dc439d9fb0cb68900bfea8f3f062bc669172 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -80,7 +80,7 @@ use theme::{ActiveTheme, GlobalTheme, SystemAppearance, ThemeRegistry, ThemeSett use ui::{PopoverMenuHandle, prelude::*}; use util::markdown::MarkdownString; use util::rel_path::RelPath; -use util::{ResultExt, asset_str}; +use util::{ResultExt, asset_str, maybe}; use uuid::Uuid; use vim_mode_setting::VimModeSetting; use workspace::notifications::{ @@ -1358,98 +1358,112 @@ fn quit(_: &Quit, cx: &mut App) { fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Context) { const MAX_LINES: usize = 1000; - workspace - .with_local_workspace(window, cx, move |workspace, window, cx| { - let app_state = workspace.app_state(); - let languages = app_state.languages.clone(); - let fs = app_state.fs.clone(); - cx.spawn_in(window, async move |workspace, cx| { - let (old_log, new_log, log_language) = futures::join!( - fs.load(paths::old_log_file()), - fs.load(paths::log_file()), - languages.language_for_name("log") - ); - let log = match (old_log, new_log) { - (Err(_), Err(_)) => None, - (old_log, new_log) => { - let mut lines = VecDeque::with_capacity(MAX_LINES); - for line in old_log - .iter() - .flat_map(|log| log.lines()) - .chain(new_log.iter().flat_map(|log| log.lines())) - { - if lines.len() == MAX_LINES { - lines.pop_front(); - } - lines.push_back(line); + let app_state = workspace.app_state(); + let languages = app_state.languages.clone(); + let fs = app_state.fs.clone(); + cx.spawn_in(window, async move |workspace, cx| { + let log = { + let result = futures::join!( + fs.load(&paths::old_log_file()), + fs.load(&paths::log_file()), + languages.language_for_name("log") + ); + match result { + (Err(_), Err(e), _) => Err(e), + (old_log, new_log, lang) => { + let mut lines = VecDeque::with_capacity(MAX_LINES); + for line in old_log + .iter() + .flat_map(|log| log.lines()) + .chain(new_log.iter().flat_map(|log| log.lines())) + { + if lines.len() == MAX_LINES { + lines.pop_front(); } - Some( - lines - .into_iter() - .flat_map(|line| [line, "\n"]) - .collect::(), - ) + lines.push_back(line); } - }; - let log_language = log_language.ok(); + Ok(( + lines + .into_iter() + .flat_map(|line| [line, "\n"]) + .collect::(), + lang.ok(), + )) + } + } + }; - workspace - .update_in(cx, |workspace, window, cx| { - let Some(log) = log else { - struct OpenLogError; + let (log, log_language) = match log { + Ok((log, log_language)) => (log, log_language), + Err(e) => { + struct OpenLogError; - workspace.show_notification( - NotificationId::unique::(), - cx, - |cx| { - cx.new(|cx| { - MessageNotification::new( - format!( - "Unable to access/open log file at path {:?}", - paths::log_file().as_path() - ), - cx, - ) - }) - }, - ); - return; - }; - let project = workspace.project().clone(); - let buffer = project.update(cx, |project, cx| { - project.create_local_buffer(&log, log_language, false, cx) - }); + workspace + .update(cx, |workspace, cx| { + workspace.show_notification( + NotificationId::unique::(), + cx, + |cx| { + cx.new(|cx| { + MessageNotification::new( + format!( + "Unable to access/open log file at path \ + {}: {e:#}", + paths::log_file().display() + ), + cx, + ) + }) + }, + ); + }) + .ok(); + return; + } + }; + maybe!(async move { + let project = workspace + .read_with(cx, |workspace, _| workspace.project().clone()) + .ok()?; + let buffer = project + .update(cx, |project, cx| { + project.create_buffer(log_language, false, cx) + }) + .await + .ok()?; + buffer.update(cx, |buffer, cx| { + buffer.set_capability(Capability::ReadOnly, cx); + buffer.set_text(log, cx); + }); - let buffer = cx - .new(|cx| MultiBuffer::singleton(buffer, cx).with_title("Log".into())); - let editor = cx.new(|cx| { - let mut editor = - Editor::for_multibuffer(buffer, Some(project), window, cx); - editor.set_read_only(true); - editor.set_breadcrumb_header(format!( - "Last {} lines in {}", - MAX_LINES, - paths::log_file().display() - )); - editor - }); + let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title("Log".into())); - editor.update(cx, |editor, cx| { - let last_multi_buffer_offset = editor.buffer().read(cx).len(cx); - editor.change_selections(Default::default(), window, cx, |s| { - s.select_ranges(Some( - last_multi_buffer_offset..last_multi_buffer_offset, - )); - }) - }); + let editor = cx + .new_window_entity(|window, cx| { + let mut editor = Editor::for_multibuffer(buffer, Some(project), window, cx); + editor.set_read_only(true); + editor.set_breadcrumb_header(format!( + "Last {} lines in {}", + MAX_LINES, + paths::log_file().display() + )); + let last_multi_buffer_offset = editor.buffer().read(cx).len(cx); + editor.change_selections(Default::default(), window, cx, |s| { + s.select_ranges(Some(last_multi_buffer_offset..last_multi_buffer_offset)); + }); + editor + }) + .ok()?; - workspace.add_item_to_active_pane(Box::new(editor), None, true, window, cx); - }) - .log_err(); - }) - .detach(); + workspace + .update_in(cx, |workspace, window, cx| { + workspace.add_item_to_active_pane(Box::new(editor), None, true, window, cx); + }) + .ok() }) - .detach(); + .await; + }) + .detach(); } fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool, cx: &mut App) { @@ -1997,33 +2011,39 @@ fn open_bundled_file( cx.spawn_in(window, async move |workspace, cx| { let language = language.await.log_err(); workspace - .update_in(cx, |workspace, window, cx| { - workspace.with_local_workspace(window, cx, |workspace, window, cx| { - let project = workspace.project(); - let buffer = project.update(cx, move |project, cx| { - let buffer = - project.create_local_buffer(text.as_ref(), language, false, cx); - buffer.update(cx, |buffer, cx| { - buffer.set_capability(Capability::ReadOnly, cx); - }); - buffer + .update_in(cx, move |workspace, window, cx| { + let project = workspace.project().clone(); + let buffer = project.update(cx, move |project, cx| { + project.create_buffer(language, false, cx) + }); + cx.spawn_in(window, async move |workspace, cx| { + let buffer = buffer.await?; + buffer.update(cx, |buffer, cx| { + buffer.set_text(text.into_owned(), cx); + buffer.set_capability(Capability::ReadOnly, cx); }); let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into())); - workspace.add_item_to_active_pane( - Box::new(cx.new(|cx| { - let mut editor = - Editor::for_multibuffer(buffer, Some(project.clone()), window, cx); - editor.set_read_only(true); - editor.set_should_serialize(false, cx); - editor.set_breadcrumb_header(title.into()); - editor - })), - None, - true, - window, - cx, - ); + workspace.update_in(cx, |workspace, window, cx| { + workspace.add_item_to_active_pane( + Box::new(cx.new(|cx| { + let mut editor = Editor::for_multibuffer( + buffer, + Some(project.clone()), + window, + cx, + ); + editor.set_read_only(true); + editor.set_should_serialize(false, cx); + editor.set_breadcrumb_header(title.into()); + editor + })), + None, + true, + window, + cx, + ) + }) }) })? .await @@ -2040,8 +2060,15 @@ fn open_settings_file( cx.spawn_in(window, async move |workspace, cx| { let (worktree_creation_task, settings_open_task) = workspace .update_in(cx, |workspace, window, cx| { - workspace.with_local_workspace(window, cx, move |workspace, window, cx| { - let worktree_creation_task = workspace.project().update(cx, |project, cx| { + workspace.with_local_or_wsl_workspace(window, cx, move |workspace, window, cx| { + let project = workspace.project().clone(); + + let worktree_creation_task = cx.spawn_in(window, async move |_, cx| { + let config_dir = project + .update(cx, |project, cx| { + project.try_windows_path_to_wsl(paths::config_dir().as_path(), cx) + }) + .await?; // Set up a dedicated worktree for settings, since // otherwise we're dropping and re-starting LSP servers // for each file inside on every settings file @@ -2051,7 +2078,11 @@ fn open_settings_file( // drag and drop from OS) still have their worktrees // released on file close, causing LSP servers' // restarts. - project.find_or_create_worktree(paths::config_dir().as_path(), false, cx) + project + .update(cx, |project, cx| { + project.find_or_create_worktree(&config_dir, false, cx) + }) + .await }); let settings_open_task = create_and_open_local_file(abs_path, window, cx, default_content);