From 88b88a80672014b51b5ed1c79d8bf79985a5715f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 28 Apr 2021 17:46:27 -0700 Subject: [PATCH] Start work on opening files --- zed/src/editor/buffer/mod.rs | 7 ++- zed/src/editor/buffer_view.rs | 7 +-- zed/src/workspace/mod.rs | 10 ++-- zed/src/workspace/workspace.rs | 25 +++++---- zed/src/workspace/workspace_view.rs | 82 +++++++++++++++++++++++++++-- zed/src/worktree.rs | 24 ++++++++- 6 files changed, 132 insertions(+), 23 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 597990fbb3..55ffaec028 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -18,11 +18,12 @@ use crate::{ worktree::FileHandle, }; use anyhow::{anyhow, Result}; -use gpui::{Entity, ModelContext}; +use gpui::{AppContext, Entity, ModelContext}; use lazy_static::lazy_static; use rand::prelude::*; use std::{ cmp, + ffi::OsString, hash::BuildHasher, iter::{self, Iterator}, ops::{AddAssign, Range}, @@ -447,6 +448,10 @@ impl Buffer { } } + pub fn file_name(&self, ctx: &AppContext) -> Option { + self.file.as_ref().and_then(|file| file.file_name(ctx)) + } + pub fn path(&self) -> Option> { self.file.as_ref().map(|file| file.path()) } diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 77cf3bf779..bd74083193 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -1362,11 +1362,8 @@ impl workspace::ItemView for BufferView { } fn title(&self, app: &AppContext) -> std::string::String { - if let Some(path) = self.buffer.read(app).path() { - path.file_name() - .expect("buffer's path is always to a file") - .to_string_lossy() - .into() + if let Some(name) = self.buffer.read(app).file_name(app) { + name.to_string_lossy().into() } else { "untitled".into() } diff --git a/zed/src/workspace/mod.rs b/zed/src/workspace/mod.rs index 005b4b2435..ae415c0e71 100644 --- a/zed/src/workspace/mod.rs +++ b/zed/src/workspace/mod.rs @@ -52,7 +52,7 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { if let Some(handle) = app.root_view::(window_id) { if handle.update(app, |view, ctx| { if view.contains_paths(¶ms.paths, ctx.as_ref()) { - view.open_paths(¶ms.paths, ctx.as_mut()); + view.open_paths(¶ms.paths, ctx); log::info!("open paths on existing workspace"); true } else { @@ -67,8 +67,12 @@ fn open_paths(params: &OpenParams, app: &mut MutableAppContext) { log::info!("open new workspace"); // Add a new workspace if necessary - let workspace = app.add_model(|ctx| Workspace::new(params.paths.clone(), ctx)); - app.add_window(|ctx| WorkspaceView::new(workspace, params.settings.clone(), ctx)); + let workspace = app.add_model(|ctx| Workspace::new(vec![], ctx)); + app.add_window(|ctx| { + let view = WorkspaceView::new(workspace, params.settings.clone(), ctx); + view.open_paths(¶ms.paths, ctx); + view + }); } fn quit(_: &(), app: &mut MutableAppContext) { diff --git a/zed/src/workspace/workspace.rs b/zed/src/workspace/workspace.rs index 925781e340..0f02729572 100644 --- a/zed/src/workspace/workspace.rs +++ b/zed/src/workspace/workspace.rs @@ -117,23 +117,31 @@ impl Workspace { .any(|worktree| worktree.read(app).contains_abs_path(path)) } - pub fn open_paths(&mut self, paths: &[PathBuf], ctx: &mut ModelContext) { - for path in paths.iter().cloned() { - self.open_path(path, ctx); - } + pub fn open_paths( + &mut self, + paths: &[PathBuf], + ctx: &mut ModelContext, + ) -> Vec<(usize, Arc)> { + paths + .iter() + .cloned() + .map(move |path| self.open_path(path, ctx)) + .collect() } - pub fn open_path<'a>(&'a mut self, path: PathBuf, ctx: &mut ModelContext) { + fn open_path(&mut self, path: PathBuf, ctx: &mut ModelContext) -> (usize, Arc) { for tree in self.worktrees.iter() { - if tree.read(ctx).contains_abs_path(&path) { - return; + if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) { + return (tree.id(), relative_path.into()); } } - let worktree = ctx.add_model(|ctx| Worktree::new(path, ctx)); + let worktree = ctx.add_model(|ctx| Worktree::new(path.clone(), ctx)); + let worktree_id = worktree.id(); ctx.observe(&worktree, Self::on_worktree_updated); self.worktrees.insert(worktree); ctx.notify(); + (worktree_id, Path::new("").into()) } pub fn open_entry( @@ -174,7 +182,6 @@ impl Workspace { let replica_id = self.replica_id; let file = worktree.file(path.clone(), ctx.as_ref())?; let history = file.load_history(ctx.as_ref()); - // let buffer = async move { Ok(Buffer::from_history(replica_id, file, history.await?)) }; let (mut tx, rx) = watch::channel(None); self.items.insert(item_key, OpenedItem::Loading(rx)); diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index f8ca8822b2..e698a3a278 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -161,9 +161,23 @@ impl WorkspaceView { self.workspace.read(app).contains_paths(paths, app) } - pub fn open_paths(&self, paths: &[PathBuf], app: &mut MutableAppContext) { - self.workspace - .update(app, |workspace, ctx| workspace.open_paths(paths, ctx)); + pub fn open_paths(&self, paths: &[PathBuf], ctx: &mut ViewContext) { + let entries = self + .workspace + .update(ctx, |workspace, ctx| workspace.open_paths(paths, ctx)); + for (i, entry) in entries.into_iter().enumerate() { + let path = paths[i].clone(); + ctx.spawn( + ctx.background_executor() + .spawn(async move { path.is_file() }), + |me, is_file, ctx| { + if is_file { + me.open_entry(entry, ctx) + } + }, + ) + .detach(); + } } pub fn toggle_modal(&mut self, ctx: &mut ViewContext, add_view: F) @@ -382,6 +396,7 @@ mod tests { use crate::{settings, test::temp_tree, workspace::WorkspaceHandle as _}; use gpui::App; use serde_json::json; + use std::collections::HashSet; #[test] fn test_open_entry() { @@ -444,6 +459,67 @@ mod tests { }); } + #[test] + fn test_open_paths() { + App::test_async((), |mut app| async move { + let dir1 = temp_tree(json!({ + "a.txt": "", + })); + let dir2 = temp_tree(json!({ + "b.txt": "", + })); + + let workspace = app.add_model(|ctx| Workspace::new(vec![dir1.path().into()], ctx)); + let settings = settings::channel(&app.font_cache()).unwrap().1; + let (_, workspace_view) = + app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx)); + app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx)) + .await; + + // Open a file within an existing worktree. + app.update(|ctx| { + workspace_view.update(ctx, |view, ctx| { + view.open_paths(&[dir1.path().join("a.txt")], ctx); + }); + }); + workspace_view + .condition(&app, |view, ctx| { + view.active_pane() + .read(ctx) + .active_item() + .map_or(false, |item| item.title(&ctx) == "a.txt") + }) + .await; + + // Open a file outside of any existing worktree. + app.update(|ctx| { + workspace_view.update(ctx, |view, ctx| { + view.open_paths(&[dir2.path().join("b.txt")], ctx); + }); + let worktree_roots = workspace + .read(ctx) + .worktrees() + .iter() + .map(|w| w.read(ctx).abs_path()) + .collect::>(); + assert_eq!( + worktree_roots, + vec![dir1.path(), &dir2.path().join("b.txt")] + .into_iter() + .collect(), + ); + }); + workspace_view + .condition(&app, |view, ctx| { + view.active_pane() + .read(ctx) + .active_item() + .map_or(false, |item| item.title(&ctx) == "b.txt") + }) + .await; + }); + } + #[test] fn test_pane_actions() { App::test_async((), |mut app| async move { diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 2ac744e8b8..be1f1c2307 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -20,7 +20,7 @@ use smol::{channel::Sender, Timer}; use std::{ cmp, collections::{HashMap, HashSet}, - ffi::{CStr, OsStr}, + ffi::{CStr, OsStr, OsString}, fmt, fs, future::Future, io::{self, Read, Write}, @@ -163,6 +163,10 @@ impl Worktree { self.snapshot.clone() } + pub fn abs_path(&self) -> &Path { + self.snapshot.abs_path.as_ref() + } + pub fn contains_abs_path(&self, path: &Path) -> bool { path.starts_with(&self.snapshot.abs_path) } @@ -172,7 +176,11 @@ impl Worktree { path: &Path, ctx: &AppContext, ) -> impl Future> { - let abs_path = self.snapshot.abs_path.join(path); + let abs_path = if path.file_name().is_some() { + self.snapshot.abs_path.join(path) + } else { + self.snapshot.abs_path.to_path_buf() + }; ctx.background_executor().spawn(async move { let mut file = std::fs::File::open(&abs_path)?; let mut base_text = String::new(); @@ -381,10 +389,22 @@ impl fmt::Debug for Snapshot { } impl FileHandle { + /// Returns this file's path relative to the root of its worktree. pub fn path(&self) -> Arc { self.state.lock().path.clone() } + /// Returns the last component of this handle's absolute path. If this handle refers to the root + /// of its worktree, then this method will return the name of the worktree itself. + pub fn file_name<'a>(&'a self, ctx: &'a AppContext) -> Option { + self.state + .lock() + .path + .file_name() + .or_else(|| self.worktree.read(ctx).abs_path().file_name()) + .map(Into::into) + } + pub fn is_deleted(&self) -> bool { self.state.lock().is_deleted }