Detailed changes
@@ -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| {
@@ -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");
@@ -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);
});
@@ -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<Workspace>,
) {
- struct ViewReleaseNotesLocallyError;
+ struct ViewReleaseNotesError;
workspace.show_notification(
- NotificationId::unique::<ViewReleaseNotesLocallyError>(),
+ NotificationId::unique::<ViewReleaseNotesError>(),
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<ReleaseNotesBody> =
- 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> =
- 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<ReleaseNotesBody> = 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> = 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
@@ -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();
@@ -81,7 +81,9 @@ pub async fn apply_diff(
Entry::Vacant(entry) => {
let buffer: Entity<Buffer> = 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
@@ -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?
};
@@ -2929,7 +2929,7 @@ impl Editor {
cx: &mut Context<Workspace>,
) -> Task<Result<Entity<Editor>>> {
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<Workspace>,
) {
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?;
@@ -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")?;
@@ -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 =
@@ -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<String> {
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();
@@ -92,43 +92,38 @@ pub fn init(cx: &mut App) {
window: &mut Window,
cx: &mut Context<Workspace>,
) {
- workspace
- .with_local_workspace(window, cx, |workspace, window, cx| {
- let existing = workspace
- .active_pane()
- .read(cx)
- .items()
- .find_map(|item| item.downcast::<KeymapEditor>());
-
- 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::<KeymapEditor>());
- 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| {
@@ -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()
@@ -331,6 +331,7 @@ impl RemoteBufferStore {
fn create_buffer(
&self,
+ language: Option<Arc<Language>>,
project_searchable: bool,
cx: &mut Context<BufferStore>,
) -> Task<Result<Entity<Buffer>>> {
@@ -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<Arc<Language>>,
project_searchable: bool,
cx: &mut Context<BufferStore>,
) -> Task<Result<Entity<Buffer>>> {
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<Arc<Language>>,
project_searchable: bool,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Buffer>>> {
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),
}
}
@@ -3966,17 +3966,18 @@ impl Repository {
cx: &mut Context<Self>,
) -> Task<Result<Entity<Buffer>>> {
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());
})?;
@@ -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<UserStore>,
fs: Arc<dyn Fs>,
remote_client: Option<Entity<RemoteClient>>,
+ // todo lw explain the client_state x remote_client matrix, its super confusing
client_state: ProjectClientState,
git_store: Entity<GitStore>,
collaborators: HashMap<proto::PeerId, Collaborator>,
@@ -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>) {
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<Arc<Language>>,
+ project_searchable: bool,
cx: &mut Context<Self>,
) -> Task<Result<Entity<Buffer>>> {
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<Output = Result<PathBuf>> + 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<Path>,
@@ -5183,7 +5224,7 @@ impl Project {
mut cx: AsyncApp,
) -> Result<proto::OpenBufferResponse> {
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()?;
@@ -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| {
@@ -609,9 +609,7 @@ impl WorktreeStore {
scanning_enabled,
cx,
)
- .await;
-
- let worktree = worktree?;
+ .await?;
this.update(cx, |this, cx| this.add(&worktree, cx))?;
@@ -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<String>,
@@ -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<String> {
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<String> {
- 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<Output = Result<String>> + 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<Output = Result<String>> + 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<String> {
- 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<Output = Result<String>> {
+ 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.
@@ -687,9 +687,9 @@ impl HeadlessProject {
) -> Result<proto::OpenBufferResponse> {
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)
});
@@ -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();
@@ -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
}
@@ -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<T, F>(
+ &mut self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ callback: F,
+ ) -> Task<Result<T>>
+ where
+ T: 'static,
+ F: 'static + FnOnce(&mut Workspace, &mut Window, &mut Context<Workspace>) -> 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<Item = Entity<Worktree>> {
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
})
}
@@ -942,16 +942,15 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, 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))],
@@ -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<Workspace>) {
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::<String>(),
- )
+ lines.push_back(line);
}
- };
- let log_language = log_language.ok();
+ Ok((
+ lines
+ .into_iter()
+ .flat_map(|line| [line, "\n"])
+ .collect::<String>(),
+ 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::<OpenLogError>(),
- 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::<OpenLogError>(),
+ 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);