mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
WIP
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
parent
ec2e1c3045
commit
c9d7249305
@ -18,7 +18,7 @@ use crate::{
|
||||
worktree::FileHandle,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui::{AppContext, Entity, ModelContext};
|
||||
use gpui::{Entity, ModelContext};
|
||||
use lazy_static::lazy_static;
|
||||
use rand::prelude::*;
|
||||
use std::{
|
||||
@ -26,7 +26,7 @@ use std::{
|
||||
hash::BuildHasher,
|
||||
iter::{self, Iterator},
|
||||
ops::{AddAssign, Range},
|
||||
path::PathBuf,
|
||||
path::Path,
|
||||
str,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@ -429,11 +429,11 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self, app: &AppContext) -> Option<PathBuf> {
|
||||
self.file.as_ref().map(|file| file.path(app))
|
||||
pub fn path(&self) -> Option<&Arc<Path>> {
|
||||
self.file.as_ref().map(|file| file.path())
|
||||
}
|
||||
|
||||
pub fn entry_id(&self) -> Option<(usize, u64)> {
|
||||
pub fn entry_id(&self) -> Option<(usize, Arc<Path>)> {
|
||||
self.file.as_ref().map(|file| file.entry_id())
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ use std::{
|
||||
fmt::Write,
|
||||
iter::FromIterator,
|
||||
ops::Range,
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
@ -1375,7 +1376,7 @@ impl workspace::ItemView for BufferView {
|
||||
}
|
||||
|
||||
fn title(&self, app: &AppContext) -> std::string::String {
|
||||
if let Some(path) = self.buffer.read(app).path(app) {
|
||||
if let Some(path) = self.buffer.read(app).path() {
|
||||
path.file_name()
|
||||
.expect("buffer's path is always to a file")
|
||||
.to_string_lossy()
|
||||
@ -1385,7 +1386,7 @@ impl workspace::ItemView for BufferView {
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)> {
|
||||
self.buffer.read(app).entry_id()
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ use gpui::{
|
||||
AppContext, Axis, Border, Entity, ModelHandle, MutableAppContext, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use std::{cmp, path::Path};
|
||||
use std::{cmp, path::Path, sync::Arc};
|
||||
|
||||
pub struct FileFinder {
|
||||
handle: WeakViewHandle<Self>,
|
||||
@ -44,7 +44,7 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
Selected(usize, u64),
|
||||
Selected(usize, Arc<Path>),
|
||||
Dismissed,
|
||||
}
|
||||
|
||||
@ -137,18 +137,18 @@ impl FileFinder {
|
||||
app: &AppContext,
|
||||
) -> Option<ElementBox> {
|
||||
let tree_id = path_match.tree_id;
|
||||
let entry_id = path_match.entry_id;
|
||||
|
||||
self.worktree(tree_id, app).map(|_| {
|
||||
let path = &path_match.path;
|
||||
let file_name = Path::new(path)
|
||||
let path = path_match.path.clone();
|
||||
let path_string = &path_match.path_string;
|
||||
let file_name = Path::new(&path_string)
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let path_positions = path_match.positions.clone();
|
||||
let file_name_start = path.chars().count() - file_name.chars().count();
|
||||
let file_name_start = path_string.chars().count() - file_name.chars().count();
|
||||
let mut file_name_positions = Vec::new();
|
||||
file_name_positions.extend(path_positions.iter().filter_map(|pos| {
|
||||
if pos >= &file_name_start {
|
||||
@ -191,7 +191,7 @@ impl FileFinder {
|
||||
)
|
||||
.with_child(
|
||||
Label::new(
|
||||
path.into(),
|
||||
path_string.into(),
|
||||
settings.ui_font_family,
|
||||
settings.ui_font_size,
|
||||
)
|
||||
@ -217,7 +217,7 @@ impl FileFinder {
|
||||
|
||||
EventHandler::new(container.boxed())
|
||||
.on_mouse_down(move |ctx| {
|
||||
ctx.dispatch_action("file_finder:select", (tree_id, entry_id));
|
||||
ctx.dispatch_action("file_finder:select", (tree_id, path.clone()));
|
||||
true
|
||||
})
|
||||
.named("match")
|
||||
@ -245,8 +245,8 @@ impl FileFinder {
|
||||
ctx: &mut ViewContext<WorkspaceView>,
|
||||
) {
|
||||
match event {
|
||||
Event::Selected(tree_id, entry_id) => {
|
||||
workspace_view.open_entry((*tree_id, *entry_id), ctx);
|
||||
Event::Selected(tree_id, path) => {
|
||||
workspace_view.open_entry((*tree_id, path.clone()), ctx);
|
||||
workspace_view.dismiss_modal(ctx);
|
||||
}
|
||||
Event::Dismissed => {
|
||||
@ -329,13 +329,12 @@ impl FileFinder {
|
||||
|
||||
fn confirm(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
|
||||
if let Some(m) = self.matches.get(self.selected) {
|
||||
ctx.emit(Event::Selected(m.tree_id, m.entry_id));
|
||||
ctx.emit(Event::Selected(m.tree_id, m.path.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn select(&mut self, entry: &(usize, u64), ctx: &mut ViewContext<Self>) {
|
||||
let (tree_id, entry_id) = *entry;
|
||||
ctx.emit(Event::Selected(tree_id, entry_id));
|
||||
fn select(&mut self, (tree_id, path): &(usize, Arc<Path>), ctx: &mut ViewContext<Self>) {
|
||||
ctx.emit(Event::Selected(*tree_id, path.clone()));
|
||||
}
|
||||
|
||||
fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
|
||||
|
@ -7,7 +7,7 @@ use gpui::{
|
||||
keymap::Binding,
|
||||
AppContext, Border, Entity, MutableAppContext, Quad, View, ViewContext,
|
||||
};
|
||||
use std::cmp;
|
||||
use std::{cmp, path::Path, sync::Arc};
|
||||
|
||||
pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_action(
|
||||
@ -105,7 +105,11 @@ impl Pane {
|
||||
self.items.get(self.active_item).cloned()
|
||||
}
|
||||
|
||||
pub fn activate_entry(&mut self, entry_id: (usize, u64), ctx: &mut ViewContext<Self>) -> bool {
|
||||
pub fn activate_entry(
|
||||
&mut self,
|
||||
entry_id: (usize, Arc<Path>),
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
if let Some(index) = self.items.iter().position(|item| {
|
||||
item.entry_id(ctx.as_ref())
|
||||
.map_or(false, |id| id == entry_id)
|
||||
|
@ -138,10 +138,22 @@ impl Workspace {
|
||||
|
||||
pub fn open_entry(
|
||||
&mut self,
|
||||
entry: (usize, u64),
|
||||
(worktree_id, path): (usize, Arc<Path>),
|
||||
ctx: &mut ModelContext<'_, Self>,
|
||||
) -> anyhow::Result<Pin<Box<dyn Future<Output = OpenResult> + Send>>> {
|
||||
if let Some(item) = self.items.get(&entry).cloned() {
|
||||
let worktree = self
|
||||
.worktrees
|
||||
.get(&worktree_id)
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow!("worktree {} does not exist", worktree_id,))?;
|
||||
|
||||
let inode = worktree
|
||||
.read(ctx)
|
||||
.inode_for_path(&path)
|
||||
.ok_or_else(|| anyhow!("path {:?} does not exist", path))?;
|
||||
|
||||
let item_key = (worktree_id, inode);
|
||||
if let Some(item) = self.items.get(&item_key).cloned() {
|
||||
return Ok(async move {
|
||||
match item {
|
||||
OpenedItem::Loaded(handle) => {
|
||||
@ -159,25 +171,20 @@ impl Workspace {
|
||||
.boxed());
|
||||
}
|
||||
|
||||
let worktree = self
|
||||
.worktrees
|
||||
.get(&entry.0)
|
||||
.cloned()
|
||||
.ok_or(anyhow!("worktree {} does not exist", entry.0,))?;
|
||||
|
||||
let replica_id = self.replica_id;
|
||||
let file = worktree.file(entry.1, ctx.as_ref())?;
|
||||
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(entry, OpenedItem::Loading(rx));
|
||||
self.items.insert(item_key, OpenedItem::Loading(rx));
|
||||
ctx.spawn(
|
||||
buffer,
|
||||
move |me, buffer: anyhow::Result<Buffer>, ctx| match buffer {
|
||||
Ok(buffer) => {
|
||||
let handle = Box::new(ctx.add_model(|_| buffer)) as Box<dyn ItemHandle>;
|
||||
me.items.insert(entry, OpenedItem::Loaded(handle.clone()));
|
||||
me.items
|
||||
.insert(item_key, OpenedItem::Loaded(handle.clone()));
|
||||
ctx.spawn(
|
||||
async move {
|
||||
tx.update(|value| *value = Some(Ok(handle))).await;
|
||||
@ -199,7 +206,7 @@ impl Workspace {
|
||||
)
|
||||
.detach();
|
||||
|
||||
self.open_entry(entry, ctx)
|
||||
self.open_entry((worktree_id, path), ctx)
|
||||
}
|
||||
|
||||
fn on_worktree_updated(&mut self, _: ModelHandle<Worktree>, ctx: &mut ModelContext<Self>) {
|
||||
@ -213,18 +220,20 @@ impl Entity for Workspace {
|
||||
|
||||
#[cfg(test)]
|
||||
pub trait WorkspaceHandle {
|
||||
fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)>;
|
||||
fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc<Path>)>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl WorkspaceHandle for ModelHandle<Workspace> {
|
||||
fn file_entries(&self, app: &AppContext) -> Vec<(usize, u64)> {
|
||||
fn file_entries(&self, app: &AppContext) -> Vec<(usize, Arc<Path>)> {
|
||||
self.read(app)
|
||||
.worktrees()
|
||||
.iter()
|
||||
.flat_map(|tree| {
|
||||
let tree_id = tree.id();
|
||||
tree.read(app).files(0).map(move |f| (tree_id, f.inode()))
|
||||
tree.read(app)
|
||||
.files(0)
|
||||
.map(move |f| (tree_id, f.path().clone()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@ -253,14 +262,14 @@ mod tests {
|
||||
|
||||
// Get the first file entry.
|
||||
let tree = app.read(|ctx| workspace.read(ctx).worktrees.iter().next().unwrap().clone());
|
||||
let file_inode = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().inode());
|
||||
let entry = (tree.id(), file_inode);
|
||||
let path = app.read(|ctx| tree.read(ctx).files(0).next().unwrap().path().clone());
|
||||
let entry = (tree.id(), path);
|
||||
|
||||
// Open the same entry twice before it finishes loading.
|
||||
let (future_1, future_2) = workspace.update(&mut app, |w, app| {
|
||||
(
|
||||
w.open_entry(entry, app).unwrap(),
|
||||
w.open_entry(entry, app).unwrap(),
|
||||
w.open_entry(entry.clone(), app).unwrap(),
|
||||
w.open_entry(entry.clone(), app).unwrap(),
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,11 @@ use gpui::{
|
||||
ClipboardItem, Entity, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use log::error;
|
||||
use std::{collections::HashSet, path::PathBuf};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
pub fn init(app: &mut MutableAppContext) {
|
||||
app.add_action("workspace:save", WorkspaceView::save_active_item);
|
||||
@ -19,7 +23,7 @@ pub fn init(app: &mut MutableAppContext) {
|
||||
|
||||
pub trait ItemView: View {
|
||||
fn title(&self, app: &AppContext) -> String;
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
|
||||
fn clone_on_split(&self, _: &mut ViewContext<Self>) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
@ -42,7 +46,7 @@ pub trait ItemView: View {
|
||||
|
||||
pub trait ItemViewHandle: Send + Sync {
|
||||
fn title(&self, app: &AppContext) -> String;
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)>;
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)>;
|
||||
fn boxed_clone(&self) -> Box<dyn ItemViewHandle>;
|
||||
fn clone_on_split(&self, app: &mut MutableAppContext) -> Option<Box<dyn ItemViewHandle>>;
|
||||
fn set_parent_pane(&self, pane: &ViewHandle<Pane>, app: &mut MutableAppContext);
|
||||
@ -57,7 +61,7 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
|
||||
self.read(app).title(app)
|
||||
}
|
||||
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, u64)> {
|
||||
fn entry_id(&self, app: &AppContext) -> Option<(usize, Arc<Path>)> {
|
||||
self.read(app).entry_id(app)
|
||||
}
|
||||
|
||||
@ -124,7 +128,7 @@ pub struct WorkspaceView {
|
||||
center: PaneGroup,
|
||||
panes: Vec<ViewHandle<Pane>>,
|
||||
active_pane: ViewHandle<Pane>,
|
||||
loading_entries: HashSet<(usize, u64)>,
|
||||
loading_entries: HashSet<(usize, Arc<Path>)>,
|
||||
}
|
||||
|
||||
impl WorkspaceView {
|
||||
@ -189,24 +193,23 @@ impl WorkspaceView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_entry(&mut self, entry: (usize, u64), ctx: &mut ViewContext<Self>) {
|
||||
pub fn open_entry(&mut self, entry: (usize, Arc<Path>), ctx: &mut ViewContext<Self>) {
|
||||
if self.loading_entries.contains(&entry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self
|
||||
.active_pane()
|
||||
.update(ctx, |pane, ctx| pane.activate_entry(entry, ctx))
|
||||
.update(ctx, |pane, ctx| pane.activate_entry(entry.clone(), ctx))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self.loading_entries.insert(entry);
|
||||
self.loading_entries.insert(entry.clone());
|
||||
|
||||
match self
|
||||
.workspace
|
||||
.update(ctx, |workspace, ctx| workspace.open_entry(entry, ctx))
|
||||
{
|
||||
match self.workspace.update(ctx, |workspace, ctx| {
|
||||
workspace.open_entry(entry.clone(), ctx)
|
||||
}) {
|
||||
Err(error) => error!("{}", error),
|
||||
Ok(item) => {
|
||||
let settings = self.settings.clone();
|
||||
@ -396,32 +399,35 @@ mod tests {
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0];
|
||||
let file2 = entries[1];
|
||||
let file3 = entries[2];
|
||||
let file1 = entries[0].clone();
|
||||
let file2 = entries[1].clone();
|
||||
let file3 = entries[2].clone();
|
||||
|
||||
let (_, workspace_view) =
|
||||
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
||||
let pane = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
|
||||
|
||||
// Open the first entry
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
|
||||
pane.condition(&app, |pane, _| pane.items().len() == 1)
|
||||
.await;
|
||||
|
||||
// Open the second entry
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file2, ctx));
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file2.clone(), ctx));
|
||||
pane.condition(&app, |pane, _| pane.items().len() == 2)
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
let pane = pane.read(ctx);
|
||||
assert_eq!(pane.active_item().unwrap().entry_id(ctx), Some(file2));
|
||||
assert_eq!(
|
||||
pane.active_item().unwrap().entry_id(ctx),
|
||||
Some(file2.clone())
|
||||
);
|
||||
});
|
||||
|
||||
// Open the first entry again
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
|
||||
pane.condition(&app, move |pane, ctx| {
|
||||
pane.active_item().unwrap().entry_id(ctx) == Some(file1)
|
||||
pane.active_item().unwrap().entry_id(ctx) == Some(file1.clone())
|
||||
})
|
||||
.await;
|
||||
app.read(|ctx| {
|
||||
@ -430,8 +436,8 @@ mod tests {
|
||||
|
||||
// Open the third entry twice concurrently
|
||||
workspace_view.update(&mut app, |w, ctx| {
|
||||
w.open_entry(file3, ctx);
|
||||
w.open_entry(file3, ctx);
|
||||
w.open_entry(file3.clone(), ctx);
|
||||
w.open_entry(file3.clone(), ctx);
|
||||
});
|
||||
pane.condition(&app, |pane, _| pane.items().len() == 3)
|
||||
.await;
|
||||
@ -456,18 +462,21 @@ mod tests {
|
||||
app.read(|ctx| workspace.read(ctx).worktree_scans_complete(ctx))
|
||||
.await;
|
||||
let entries = app.read(|ctx| workspace.file_entries(ctx));
|
||||
let file1 = entries[0];
|
||||
let file1 = entries[0].clone();
|
||||
|
||||
let (window_id, workspace_view) =
|
||||
app.add_window(|ctx| WorkspaceView::new(workspace.clone(), settings, ctx));
|
||||
let pane_1 = app.read(|ctx| workspace_view.read(ctx).active_pane().clone());
|
||||
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1, ctx));
|
||||
pane_1
|
||||
.condition(&app, move |pane, ctx| {
|
||||
pane.active_item().and_then(|i| i.entry_id(ctx)) == Some(file1)
|
||||
})
|
||||
.await;
|
||||
workspace_view.update(&mut app, |w, ctx| w.open_entry(file1.clone(), ctx));
|
||||
{
|
||||
let file1 = file1.clone();
|
||||
pane_1
|
||||
.condition(&app, move |pane, ctx| {
|
||||
pane.active_item().and_then(|i| i.entry_id(ctx)) == Some(file1.clone())
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
app.dispatch_action(window_id, vec![pane_1.id()], "pane:split_right", ());
|
||||
app.update(|ctx| {
|
||||
@ -475,7 +484,7 @@ mod tests {
|
||||
assert_ne!(pane_1, pane_2);
|
||||
|
||||
let pane2_item = pane_2.read(ctx).active_item().unwrap();
|
||||
assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1));
|
||||
assert_eq!(pane2_item.entry_id(ctx.as_ref()), Some(file1.clone()));
|
||||
|
||||
ctx.dispatch_action(window_id, vec![pane_2.id()], "pane:close_active_item", ());
|
||||
let workspace_view = workspace_view.read(ctx);
|
||||
|
@ -54,7 +54,7 @@ pub struct Worktree {
|
||||
#[derive(Clone)]
|
||||
pub struct FileHandle {
|
||||
worktree: ModelHandle<Worktree>,
|
||||
inode: u64,
|
||||
path: Arc<Path>,
|
||||
}
|
||||
|
||||
impl Worktree {
|
||||
@ -152,25 +152,14 @@ impl Worktree {
|
||||
path.starts_with(&self.snapshot.path)
|
||||
}
|
||||
|
||||
pub fn has_inode(&self, inode: u64) -> bool {
|
||||
todo!()
|
||||
// self.snapshot.entries.get(&inode).is_some()
|
||||
}
|
||||
|
||||
pub fn abs_path_for_inode(&self, ino: u64) -> Result<PathBuf> {
|
||||
let mut result = self.snapshot.path.to_path_buf();
|
||||
result.push(self.path_for_inode(ino, false)?);
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn load_history(
|
||||
&self,
|
||||
ino: u64,
|
||||
relative_path: &Path,
|
||||
ctx: &AppContext,
|
||||
) -> impl Future<Output = Result<History>> {
|
||||
let path = self.abs_path_for_inode(ino);
|
||||
let path = self.snapshot.path.join(relative_path);
|
||||
ctx.background_executor().spawn(async move {
|
||||
let mut file = std::fs::File::open(&path?)?;
|
||||
let mut file = std::fs::File::open(&path)?;
|
||||
let mut base_text = String::new();
|
||||
file.read_to_string(&mut base_text)?;
|
||||
Ok(History::new(Arc::from(base_text)))
|
||||
@ -179,14 +168,14 @@ impl Worktree {
|
||||
|
||||
pub fn save<'a>(
|
||||
&self,
|
||||
ino: u64,
|
||||
relative_path: &Path,
|
||||
content: BufferSnapshot,
|
||||
ctx: &AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
let path = self.abs_path_for_inode(ino);
|
||||
let path = self.snapshot.path.join(relative_path);
|
||||
ctx.background_executor().spawn(async move {
|
||||
let buffer_size = content.text_summary().bytes.min(10 * 1024);
|
||||
let file = std::fs::File::create(&path?)?;
|
||||
let file = std::fs::File::create(&path)?;
|
||||
let mut writer = std::io::BufWriter::with_capacity(buffer_size, file);
|
||||
for chunk in content.fragments() {
|
||||
writer.write(chunk.as_bytes())?;
|
||||
@ -258,7 +247,7 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
|
||||
pub fn inode_for_path(&self, path: impl AsRef<Path>) -> Option<u64> {
|
||||
self.entry_for_path(path.as_ref()).map(|e| e.inode())
|
||||
}
|
||||
|
||||
@ -288,10 +277,6 @@ impl Snapshot {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_for_inode(&self, mut inode: u64, include_root: bool) -> Result<PathBuf> {
|
||||
todo!("this method should go away")
|
||||
}
|
||||
|
||||
fn insert_entry(&mut self, entry: Entry) {
|
||||
if !entry.is_dir() && entry.path().file_name() == Some(&GITIGNORE) {
|
||||
self.insert_ignore_file(entry.path());
|
||||
@ -362,24 +347,21 @@ impl fmt::Debug for Snapshot {
|
||||
}
|
||||
|
||||
impl FileHandle {
|
||||
pub fn path(&self, ctx: &AppContext) -> PathBuf {
|
||||
self.worktree
|
||||
.read(ctx)
|
||||
.path_for_inode(self.inode, false)
|
||||
.unwrap()
|
||||
pub fn path(&self) -> &Arc<Path> {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn load_history(&self, ctx: &AppContext) -> impl Future<Output = Result<History>> {
|
||||
self.worktree.read(ctx).load_history(self.inode, ctx)
|
||||
self.worktree.read(ctx).load_history(&self.path, ctx)
|
||||
}
|
||||
|
||||
pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task<Result<()>> {
|
||||
let worktree = self.worktree.read(ctx);
|
||||
worktree.save(self.inode, content, ctx)
|
||||
worktree.save(&self.path, content, ctx)
|
||||
}
|
||||
|
||||
pub fn entry_id(&self) -> (usize, u64) {
|
||||
(self.worktree.id(), self.inode)
|
||||
pub fn entry_id(&self) -> (usize, Arc<Path>) {
|
||||
(self.worktree.id(), self.path.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,13 +384,20 @@ pub enum Entry {
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn path(&self) -> &Arc<Path> {
|
||||
pub fn path(&self) -> &Arc<Path> {
|
||||
match self {
|
||||
Entry::Dir { path, .. } => path,
|
||||
Entry::File { path, .. } => path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inode(&self) -> u64 {
|
||||
match self {
|
||||
Entry::Dir { inode, .. } => *inode,
|
||||
Entry::File { inode, .. } => *inode,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_ignored(&self) -> Option<bool> {
|
||||
match self {
|
||||
Entry::Dir { is_ignored, .. } => *is_ignored,
|
||||
@ -423,13 +412,6 @@ impl Entry {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inode(&self) -> u64 {
|
||||
match self {
|
||||
Entry::Dir { inode, .. } => *inode,
|
||||
Entry::File { inode, .. } => *inode,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dir(&self) -> bool {
|
||||
matches!(self, Entry::Dir { .. })
|
||||
}
|
||||
@ -683,7 +665,7 @@ impl BackgroundScanner {
|
||||
});
|
||||
} else {
|
||||
self.snapshot.lock().insert_entry(Entry::File {
|
||||
path_entry: PathEntry::new(inode, &relative_path),
|
||||
path_entry: PathEntry::new(inode, relative_path.clone()),
|
||||
path: relative_path,
|
||||
inode,
|
||||
is_symlink,
|
||||
@ -729,7 +711,7 @@ impl BackgroundScanner {
|
||||
});
|
||||
} else {
|
||||
new_entries.push(Entry::File {
|
||||
path_entry: PathEntry::new(child_inode, &child_relative_path),
|
||||
path_entry: PathEntry::new(child_inode, child_relative_path.clone()),
|
||||
path: child_relative_path,
|
||||
inode: child_inode,
|
||||
is_symlink: child_is_symlink,
|
||||
@ -956,11 +938,12 @@ impl BackgroundScanner {
|
||||
.is_symlink();
|
||||
let relative_path_with_root = root_path
|
||||
.parent()
|
||||
.map_or(path, |parent| path.strip_prefix(parent).unwrap());
|
||||
.map_or(path, |parent| path.strip_prefix(parent).unwrap())
|
||||
.into();
|
||||
|
||||
let entry = if metadata.file_type().is_dir() {
|
||||
Entry::Dir {
|
||||
path: Arc::from(relative_path_with_root),
|
||||
path: relative_path_with_root,
|
||||
inode,
|
||||
is_symlink,
|
||||
pending: true,
|
||||
@ -968,8 +951,8 @@ impl BackgroundScanner {
|
||||
}
|
||||
} else {
|
||||
Entry::File {
|
||||
path_entry: PathEntry::new(inode, relative_path_with_root),
|
||||
path: Arc::from(relative_path_with_root),
|
||||
path_entry: PathEntry::new(inode, relative_path_with_root.clone()),
|
||||
path: relative_path_with_root,
|
||||
inode,
|
||||
is_symlink,
|
||||
is_ignored: None,
|
||||
@ -987,19 +970,18 @@ struct ScanJob {
|
||||
}
|
||||
|
||||
pub trait WorktreeHandle {
|
||||
fn file(&self, entry_id: u64, app: &AppContext) -> Result<FileHandle>;
|
||||
fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle>;
|
||||
}
|
||||
|
||||
impl WorktreeHandle for ModelHandle<Worktree> {
|
||||
fn file(&self, inode: u64, app: &AppContext) -> Result<FileHandle> {
|
||||
if self.read(app).has_inode(inode) {
|
||||
Ok(FileHandle {
|
||||
fn file(&self, path: impl AsRef<Path>, app: &AppContext) -> Result<FileHandle> {
|
||||
self.read(app)
|
||||
.entry_for_path(&path)
|
||||
.map(|entry| FileHandle {
|
||||
worktree: self.clone(),
|
||||
inode,
|
||||
path: entry.path().clone(),
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("entry does not exist in tree"))
|
||||
}
|
||||
.ok_or_else(|| anyhow!("path does not exist in tree"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1125,14 +1107,13 @@ mod tests {
|
||||
ctx.thread_pool().clone(),
|
||||
)
|
||||
.iter()
|
||||
.map(|result| tree.path_for_inode(result.entry_id, true))
|
||||
.collect::<Result<Vec<PathBuf>, _>>()
|
||||
.unwrap();
|
||||
.map(|result| result.path.clone())
|
||||
.collect::<Vec<Arc<Path>>>();
|
||||
assert_eq!(
|
||||
results,
|
||||
vec![
|
||||
PathBuf::from("root_link/banana/carrot/date"),
|
||||
PathBuf::from("root_link/banana/carrot/endive"),
|
||||
PathBuf::from("root_link/banana/carrot/date").into(),
|
||||
PathBuf::from("root_link/banana/carrot/endive").into(),
|
||||
]
|
||||
);
|
||||
})
|
||||
@ -1152,25 +1133,15 @@ mod tests {
|
||||
|
||||
let buffer = Buffer::new(1, "a line of text.\n".repeat(10 * 1024));
|
||||
|
||||
let file_inode = app.read(|ctx| {
|
||||
let tree = tree.read(ctx);
|
||||
let inode = tree.files(0).next().unwrap().inode();
|
||||
assert_eq!(
|
||||
tree.path_for_inode(inode, false)
|
||||
.unwrap()
|
||||
.file_name()
|
||||
.unwrap(),
|
||||
"file1"
|
||||
);
|
||||
inode
|
||||
});
|
||||
|
||||
tree.update(&mut app, |tree, ctx| {
|
||||
smol::block_on(tree.save(file_inode, buffer.snapshot(), ctx.as_ref())).unwrap()
|
||||
let path = tree.update(&mut app, |tree, ctx| {
|
||||
let path = tree.files(0).next().unwrap().path().clone();
|
||||
assert_eq!(path.file_name().unwrap(), "file1");
|
||||
smol::block_on(tree.save(&path, buffer.snapshot(), ctx.as_ref())).unwrap();
|
||||
path
|
||||
});
|
||||
|
||||
let loaded_history = app
|
||||
.read(|ctx| tree.read(ctx).load_history(file_inode, ctx))
|
||||
.read(|ctx| tree.read(ctx).load_history(&path, ctx))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(loaded_history.base_text.as_ref(), buffer.text());
|
||||
@ -1196,15 +1167,16 @@ mod tests {
|
||||
app.read(|ctx| assert_eq!(tree.read(ctx).file_count(), 2));
|
||||
|
||||
let file2 = app.read(|ctx| {
|
||||
let inode = tree.read(ctx).inode_for_path("b/c/file2").unwrap();
|
||||
let file2 = tree.file(inode, ctx).unwrap();
|
||||
assert_eq!(file2.path(ctx), Path::new("b/c/file2"));
|
||||
let file2 = tree.file("b/c/file2", ctx).unwrap();
|
||||
assert_eq!(file2.path().as_ref(), Path::new("b/c/file2"));
|
||||
file2
|
||||
});
|
||||
|
||||
std::fs::rename(dir.path().join("b/c"), dir.path().join("d")).unwrap();
|
||||
tree.condition(&app, move |_, ctx| file2.path(ctx) == Path::new("d/file2"))
|
||||
.await;
|
||||
tree.condition(&app, move |_, _| {
|
||||
file2.path().as_ref() == Path::new("d/file2")
|
||||
})
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1513,7 +1485,7 @@ mod tests {
|
||||
));
|
||||
if let Entry::File { path_entry, .. } = entry {
|
||||
assert_eq!(
|
||||
String::from_iter(path_entry.path.iter()),
|
||||
String::from_iter(path_entry.path_chars.iter()),
|
||||
entry.path().to_str().unwrap()
|
||||
);
|
||||
}
|
||||
|
@ -14,20 +14,22 @@ const MIN_DISTANCE_PENALTY: f64 = 0.2;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PathEntry {
|
||||
pub ino: u64,
|
||||
pub path_chars: CharBag,
|
||||
pub path: Arc<[char]>,
|
||||
pub char_bag: CharBag,
|
||||
pub path_chars: Arc<[char]>,
|
||||
pub path: Arc<Path>,
|
||||
pub lowercase_path: Arc<[char]>,
|
||||
}
|
||||
|
||||
impl PathEntry {
|
||||
pub fn new(ino: u64, path: &Path) -> Self {
|
||||
let path = path.to_string_lossy();
|
||||
let lowercase_path = path.to_lowercase().chars().collect::<Vec<_>>().into();
|
||||
let path: Arc<[char]> = path.chars().collect::<Vec<_>>().into();
|
||||
let path_chars = CharBag::from(path.as_ref());
|
||||
pub fn new(ino: u64, path: Arc<Path>) -> Self {
|
||||
let path_str = path.to_string_lossy();
|
||||
let lowercase_path = path_str.to_lowercase().chars().collect::<Vec<_>>().into();
|
||||
let path_chars: Arc<[char]> = path_str.chars().collect::<Vec<_>>().into();
|
||||
let char_bag = CharBag::from(path_chars.as_ref());
|
||||
|
||||
Self {
|
||||
ino,
|
||||
char_bag,
|
||||
path_chars,
|
||||
path,
|
||||
lowercase_path,
|
||||
@ -39,9 +41,9 @@ impl PathEntry {
|
||||
pub struct PathMatch {
|
||||
pub score: f64,
|
||||
pub positions: Vec<usize>,
|
||||
pub path: String,
|
||||
pub path_string: String,
|
||||
pub tree_id: usize,
|
||||
pub entry_id: u64,
|
||||
pub path: Arc<Path>,
|
||||
}
|
||||
|
||||
impl PartialEq for PathMatch {
|
||||
@ -199,7 +201,7 @@ fn match_single_tree_paths<'a>(
|
||||
best_position_matrix: &mut Vec<usize>,
|
||||
) {
|
||||
for path_entry in path_entries {
|
||||
if !path_entry.path_chars.is_superset(query_chars) {
|
||||
if !path_entry.char_bag.is_superset(query_chars) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -212,7 +214,7 @@ fn match_single_tree_paths<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
let matrix_len = query.len() * (path_entry.path.len() - skipped_prefix_len);
|
||||
let matrix_len = query.len() * (path_entry.path_chars.len() - skipped_prefix_len);
|
||||
score_matrix.clear();
|
||||
score_matrix.resize(matrix_len, None);
|
||||
best_position_matrix.clear();
|
||||
@ -221,7 +223,7 @@ fn match_single_tree_paths<'a>(
|
||||
let score = score_match(
|
||||
&query[..],
|
||||
&lowercase_query[..],
|
||||
&path_entry.path,
|
||||
&path_entry.path_chars,
|
||||
&path_entry.lowercase_path,
|
||||
skipped_prefix_len,
|
||||
smart_case,
|
||||
@ -235,8 +237,12 @@ fn match_single_tree_paths<'a>(
|
||||
if score > 0.0 {
|
||||
results.push(Reverse(PathMatch {
|
||||
tree_id: snapshot.id,
|
||||
entry_id: path_entry.ino,
|
||||
path: path_entry.path.iter().skip(skipped_prefix_len).collect(),
|
||||
path_string: path_entry
|
||||
.path_chars
|
||||
.iter()
|
||||
.skip(skipped_prefix_len)
|
||||
.collect(),
|
||||
path: path_entry.path.clone(),
|
||||
score,
|
||||
positions: match_positions.clone(),
|
||||
}));
|
||||
@ -496,12 +502,13 @@ mod tests {
|
||||
for (i, path) in paths.iter().enumerate() {
|
||||
let lowercase_path: Arc<[char]> =
|
||||
path.to_lowercase().chars().collect::<Vec<_>>().into();
|
||||
let path_chars = CharBag::from(lowercase_path.as_ref());
|
||||
let path = path.chars().collect();
|
||||
let char_bag = CharBag::from(lowercase_path.as_ref());
|
||||
let path_chars = path.chars().collect();
|
||||
path_entries.push(PathEntry {
|
||||
ino: i as u64,
|
||||
char_bag,
|
||||
path_chars,
|
||||
path,
|
||||
path: Arc::from(PathBuf::from(path)),
|
||||
lowercase_path,
|
||||
});
|
||||
}
|
||||
@ -540,7 +547,11 @@ mod tests {
|
||||
.rev()
|
||||
.map(|result| {
|
||||
(
|
||||
paths[result.0.entry_id as usize].clone(),
|
||||
paths
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|p| result.0.path.as_ref() == Path::new(p))
|
||||
.unwrap(),
|
||||
result.0.positions,
|
||||
)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user