Start work on creating and saving new files

Max Brunsfeld created

Change summary

Cargo.lock                        |  12 +-
Cargo.toml                        |  10 +-
gpui/src/app.rs                   |  32 +++++++++
gpui/src/platform/mac/platform.rs |  41 +++++++++++
gpui/src/platform/mod.rs          |  13 +++
gpui/src/platform/test.rs         |   4 
zed/src/editor/buffer_view.rs     |  16 +++-
zed/src/menus.rs                  |  21 ++++-
zed/src/workspace.rs              | 112 +++++++++++++++++++++++---------
zed/src/worktree.rs               |  41 +++++++-----
10 files changed, 227 insertions(+), 75 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -449,7 +449,7 @@ dependencies = [
 [[package]]
 name = "cocoa"
 version = "0.24.0"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 dependencies = [
  "bitflags 1.2.1",
  "block",
@@ -464,7 +464,7 @@ dependencies = [
 [[package]]
 name = "cocoa-foundation"
 version = "0.1.0"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 dependencies = [
  "bitflags 1.2.1",
  "block",
@@ -499,7 +499,7 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
 [[package]]
 name = "core-foundation"
 version = "0.9.1"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 dependencies = [
  "core-foundation-sys",
  "libc",
@@ -508,12 +508,12 @@ dependencies = [
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.2"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 
 [[package]]
 name = "core-graphics"
 version = "0.22.2"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 dependencies = [
  "bitflags 1.2.1",
  "core-foundation",
@@ -525,7 +525,7 @@ dependencies = [
 [[package]]
 name = "core-graphics-types"
 version = "0.1.1"
-source = "git+https://github.com/servo/core-foundation-rs?rev=e9a65bb15d591ec22649e03659db8095d4f2dd60#e9a65bb15d591ec22649e03659db8095d4f2dd60"
+source = "git+https://github.com/zed-industries/core-foundation-rs?rev=39e1e0eeef11a17cf49aa6a500c37e665d967d2a#39e1e0eeef11a17cf49aa6a500c37e665d967d2a"
 dependencies = [
  "bitflags 1.2.1",
  "core-foundation",

Cargo.toml πŸ”—

@@ -4,11 +4,11 @@ members = ["zed", "gpui", "fsevent", "scoped_pool"]
 [patch.crates-io]
 async-task = {git = "https://github.com/zed-industries/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
 
-# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/454
-cocoa = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-cocoa-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-core-foundation = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
-core-graphics = {git = "https://github.com/servo/core-foundation-rs", rev = "e9a65bb15d591ec22649e03659db8095d4f2dd60"}
+# TODO - Remove when a version is released with this PR: https://github.com/servo/core-foundation-rs/pull/457
+cocoa = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "39e1e0eeef11a17cf49aa6a500c37e665d967d2a"}
+cocoa-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "39e1e0eeef11a17cf49aa6a500c37e665d967d2a"}
+core-foundation = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "39e1e0eeef11a17cf49aa6a500c37e665d967d2a"}
+core-graphics = {git = "https://github.com/zed-industries/core-foundation-rs", rev = "39e1e0eeef11a17cf49aa6a500c37e665d967d2a"}
 
 [profile.dev]
 split-debuginfo = "unpacked"

gpui/src/app.rs πŸ”—

@@ -21,7 +21,7 @@ use std::{
     fmt::{self, Debug},
     hash::{Hash, Hasher},
     marker::PhantomData,
-    path::PathBuf,
+    path::{Path, PathBuf},
     rc::{self, Rc},
     sync::{Arc, Weak},
     time::Duration,
@@ -586,6 +586,22 @@ impl MutableAppContext {
         );
     }
 
+    pub fn prompt_for_new_path<F>(&self, directory: &Path, done_fn: F)
+    where
+        F: 'static + FnOnce(Option<PathBuf>, &mut MutableAppContext),
+    {
+        let app = self.weak_self.as_ref().unwrap().upgrade().unwrap();
+        let foreground = self.foreground.clone();
+        self.platform().prompt_for_new_path(
+            directory,
+            Box::new(move |path| {
+                foreground
+                    .spawn(async move { (done_fn)(path, &mut *app.borrow_mut()) })
+                    .detach();
+            }),
+        );
+    }
+
     pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) {
         self.pending_effects
             .push_back(Effect::ViewNotification { window_id, view_id });
@@ -1765,6 +1781,20 @@ impl<'a, T: View> ViewContext<'a, T> {
         &self.app.ctx.background
     }
 
+    pub fn prompt_for_paths<F>(&self, options: PathPromptOptions, done_fn: F)
+    where
+        F: 'static + FnOnce(Option<Vec<PathBuf>>, &mut MutableAppContext),
+    {
+        self.app.prompt_for_paths(options, done_fn)
+    }
+
+    pub fn prompt_for_new_path<F>(&self, directory: &Path, done_fn: F)
+    where
+        F: 'static + FnOnce(Option<PathBuf>, &mut MutableAppContext),
+    {
+        self.app.prompt_for_new_path(directory, done_fn)
+    }
+
     pub fn debug_elements(&self) -> crate::json::Value {
         self.app.debug_elements(self.window_id).unwrap()
     }

gpui/src/platform/mac/platform.rs πŸ”—

@@ -5,7 +5,7 @@ use cocoa::{
     appkit::{
         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
         NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
-        NSPasteboardTypeString, NSWindow,
+        NSPasteboardTypeString, NSSavePanel, NSWindow,
     },
     base::{id, nil, selector},
     foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSURL},
@@ -25,7 +25,7 @@ use std::{
     convert::TryInto,
     ffi::{c_void, CStr},
     os::raw::c_char,
-    path::PathBuf,
+    path::{Path, PathBuf},
     ptr,
     rc::Rc,
     slice, str,
@@ -305,6 +305,43 @@ impl platform::Platform for MacPlatform {
         }
     }
 
+    fn prompt_for_new_path(
+        &self,
+        directory: &Path,
+        done_fn: Box<dyn FnOnce(Option<std::path::PathBuf>)>,
+    ) {
+        unsafe {
+            let panel = NSSavePanel::savePanel(nil);
+            let path = ns_string(directory.to_string_lossy().as_ref());
+            let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+            panel.setDirectoryURL(url);
+
+            let done_fn = Cell::new(Some(done_fn));
+            let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                let result = if response == NSModalResponse::NSModalResponseOk {
+                    let url = panel.URL();
+                    let string = url.absoluteString();
+                    let string = std::ffi::CStr::from_ptr(string.UTF8String())
+                        .to_string_lossy()
+                        .to_string();
+                    if let Some(path) = string.strip_prefix("file://") {
+                        Some(PathBuf::from(path))
+                    } else {
+                        None
+                    }
+                } else {
+                    None
+                };
+
+                if let Some(done_fn) = done_fn.take() {
+                    (done_fn)(result);
+                }
+            });
+            let block = block.copy();
+            let _: () = msg_send![panel, beginWithCompletionHandler: block];
+        }
+    }
+
     fn fonts(&self) -> Arc<dyn platform::FontSystem> {
         self.fonts.clone()
     }

gpui/src/platform/mod.rs πŸ”—

@@ -19,7 +19,13 @@ use crate::{
 };
 use async_task::Runnable;
 pub use event::Event;
-use std::{any::Any, ops::Range, path::PathBuf, rc::Rc, sync::Arc};
+use std::{
+    any::Any,
+    ops::Range,
+    path::{Path, PathBuf},
+    rc::Rc,
+    sync::Arc,
+};
 
 pub trait Platform {
     fn on_menu_command(&self, callback: Box<dyn FnMut(&str, Option<&dyn Any>)>);
@@ -45,6 +51,11 @@ pub trait Platform {
         options: PathPromptOptions,
         done_fn: Box<dyn FnOnce(Option<Vec<std::path::PathBuf>>)>,
     );
+    fn prompt_for_new_path(
+        &self,
+        directory: &Path,
+        done_fn: Box<dyn FnOnce(Option<std::path::PathBuf>)>,
+    );
     fn quit(&self);
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;

gpui/src/platform/test.rs πŸ”—

@@ -1,6 +1,6 @@
 use crate::ClipboardItem;
 use pathfinder_geometry::vector::Vector2F;
-use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
+use std::{any::Any, cell::RefCell, path::Path, rc::Rc, sync::Arc};
 
 struct Platform {
     dispatcher: Arc<dyn super::Dispatcher>,
@@ -77,6 +77,8 @@ impl super::Platform for Platform {
     ) {
     }
 
+    fn prompt_for_new_path(&self, _: &Path, _: Box<dyn FnOnce(Option<std::path::PathBuf>)>) {}
+
     fn write_to_clipboard(&self, item: ClipboardItem) {
         *self.current_clipboard_item.borrow_mut() = Some(item);
     }

zed/src/editor/buffer_view.rs πŸ”—

@@ -6,11 +6,10 @@ use crate::{settings::Settings, watch, workspace, worktree::FileHandle};
 use anyhow::Result;
 use futures_core::future::LocalBoxFuture;
 use gpui::{
-    fonts::Properties as FontProperties, keymap::Binding, text_layout, AppContext, ClipboardItem,
-    Element, ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, View, ViewContext,
-    WeakViewHandle,
+    fonts::Properties as FontProperties, geometry::vector::Vector2F, keymap::Binding, text_layout,
+    AppContext, ClipboardItem, Element, ElementBox, Entity, FontCache, ModelHandle,
+    MutableAppContext, TextLayoutCache, View, ViewContext, WeakViewHandle,
 };
-use gpui::{geometry::vector::Vector2F, TextLayoutCache};
 use parking_lot::Mutex;
 use serde::{Deserialize, Serialize};
 use smallvec::SmallVec;
@@ -2135,7 +2134,14 @@ impl workspace::ItemView for BufferView {
         Some(clone)
     }
 
-    fn save(&self, ctx: &mut ViewContext<Self>) -> LocalBoxFuture<'static, Result<()>> {
+    fn save(
+        &mut self,
+        file: Option<FileHandle>,
+        ctx: &mut ViewContext<Self>,
+    ) -> LocalBoxFuture<'static, Result<()>> {
+        if file.is_some() {
+            self.file = file;
+        }
         if let Some(file) = self.file.as_ref() {
             self.buffer
                 .update(ctx, |buffer, ctx| buffer.save(file, ctx))

zed/src/menus.rs πŸ”—

@@ -24,12 +24,21 @@ pub fn menus(settings: Receiver<Settings>) -> Vec<Menu<'static>> {
         },
         Menu {
             name: "File",
-            items: vec![MenuItem::Action {
-                name: "Open…",
-                keystroke: Some("cmd-o"),
-                action: "workspace:open",
-                arg: Some(Box::new(settings)),
-            }],
+            items: vec![
+                MenuItem::Action {
+                    name: "New",
+                    keystroke: Some("cmd-n"),
+                    action: "workspace:new_file",
+                    arg: None,
+                },
+                MenuItem::Separator,
+                MenuItem::Action {
+                    name: "Open…",
+                    keystroke: Some("cmd-o"),
+                    action: "workspace:open",
+                    arg: Some(Box::new(settings)),
+                },
+            ],
         },
         Menu {
             name: "Edit",

zed/src/workspace.rs πŸ”—

@@ -6,6 +6,7 @@ pub use pane_group::*;
 use crate::{
     settings::Settings,
     watch::{self, Receiver},
+    worktree::FileHandle,
 };
 use gpui::{MutableAppContext, PathPromptOptions};
 use std::path::PathBuf;
@@ -15,6 +16,7 @@ pub fn init(app: &mut MutableAppContext) {
     app.add_global_action("app:quit", quit);
     app.add_action("workspace:save", Workspace::save_active_item);
     app.add_action("workspace:debug_elements", Workspace::debug_elements);
+    app.add_action("workspace:new_file", Workspace::open_new_file);
     app.add_bindings(vec![
         Binding::new("cmd-s", "workspace:save", None),
         Binding::new("cmd-alt-i", "workspace:debug_elements", None),
@@ -108,7 +110,11 @@ pub trait ItemView: View {
     fn is_dirty(&self, _: &AppContext) -> bool {
         false
     }
-    fn save(&self, _: &mut ViewContext<Self>) -> LocalBoxFuture<'static, anyhow::Result<()>> {
+    fn save(
+        &mut self,
+        _: Option<FileHandle>,
+        _: &mut ViewContext<Self>,
+    ) -> LocalBoxFuture<'static, anyhow::Result<()>> {
         Box::pin(async { Ok(()) })
     }
     fn should_activate_item_on_event(_: &Self::Event) -> bool {
@@ -128,7 +134,11 @@ pub trait ItemViewHandle: Send + Sync {
     fn id(&self) -> usize;
     fn to_any(&self) -> AnyViewHandle;
     fn is_dirty(&self, ctx: &AppContext) -> bool;
-    fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>>;
+    fn save(
+        &self,
+        file: Option<FileHandle>,
+        ctx: &mut MutableAppContext,
+    ) -> LocalBoxFuture<'static, anyhow::Result<()>>;
 }
 
 impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
@@ -167,8 +177,12 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
         })
     }
 
-    fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>> {
-        self.update(ctx, |item, ctx| item.save(ctx))
+    fn save(
+        &self,
+        file: Option<FileHandle>,
+        ctx: &mut MutableAppContext,
+    ) -> LocalBoxFuture<'static, anyhow::Result<()>> {
+        self.update(ctx, |item, ctx| item.save(file, ctx))
     }
 
     fn is_dirty(&self, ctx: &AppContext) -> bool {
@@ -209,6 +223,7 @@ pub struct Workspace {
         (usize, u64),
         postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
     >,
+    untitled_buffers: HashSet<ModelHandle<Buffer>>,
 }
 
 impl Workspace {
@@ -234,6 +249,7 @@ impl Workspace {
             replica_id,
             worktrees: Default::default(),
             buffers: Default::default(),
+            untitled_buffers: Default::default(),
         }
     }
 
@@ -272,15 +288,7 @@ impl Workspace {
         let entries = paths
             .iter()
             .cloned()
-            .map(|path| {
-                for tree in self.worktrees.iter() {
-                    if let Ok(relative_path) = path.strip_prefix(tree.read(ctx).abs_path()) {
-                        return (tree.id(), relative_path.into());
-                    }
-                }
-                let worktree_id = self.add_worktree(&path, ctx);
-                (worktree_id, Path::new("").into())
-            })
+            .map(|path| self.file_for_path(&path, ctx))
             .collect::<Vec<_>>();
 
         let bg = ctx.background_executor().clone();
@@ -288,12 +296,12 @@ impl Workspace {
             .iter()
             .cloned()
             .zip(entries.into_iter())
-            .map(|(path, entry)| {
+            .map(|(abs_path, file)| {
                 ctx.spawn(
-                    bg.spawn(async move { path.is_file() }),
-                    |me, is_file, ctx| {
+                    bg.spawn(async move { abs_path.is_file() }),
+                    move |me, is_file, ctx| {
                         if is_file {
-                            me.open_entry(entry, ctx)
+                            me.open_entry(file.entry_id(), ctx)
                         } else {
                             None
                         }
@@ -310,13 +318,26 @@ impl Workspace {
         }
     }
 
-    pub fn add_worktree(&mut self, path: &Path, ctx: &mut ViewContext<Self>) -> usize {
+    fn file_for_path(&mut self, abs_path: &Path, ctx: &mut ViewContext<Self>) -> FileHandle {
+        for tree in self.worktrees.iter() {
+            if let Ok(relative_path) = abs_path.strip_prefix(tree.read(ctx).abs_path()) {
+                return tree.file(relative_path, ctx.as_ref());
+            }
+        }
+        let worktree = self.add_worktree(&abs_path, ctx);
+        worktree.file(Path::new(""), ctx.as_ref())
+    }
+
+    pub fn add_worktree(
+        &mut self,
+        path: &Path,
+        ctx: &mut ViewContext<Self>,
+    ) -> ModelHandle<Worktree> {
         let worktree = ctx.add_model(|ctx| Worktree::new(path, ctx));
-        let worktree_id = worktree.id();
         ctx.observe_model(&worktree, |_, _, ctx| ctx.notify());
-        self.worktrees.insert(worktree);
+        self.worktrees.insert(worktree.clone());
         ctx.notify();
-        worktree_id
+        worktree
     }
 
     pub fn toggle_modal<V, F>(&mut self, ctx: &mut ViewContext<Self>, add_view: F)
@@ -346,6 +367,15 @@ impl Workspace {
         }
     }
 
+    pub fn open_new_file(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        let buffer = ctx.add_model(|_| Buffer::new(self.replica_id, ""));
+        let buffer_view = Box::new(ctx.add_view(|ctx| {
+            BufferView::for_buffer(buffer.clone(), None, self.settings.clone(), ctx)
+        }));
+        self.untitled_buffers.insert(buffer);
+        self.add_item(buffer_view, ctx);
+    }
+
     #[must_use]
     pub fn open_entry(
         &mut self,
@@ -381,13 +411,11 @@ impl Workspace {
             }
         };
 
-        let file = match worktree.file(path.clone(), ctx.as_ref()) {
-            Some(file) => file,
-            None => {
-                log::error!("path {:?} does not exist", path);
-                return None;
-            }
-        };
+        let file = worktree.file(path.clone(), ctx.as_ref());
+        if file.is_deleted() {
+            log::error!("path {:?} does not exist", path);
+            return None;
+        }
 
         self.loading_entries.insert(entry.clone());
 
@@ -441,12 +469,34 @@ impl Workspace {
     }
 
     pub fn save_active_item(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
-        self.active_pane.update(ctx, |pane, ctx| {
+        let handle = ctx.handle();
+        let first_worktree = self.worktrees.iter().next();
+        self.active_pane.update(ctx, move |pane, ctx| {
             if let Some(item) = pane.active_item() {
-                let task = item.save(ctx.as_mut());
+                if item.entry_id(ctx.as_ref()).is_none() {
+                    let start_path = first_worktree
+                        .map_or(Path::new(""), |h| h.read(ctx).abs_path())
+                        .to_path_buf();
+                    ctx.prompt_for_new_path(&start_path, move |path, ctx| {
+                        if let Some(path) = path {
+                            handle.update(ctx, move |this, ctx| {
+                                let file = this.file_for_path(&path, ctx);
+                                let task = item.save(Some(file), ctx.as_mut());
+                                ctx.spawn(task, |_, result, _| {
+                                    if let Err(e) = result {
+                                        error!("failed to save item: {:?}, ", e);
+                                    }
+                                })
+                                .detach()
+                            })
+                        }
+                    });
+                    return;
+                }
+
+                let task = item.save(None, ctx.as_mut());
                 ctx.spawn(task, |_, result, _| {
                     if let Err(e) = result {
-                        // TODO - present this error to the user
                         error!("failed to save item: {:?}, ", e);
                     }
                 })

zed/src/worktree.rs πŸ”—

@@ -1126,31 +1126,38 @@ struct UpdateIgnoreStatusJob {
 }
 
 pub trait WorktreeHandle {
-    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Option<FileHandle>;
+    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> FileHandle;
 }
 
 impl WorktreeHandle for ModelHandle<Worktree> {
-    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Option<FileHandle> {
+    fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> FileHandle {
+        let path = path.as_ref();
         let tree = self.read(app);
-        let entry = tree.entry_for_path(&path)?;
-
-        let path = entry.path().clone();
         let mut handles = tree.handles.lock();
-        let state = if let Some(state) = handles.get(&path).and_then(Weak::upgrade) {
+        let state = if let Some(state) = handles.get(path).and_then(Weak::upgrade) {
             state
         } else {
-            let state = Arc::new(Mutex::new(FileHandleState {
-                path: path.clone(),
-                is_deleted: false,
-            }));
-            handles.insert(path, Arc::downgrade(&state));
+            let handle_state = if let Some(entry) = tree.entry_for_path(path) {
+                FileHandleState {
+                    path: entry.path().clone(),
+                    is_deleted: false,
+                }
+            } else {
+                FileHandleState {
+                    path: path.into(),
+                    is_deleted: true,
+                }
+            };
+
+            let state = Arc::new(Mutex::new(handle_state.clone()));
+            handles.insert(handle_state.path, Arc::downgrade(&state));
             state
         };
 
-        Some(FileHandle {
+        FileHandle {
             worktree: self.clone(),
             state,
-        })
+        }
     }
 }
 
@@ -1389,10 +1396,10 @@ mod tests {
 
             let (file2, file3, file4, file5) = app.read(|ctx| {
                 (
-                    tree.file("a/file2", ctx).unwrap(),
-                    tree.file("a/file3", ctx).unwrap(),
-                    tree.file("b/c/file4", ctx).unwrap(),
-                    tree.file("b/c/file5", ctx).unwrap(),
+                    tree.file("a/file2", ctx),
+                    tree.file("a/file3", ctx),
+                    tree.file("b/c/file4", ctx),
+                    tree.file("b/c/file5", ctx),
                 )
             });