mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
fix path handling on windows (#762)
* this reduces memory overhead where nothing is folded up * makes folding work with windows path seperators
This commit is contained in:
parent
0e7ac4a14c
commit
869e4b7287
6 changed files with 167 additions and 154 deletions
|
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- wrong file with same name shown in file tree ([#748](https://github.com/extrawurst/gitui/issues/748))
|
- wrong file with same name shown in file tree ([#748](https://github.com/extrawurst/gitui/issues/748))
|
||||||
|
- filetree collapsing broken on windows ([#761](https://github.com/extrawurst/gitui/issues/761))
|
||||||
|
|
||||||
### Internal
|
### Internal
|
||||||
- use git_repository_message [[@kosayoda](https://github.com/kosayoda)] ([#751](https://github.com/extrawurst/gitui/issues/751))
|
- use git_repository_message [[@kosayoda](https://github.com/kosayoda)] ([#751](https://github.com/extrawurst/gitui/issues/751))
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ pub enum Error {
|
||||||
#[error("InvalidPath: `{0}`")]
|
#[error("InvalidPath: `{0}`")]
|
||||||
InvalidPath(PathBuf),
|
InvalidPath(PathBuf),
|
||||||
|
|
||||||
#[error("InvalidFilePath: `{0}`")]
|
|
||||||
InvalidFilePath(String),
|
|
||||||
|
|
||||||
#[error("TryFromInt error:{0}")]
|
#[error("TryFromInt error:{0}")]
|
||||||
IntConversion(#[from] TryFromIntError),
|
IntConversion(#[from] TryFromIntError),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
error::Result, filetreeitems::FileTreeItems,
|
error::Result, filetreeitems::FileTreeItems,
|
||||||
tree_iter::TreeIterator, TreeItemInfo,
|
tree_iter::TreeIterator, TreeItemInfo,
|
||||||
};
|
};
|
||||||
use std::{collections::BTreeSet, usize};
|
use std::{collections::BTreeSet, path::Path, usize};
|
||||||
|
|
||||||
///
|
///
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
|
@ -35,7 +35,7 @@ pub struct FileTree {
|
||||||
impl FileTree {
|
impl FileTree {
|
||||||
///
|
///
|
||||||
pub fn new(
|
pub fn new(
|
||||||
list: &[&str],
|
list: &[&Path],
|
||||||
collapsed: &BTreeSet<&String>,
|
collapsed: &BTreeSet<&String>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let mut new_self = Self {
|
let mut new_self = Self {
|
||||||
|
|
@ -318,12 +318,12 @@ impl FileTree {
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{FileTree, MoveSelection};
|
use crate::{FileTree, MoveSelection};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::collections::BTreeSet;
|
use std::{collections::BTreeSet, path::Path};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection() {
|
fn test_selection() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b", //
|
Path::new("a/b"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut tree =
|
let mut tree =
|
||||||
|
|
@ -341,8 +341,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_skips_collapsed() {
|
fn test_selection_skips_collapsed() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -364,8 +364,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_left_collapse() {
|
fn test_selection_left_collapse() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -390,8 +390,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_left_parent() {
|
fn test_selection_left_parent() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -417,8 +417,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_right_expand() {
|
fn test_selection_right_expand() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -449,8 +449,8 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_selection_top() {
|
fn test_selection_top() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -470,9 +470,9 @@ mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_visible_selection() {
|
fn test_visible_selection() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/b/c2", //
|
Path::new("a/b/c2"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
use crate::{error::Result, treeitems_iter::TreeItemsIterator};
|
use crate::{error::Result, treeitems_iter::TreeItemsIterator};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashMap},
|
collections::{BTreeSet, HashMap},
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
usize,
|
usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ pub struct FileTreeItems {
|
||||||
impl FileTreeItems {
|
impl FileTreeItems {
|
||||||
///
|
///
|
||||||
pub fn new(
|
pub fn new(
|
||||||
list: &[&str],
|
list: &[&Path],
|
||||||
collapsed: &BTreeSet<&String>,
|
collapsed: &BTreeSet<&String>,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let (mut items, paths) = Self::create_items(list, collapsed)?;
|
let (mut items, paths) = Self::create_items(list, collapsed)?;
|
||||||
|
|
@ -34,7 +34,7 @@ impl FileTreeItems {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_items<'a>(
|
fn create_items<'a>(
|
||||||
list: &'a [&str],
|
list: &'a [&Path],
|
||||||
collapsed: &BTreeSet<&String>,
|
collapsed: &BTreeSet<&String>,
|
||||||
) -> Result<(Vec<FileTreeItem>, HashMap<&'a Path, usize>)> {
|
) -> Result<(Vec<FileTreeItem>, HashMap<&'a Path, usize>)> {
|
||||||
// scopetime::scope_time!("create_items");
|
// scopetime::scope_time!("create_items");
|
||||||
|
|
@ -45,9 +45,8 @@ impl FileTreeItems {
|
||||||
|
|
||||||
for e in list {
|
for e in list {
|
||||||
{
|
{
|
||||||
let item_path = Path::new(e);
|
|
||||||
Self::push_dirs(
|
Self::push_dirs(
|
||||||
item_path,
|
e,
|
||||||
&mut items,
|
&mut items,
|
||||||
&mut paths_added,
|
&mut paths_added,
|
||||||
collapsed,
|
collapsed,
|
||||||
|
|
@ -103,13 +102,10 @@ impl FileTreeItems {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: make non alloc
|
||||||
let path_string = Self::path_to_string(c)?;
|
let path_string = Self::path_to_string(c)?;
|
||||||
let is_collapsed = collapsed.contains(&path_string);
|
let is_collapsed = collapsed.contains(&path_string);
|
||||||
nodes.push(FileTreeItem::new_path(
|
nodes.push(FileTreeItem::new_path(c, is_collapsed)?);
|
||||||
c,
|
|
||||||
path_string,
|
|
||||||
is_collapsed,
|
|
||||||
)?);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,6 +117,7 @@ impl FileTreeItems {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: return ref
|
||||||
fn path_to_string(p: &Path) -> Result<String> {
|
fn path_to_string(p: &Path) -> Result<String> {
|
||||||
Ok(p.to_str()
|
Ok(p.to_str()
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|
|
@ -134,9 +131,8 @@ impl FileTreeItems {
|
||||||
if self.tree_items[index].kind().is_path() {
|
if self.tree_items[index].kind().is_path() {
|
||||||
self.tree_items[index].collapse_path();
|
self.tree_items[index].collapse_path();
|
||||||
|
|
||||||
let path = format!(
|
let path = PathBuf::from(
|
||||||
"{}/",
|
self.tree_items[index].info().full_path_str(),
|
||||||
self.tree_items[index].info().full_path()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
for i in index + 1..self.tree_items.len() {
|
for i in index + 1..self.tree_items.len() {
|
||||||
|
|
@ -146,7 +142,8 @@ impl FileTreeItems {
|
||||||
item.collapse_path();
|
item.collapse_path();
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_path = &item.info().full_path();
|
let item_path =
|
||||||
|
Path::new(item.info().full_path_str());
|
||||||
|
|
||||||
if item_path.starts_with(&path) {
|
if item_path.starts_with(&path) {
|
||||||
item.hide();
|
item.hide();
|
||||||
|
|
@ -161,18 +158,15 @@ impl FileTreeItems {
|
||||||
if self.tree_items[index].kind().is_path() {
|
if self.tree_items[index].kind().is_path() {
|
||||||
self.tree_items[index].expand_path();
|
self.tree_items[index].expand_path();
|
||||||
|
|
||||||
let full_path = format!(
|
let full_path = PathBuf::from(
|
||||||
"{}/",
|
self.tree_items[index].info().full_path_str(),
|
||||||
self.tree_items[index].info().full_path()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if recursive {
|
if recursive {
|
||||||
for i in index + 1..self.tree_items.len() {
|
for i in index + 1..self.tree_items.len() {
|
||||||
let item = &mut self.tree_items[i];
|
let item = &mut self.tree_items[i];
|
||||||
|
|
||||||
if !item
|
if !Path::new(item.info().full_path_str())
|
||||||
.info()
|
|
||||||
.full_path()
|
|
||||||
.starts_with(&full_path)
|
.starts_with(&full_path)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|
@ -187,7 +181,7 @@ impl FileTreeItems {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_visibility(
|
self.update_visibility(
|
||||||
Some(full_path.as_str()),
|
&Some(full_path),
|
||||||
index + 1,
|
index + 1,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
@ -196,16 +190,18 @@ impl FileTreeItems {
|
||||||
|
|
||||||
fn update_visibility(
|
fn update_visibility(
|
||||||
&mut self,
|
&mut self,
|
||||||
prefix: Option<&str>,
|
prefix: &Option<PathBuf>,
|
||||||
start_idx: usize,
|
start_idx: usize,
|
||||||
set_defaults: bool,
|
set_defaults: bool,
|
||||||
) {
|
) {
|
||||||
// if we are in any subpath that is collapsed we keep skipping over it
|
// if we are in any subpath that is collapsed we keep skipping over it
|
||||||
let mut inner_collapsed: Option<String> = None;
|
let mut inner_collapsed: Option<PathBuf> = None;
|
||||||
|
|
||||||
for i in start_idx..self.tree_items.len() {
|
for i in start_idx..self.tree_items.len() {
|
||||||
if let Some(ref collapsed_path) = inner_collapsed {
|
if let Some(ref collapsed_path) = inner_collapsed {
|
||||||
let p = self.tree_items[i].info().full_path();
|
let p = Path::new(
|
||||||
|
self.tree_items[i].info().full_path_str(),
|
||||||
|
);
|
||||||
if p.starts_with(collapsed_path) {
|
if p.starts_with(collapsed_path) {
|
||||||
if set_defaults {
|
if set_defaults {
|
||||||
self.tree_items[i]
|
self.tree_items[i]
|
||||||
|
|
@ -219,15 +215,17 @@ impl FileTreeItems {
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_kind = self.tree_items[i].kind().clone();
|
let item_kind = self.tree_items[i].kind().clone();
|
||||||
let item_path = self.tree_items[i].info().full_path();
|
let item_path =
|
||||||
|
Path::new(self.tree_items[i].info().full_path_str());
|
||||||
|
|
||||||
if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed)
|
if matches!(item_kind, FileTreeItemKind::Path(PathCollapsed(collapsed)) if collapsed)
|
||||||
{
|
{
|
||||||
// we encountered an inner path that is still collapsed
|
// we encountered an inner path that is still collapsed
|
||||||
inner_collapsed = Some(format!("{}/", &item_path));
|
inner_collapsed = Some(item_path.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefix
|
if prefix
|
||||||
|
.as_ref()
|
||||||
.map_or(true, |prefix| item_path.starts_with(prefix))
|
.map_or(true, |prefix| item_path.starts_with(prefix))
|
||||||
{
|
{
|
||||||
self.tree_items[i].info_mut().set_visible(true);
|
self.tree_items[i].info_mut().set_visible(true);
|
||||||
|
|
@ -251,8 +249,8 @@ impl FileTreeItems {
|
||||||
while i < items.len() {
|
while i < items.len() {
|
||||||
let item = &items[i];
|
let item = &items[i];
|
||||||
if item.kind().is_path() {
|
if item.kind().is_path() {
|
||||||
let children =
|
let children = paths
|
||||||
paths.get(&Path::new(item.info().full_path()));
|
.get(&Path::new(item.info().full_path_str()));
|
||||||
|
|
||||||
if let Some(children) = children {
|
if let Some(children) = children {
|
||||||
if *children == 1 {
|
if *children == 1 {
|
||||||
|
|
@ -271,7 +269,7 @@ impl FileTreeItems {
|
||||||
|
|
||||||
let prefix = item_mut
|
let prefix = item_mut
|
||||||
.info()
|
.info()
|
||||||
.full_path()
|
.full_path_str()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
Self::unindent(items, &prefix, i + 1);
|
Self::unindent(items, &prefix, i + 1);
|
||||||
|
|
@ -291,7 +289,7 @@ impl FileTreeItems {
|
||||||
start: usize,
|
start: usize,
|
||||||
) {
|
) {
|
||||||
for elem in items.iter_mut().skip(start) {
|
for elem in items.iter_mut().skip(start) {
|
||||||
if elem.info().full_path().starts_with(prefix) {
|
if elem.info().full_path_str().starts_with(prefix) {
|
||||||
elem.info_mut().unindent();
|
elem.info_mut().unindent();
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -308,7 +306,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simple() {
|
fn test_simple() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"file.txt", //
|
Path::new("file.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
|
|
@ -320,8 +318,8 @@ mod tests {
|
||||||
assert_eq!(res.tree_items[0].info().full_path(), items[0]);
|
assert_eq!(res.tree_items[0].info().full_path(), items[0]);
|
||||||
|
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"file.txt", //
|
Path::new("file.txt"), //
|
||||||
"file2.txt", //
|
Path::new("file2.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
|
|
@ -329,10 +327,7 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(res.tree_items.len(), 2);
|
assert_eq!(res.tree_items.len(), 2);
|
||||||
assert_eq!(res.tree_items.len(), res.len());
|
assert_eq!(res.tree_items.len(), res.len());
|
||||||
assert_eq!(
|
assert_eq!(res.tree_items[1].info().path(), items[1]);
|
||||||
res.tree_items[1].info().path(),
|
|
||||||
items[1].to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -392,14 +387,14 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_folder() {
|
fn test_folder() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/file.txt", //
|
Path::new("a/file.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let res = FileTreeItems::new(&items, &BTreeSet::new())
|
let res = FileTreeItems::new(&items, &BTreeSet::new())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.tree_items
|
.tree_items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| i.info().full_path().to_string())
|
.map(|i| i.info().full_path_str().to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -411,7 +406,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_indent() {
|
fn test_indent() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/file.txt", //
|
Path::new("a/b/file.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let list =
|
let list =
|
||||||
|
|
@ -421,15 +416,15 @@ mod tests {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| (i.info().indent(), i.info().path()));
|
.map(|i| (i.info().indent(), i.info().path()));
|
||||||
|
|
||||||
assert_eq!(res.next(), Some((0, "a/b")));
|
assert_eq!(res.next(), Some((0, Path::new("a/b"))));
|
||||||
assert_eq!(res.next(), Some((1, "file.txt")));
|
assert_eq!(res.next(), Some((1, Path::new("file.txt"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_indent_folder_file_name() {
|
fn test_indent_folder_file_name() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b", //
|
Path::new("a/b"), //
|
||||||
"a.txt", //
|
Path::new("a.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let list =
|
let list =
|
||||||
|
|
@ -437,7 +432,7 @@ mod tests {
|
||||||
let mut res = list
|
let mut res = list
|
||||||
.tree_items
|
.tree_items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| (i.info().indent(), i.info().path()));
|
.map(|i| (i.info().indent(), i.info().path_str()));
|
||||||
|
|
||||||
assert_eq!(res.next(), Some((0, "a")));
|
assert_eq!(res.next(), Some((0, "a")));
|
||||||
assert_eq!(res.next(), Some((1, "b")));
|
assert_eq!(res.next(), Some((1, "b")));
|
||||||
|
|
@ -447,8 +442,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_folder_dup() {
|
fn test_folder_dup() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/file.txt", //
|
Path::new("a/file.txt"), //
|
||||||
"a/file2.txt", //
|
Path::new("a/file2.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let tree =
|
let tree =
|
||||||
|
|
@ -460,7 +455,7 @@ mod tests {
|
||||||
let res = tree
|
let res = tree
|
||||||
.tree_items
|
.tree_items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| i.info().full_path().to_string())
|
.map(|i| i.info().full_path_str().to_string())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -476,8 +471,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collapse() {
|
fn test_collapse() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/file1.txt", //
|
Path::new("a/file1.txt"), //
|
||||||
"b/file2.txt", //
|
Path::new("b/file2.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut tree =
|
let mut tree =
|
||||||
|
|
@ -493,8 +488,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_iterate_collapsed() {
|
fn test_iterate_collapsed() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/file1.txt", //
|
Path::new("a/file1.txt"), //
|
||||||
"b/file2.txt", //
|
Path::new("b/file2.txt"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut tree =
|
let mut tree =
|
||||||
|
|
@ -520,8 +515,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand() {
|
fn test_expand() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -564,8 +559,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand_bug() {
|
fn test_expand_bug() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/b2/d", //
|
Path::new("a/b2/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -608,8 +603,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_collapse_too_much() {
|
fn test_collapse_too_much() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b", //
|
Path::new("a/b"), //
|
||||||
"a2/c", //
|
Path::new("a2/c"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -638,8 +633,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_expand_with_collapsed_sub_parts() {
|
fn test_expand_with_collapsed_sub_parts() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/d", //
|
Path::new("a/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -701,7 +696,7 @@ mod test_merging {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_simple() {
|
fn test_merge_simple() {
|
||||||
let list = vec!["a/b/c"];
|
let list = vec![Path::new("a/b/c")];
|
||||||
let (mut items, paths) =
|
let (mut items, paths) =
|
||||||
FileTreeItems::create_items(&list, &BTreeSet::new())
|
FileTreeItems::create_items(&list, &BTreeSet::new())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
@ -716,8 +711,8 @@ mod test_merging {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_simple2() {
|
fn test_merge_simple2() {
|
||||||
let list = vec![
|
let list = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/b/d",
|
Path::new("a/b/d"), //
|
||||||
];
|
];
|
||||||
let (mut items, paths) =
|
let (mut items, paths) =
|
||||||
FileTreeItems::create_items(&list, &BTreeSet::new())
|
FileTreeItems::create_items(&list, &BTreeSet::new())
|
||||||
|
|
@ -736,8 +731,8 @@ mod test_merging {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_indent() {
|
fn test_merge_indent() {
|
||||||
let list = vec![
|
let list = vec![
|
||||||
"a/b/c/d", //
|
Path::new("a/b/c/d"), //
|
||||||
"a/e/f",
|
Path::new("a/e/f"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0:0 a/
|
//0:0 a/
|
||||||
|
|
@ -758,7 +753,7 @@ mod test_merging {
|
||||||
assert_eq!(*paths.get(&Path::new("a/b/c")).unwrap(), 1);
|
assert_eq!(*paths.get(&Path::new("a/b/c")).unwrap(), 1);
|
||||||
assert_eq!(*paths.get(&Path::new("a/e")).unwrap(), 1);
|
assert_eq!(*paths.get(&Path::new("a/e")).unwrap(), 1);
|
||||||
|
|
||||||
FileTreeItems::fold_paths(&mut items, dbg!(&paths));
|
FileTreeItems::fold_paths(&mut items, &paths);
|
||||||
|
|
||||||
let indents: Vec<u8> =
|
let indents: Vec<u8> =
|
||||||
items.iter().map(|i| i.info().indent()).collect();
|
items.iter().map(|i| i.info().indent()).collect();
|
||||||
|
|
@ -768,8 +763,8 @@ mod test_merging {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_single_paths() {
|
fn test_merge_single_paths() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/b/d", //
|
Path::new("a/b/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/b/
|
//0 a/b/
|
||||||
|
|
@ -781,7 +776,7 @@ mod test_merging {
|
||||||
|
|
||||||
let mut it = tree
|
let mut it = tree
|
||||||
.iterate(0, 10)
|
.iterate(0, 10)
|
||||||
.map(|(_, item)| item.info().full_path());
|
.map(|(_, item)| item.info().full_path_str());
|
||||||
|
|
||||||
assert_eq!(it.next().unwrap(), "a/b");
|
assert_eq!(it.next().unwrap(), "a/b");
|
||||||
assert_eq!(it.next().unwrap(), "a/b/c");
|
assert_eq!(it.next().unwrap(), "a/b/c");
|
||||||
|
|
@ -792,8 +787,8 @@ mod test_merging {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_nothing() {
|
fn test_merge_nothing() {
|
||||||
let items = vec![
|
let items = vec![
|
||||||
"a/b/c", //
|
Path::new("a/b/c"), //
|
||||||
"a/b2/d", //
|
Path::new("a/b2/d"), //
|
||||||
];
|
];
|
||||||
|
|
||||||
//0 a/
|
//0 a/
|
||||||
|
|
@ -807,7 +802,7 @@ mod test_merging {
|
||||||
|
|
||||||
let mut it = tree
|
let mut it = tree
|
||||||
.iterate(0, 10)
|
.iterate(0, 10)
|
||||||
.map(|(_, item)| item.info().full_path());
|
.map(|(_, item)| item.info().full_path_str());
|
||||||
|
|
||||||
assert_eq!(it.next().unwrap(), "a");
|
assert_eq!(it.next().unwrap(), "a");
|
||||||
assert_eq!(it.next().unwrap(), "a/b");
|
assert_eq!(it.next().unwrap(), "a/b");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
use crate::error::{Error, Result};
|
use crate::error::Result;
|
||||||
use std::{convert::TryFrom, path::Path};
|
use std::{
|
||||||
|
convert::TryFrom,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
/// holds the information shared among all `FileTreeItem` in a `FileTree`
|
/// holds the information shared among all `FileTreeItem` in a `FileTree`
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -8,23 +11,20 @@ pub struct TreeItemInfo {
|
||||||
indent: u8,
|
indent: u8,
|
||||||
/// currently visible depending on the folder collapse states
|
/// currently visible depending on the folder collapse states
|
||||||
visible: bool,
|
visible: bool,
|
||||||
/// just the last path element
|
/// contains this paths last component and folded up paths added to it
|
||||||
path: String,
|
/// if this is `None` nothing was folding into here
|
||||||
|
folded: Option<PathBuf>,
|
||||||
/// the full path
|
/// the full path
|
||||||
full_path: String,
|
full_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TreeItemInfo {
|
impl TreeItemInfo {
|
||||||
///
|
///
|
||||||
pub const fn new(
|
pub const fn new(indent: u8, full_path: PathBuf) -> Self {
|
||||||
indent: u8,
|
|
||||||
path: String,
|
|
||||||
full_path: String,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
indent,
|
indent,
|
||||||
visible: true,
|
visible: true,
|
||||||
path,
|
folded: None,
|
||||||
full_path,
|
full_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,13 +35,36 @@ impl TreeItemInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn full_path(&self) -> &str {
|
//TODO: remove
|
||||||
&self.full_path
|
pub fn full_path_str(&self) -> &str {
|
||||||
|
self.full_path.to_str().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn path(&self) -> &str {
|
pub fn full_path(&self) -> &Path {
|
||||||
&self.path
|
self.full_path.as_path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// like `path` but as `&str`
|
||||||
|
pub fn path_str(&self) -> &str {
|
||||||
|
self.path().as_os_str().to_str().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns the last component of `full_path`
|
||||||
|
/// or the last components plus folded up children paths
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
self.folded.as_ref().map_or_else(
|
||||||
|
|| {
|
||||||
|
Path::new(
|
||||||
|
self.full_path
|
||||||
|
.components()
|
||||||
|
.last()
|
||||||
|
.and_then(|c| c.as_os_str().to_str())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|folding| folding.as_path(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -91,64 +114,38 @@ pub struct FileTreeItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileTreeItem {
|
impl FileTreeItem {
|
||||||
pub fn new_file(path: &str) -> Result<Self> {
|
pub fn new_file(path: &Path) -> Result<Self> {
|
||||||
let item_path = Path::new(&path);
|
let item_path = PathBuf::from(path);
|
||||||
|
|
||||||
let indent = u8::try_from(
|
let indent = u8::try_from(
|
||||||
item_path.ancestors().count().saturating_sub(2),
|
item_path.ancestors().count().saturating_sub(2),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let filename = item_path
|
|
||||||
.file_name()
|
|
||||||
.map_or_else(
|
|
||||||
|| Err(Error::InvalidFilePath(path.to_string())),
|
|
||||||
Ok,
|
|
||||||
)?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
info: TreeItemInfo::new(
|
info: TreeItemInfo::new(indent, item_path),
|
||||||
indent,
|
|
||||||
filename,
|
|
||||||
item_path.to_string_lossy().to_string(),
|
|
||||||
),
|
|
||||||
kind: FileTreeItemKind::File,
|
kind: FileTreeItemKind::File,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_path(
|
pub fn new_path(path: &Path, collapsed: bool) -> Result<Self> {
|
||||||
path: &Path,
|
|
||||||
path_string: String,
|
|
||||||
collapsed: bool,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let indent =
|
let indent =
|
||||||
u8::try_from(path.ancestors().count().saturating_sub(2))?;
|
u8::try_from(path.ancestors().count().saturating_sub(2))?;
|
||||||
|
|
||||||
let last_path_component =
|
|
||||||
path.components().last().map_or_else(
|
|
||||||
|| Err(Error::InvalidPath(path.to_path_buf())),
|
|
||||||
Ok,
|
|
||||||
)?;
|
|
||||||
let last_path_component = last_path_component
|
|
||||||
.as_os_str()
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
info: TreeItemInfo::new(
|
info: TreeItemInfo::new(indent, path.to_owned()),
|
||||||
indent,
|
|
||||||
last_path_component,
|
|
||||||
path_string,
|
|
||||||
),
|
|
||||||
kind: FileTreeItemKind::Path(PathCollapsed(collapsed)),
|
kind: FileTreeItemKind::Path(PathCollapsed(collapsed)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn fold(&mut self, next: Self) {
|
pub fn fold(&mut self, next: Self) {
|
||||||
self.info.path =
|
if let Some(folded) = self.info.folded.as_mut() {
|
||||||
format!("{}/{}", self.info.path, next.info.path);
|
*folded = folded.join(next.info.path());
|
||||||
|
} else {
|
||||||
|
self.info.folded =
|
||||||
|
Some(self.info.path().join(next.info.path()))
|
||||||
|
}
|
||||||
|
|
||||||
self.info.full_path = next.info.full_path;
|
self.info.full_path = next.info.full_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,6 +199,32 @@ impl PartialOrd for FileTreeItem {
|
||||||
|
|
||||||
impl Ord for FileTreeItem {
|
impl Ord for FileTreeItem {
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
self.info.path.cmp(&other.info.path)
|
self.info.path().cmp(other.info.path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_smoke() {
|
||||||
|
let mut a =
|
||||||
|
FileTreeItem::new_path(Path::new("a"), false).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(a.info.full_path_str(), "a");
|
||||||
|
assert_eq!(a.info.path_str(), "a");
|
||||||
|
|
||||||
|
let b =
|
||||||
|
FileTreeItem::new_path(Path::new("a/b"), false).unwrap();
|
||||||
|
a.fold(b);
|
||||||
|
|
||||||
|
assert_eq!(a.info.full_path_str(), "a/b");
|
||||||
|
assert_eq!(
|
||||||
|
&a.info.folded.as_ref().unwrap(),
|
||||||
|
&Path::new("a/b")
|
||||||
|
);
|
||||||
|
assert_eq!(a.info.path(), Path::new("a/b"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,8 @@ impl RevisionFilesComponent {
|
||||||
self.revision.map(|c| c == commit).unwrap_or_default();
|
self.revision.map(|c| c == commit).unwrap_or_default();
|
||||||
if !same_id {
|
if !same_id {
|
||||||
self.files = sync::tree_files(CWD, commit)?;
|
self.files = sync::tree_files(CWD, commit)?;
|
||||||
let filenames: Vec<&str> = self
|
let filenames: Vec<&Path> =
|
||||||
.files
|
self.files.iter().map(|f| f.path.as_path()).collect();
|
||||||
.iter()
|
|
||||||
.map(|f| f.path.to_str().unwrap_or_default())
|
|
||||||
.collect();
|
|
||||||
self.tree = FileTree::new(&filenames, &BTreeSet::new())?;
|
self.tree = FileTree::new(&filenames, &BTreeSet::new())?;
|
||||||
self.tree.collapse_but_root();
|
self.tree.collapse_but_root();
|
||||||
self.revision = Some(commit);
|
self.revision = Some(commit);
|
||||||
|
|
@ -108,7 +105,7 @@ impl RevisionFilesComponent {
|
||||||
theme: &SharedTheme,
|
theme: &SharedTheme,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
) -> Span<'a> {
|
) -> Span<'a> {
|
||||||
let path = item.info().path();
|
let path = item.info().path_str();
|
||||||
let indent = item.info().indent();
|
let indent = item.info().indent();
|
||||||
|
|
||||||
let indent_str = if indent == 0 {
|
let indent_str = if indent == 0 {
|
||||||
|
|
@ -136,7 +133,7 @@ impl RevisionFilesComponent {
|
||||||
self.tree.selected_file().map_or(false, |file| {
|
self.tree.selected_file().map_or(false, |file| {
|
||||||
self.queue.borrow_mut().push_back(
|
self.queue.borrow_mut().push_back(
|
||||||
InternalEvent::BlameFile(
|
InternalEvent::BlameFile(
|
||||||
file.full_path()
|
file.full_path_str()
|
||||||
.strip_prefix("./")
|
.strip_prefix("./")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
|
@ -151,7 +148,7 @@ impl RevisionFilesComponent {
|
||||||
if let Some(file) = self
|
if let Some(file) = self
|
||||||
.tree
|
.tree
|
||||||
.selected_file()
|
.selected_file()
|
||||||
.map(|file| file.full_path().to_string())
|
.map(|file| file.full_path_str().to_string())
|
||||||
{
|
{
|
||||||
let path = Path::new(&file);
|
let path = Path::new(&file);
|
||||||
if let Some(item) =
|
if let Some(item) =
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue