Implement project tree

This commit is contained in:
Jeremy Soller 2023-10-27 11:43:30 -06:00
parent 8566c6f31f
commit 05368dea2c
No known key found for this signature in database
GPG key ID: DCFCA852D3906975
3 changed files with 259 additions and 75 deletions

84
Cargo.lock generated
View file

@ -303,7 +303,7 @@ dependencies = [
"log",
"parking",
"polling 2.8.0",
"rustix 0.37.26",
"rustix 0.37.27",
"slab",
"socket2",
"waker-fn",
@ -331,7 +331,7 @@ dependencies = [
"cfg-if",
"event-listener 3.0.0",
"futures-lite",
"rustix 0.38.20",
"rustix 0.38.21",
"windows-sys 0.48.0",
]
@ -358,7 +358,7 @@ dependencies = [
"cfg-if",
"futures-core",
"futures-io",
"rustix 0.38.20",
"rustix 0.38.21",
"signal-hook-registry",
"slab",
"windows-sys 0.48.0",
@ -405,7 +405,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4d45f362125ed144544e57b0ec6de8fd6a296d41a6252fc4a20c0cf12e9ed3a"
dependencies = [
"rustix 0.38.20",
"rustix 0.38.21",
"tempfile",
"windows-sys 0.48.0",
]
@ -624,7 +624,7 @@ dependencies = [
"bitflags 2.4.1",
"log",
"polling 3.2.0",
"rustix 0.38.20",
"rustix 0.38.21",
"slab",
"thiserror",
]
@ -636,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
dependencies = [
"calloop 0.12.3",
"rustix 0.38.20",
"rustix 0.38.21",
"wayland-backend 0.3.2",
"wayland-client 0.31.1",
]
@ -839,7 +839,7 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
@ -853,7 +853,7 @@ dependencies = [
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"quote",
"syn 1.0.109",
@ -916,7 +916,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"almost",
"cosmic-config",
@ -2047,7 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iced"
version = "0.10.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2063,7 +2063,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"accesskit",
"accesskit_unix",
@ -2073,7 +2073,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.10.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"bitflags 1.3.2",
"iced_accessibility",
@ -2088,7 +2088,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.7.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"futures",
"iced_core",
@ -2100,7 +2100,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.9.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2118,7 +2118,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -2131,7 +2131,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.1.1"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2143,7 +2143,7 @@ dependencies = [
[[package]]
name = "iced_sctk"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"enum-repr",
"float-cmp",
@ -2167,7 +2167,7 @@ dependencies = [
[[package]]
name = "iced_style"
version = "0.9.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_core",
"once_cell",
@ -2177,7 +2177,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"bytemuck",
"cosmic-text 0.9.0",
@ -2195,7 +2195,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.11.1"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2217,7 +2217,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.1.3"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_renderer",
"iced_runtime",
@ -2232,7 +2232,7 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.10.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"iced_graphics",
"iced_runtime",
@ -2357,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.20",
"rustix 0.38.21",
"windows-sys 0.48.0",
]
@ -2479,7 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#d8172e3a778a7a4b8212e590a798ee0f8ad1b49e"
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
dependencies = [
"apply",
"ashpd",
@ -3483,7 +3483,7 @@ dependencies = [
"cfg-if",
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.20",
"rustix 0.38.21",
"tracing",
"windows-sys 0.48.0",
]
@ -3841,9 +3841,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.26"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
@ -3855,9 +3855,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.20"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.1",
"errno",
@ -4375,14 +4375,14 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
[[package]]
name = "tempfile"
version = "3.8.0"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
"cfg-if",
"fastrand 2.0.1",
"redox_syscall 0.3.5",
"rustix 0.38.20",
"redox_syscall 0.4.1",
"rustix 0.38.21",
"windows-sys 0.48.0",
]
@ -4548,14 +4548,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800"
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.5",
"toml_edit 0.20.7",
]
[[package]]
@ -4580,9 +4580,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.5"
version = "0.20.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85"
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
dependencies = [
"indexmap 2.0.2",
"serde",
@ -5854,18 +5854,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
[[package]]
name = "zerocopy"
version = "0.7.15"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81ba595b9f2772fbee2312de30eeb80ec773b4cb2f1e8098db024afadda6c06f"
checksum = "c552e97c5a9b90bc8ddc545b5106e798807376356688ebaa3aee36f44f8c4b9e"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.15"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "772666c41fb6dceaf520b564b962d738a8e1a83b41bd48945f50837aed78bb1d"
checksum = "964bc0588d7ac1c0243d0427ef08482618313702bbb014806cb7ab3da34d3d99"
dependencies = [
"proc-macro2",
"quote",

View file

@ -8,12 +8,12 @@ use cosmic::{
Alignment, Length, Limits,
},
style,
widget::{self, button, icon, segmented_button, view_switcher},
widget::{self, button, icon, nav_bar, segmented_button, view_switcher},
ApplicationExt, Element,
};
use cosmic_text::{FontSystem, SyntaxSystem, ViMode};
use std::{
env,
env, fs,
path::{Path, PathBuf},
sync::Mutex,
};
@ -21,7 +21,7 @@ use std::{
use self::menu::menu_bar;
mod menu;
use self::project::Project;
use self::project::ProjectNode;
mod project;
use self::tab::Tab;
@ -60,7 +60,7 @@ impl Config {
pub struct App {
core: Core,
projects: Vec<Project>,
nav_model: segmented_button::SingleSelectModel,
tab_model: segmented_button::SingleSelectModel,
config: Config,
}
@ -87,13 +87,95 @@ impl App {
self.tab_model.active_data_mut()
}
pub fn open_project<P: AsRef<Path>>(&mut self, path: P) {
match Project::new(&path) {
Ok(project) => self.projects.push(project),
fn open_folder<P: AsRef<Path>>(&mut self, path: P, mut position: u16, indent: u16) {
let read_dir = match fs::read_dir(&path) {
Ok(ok) => ok,
Err(err) => {
log::error!("failed to open '{}': {}", path.as_ref().display(), err);
log::error!("failed to read directory {:?}: {}", path.as_ref(), err);
return;
}
};
let mut nodes = Vec::new();
for entry_res in read_dir {
let entry = match entry_res {
Ok(ok) => ok,
Err(err) => {
log::error!(
"failed to read entry in directory {:?}: {}",
path.as_ref(),
err
);
continue;
}
};
let entry_path = entry.path();
let node = match ProjectNode::new(&entry_path) {
Ok(ok) => ok,
Err(err) => {
log::error!(
"failed to open directory {:?} entry {:?}: {}",
path.as_ref(),
entry_path,
err
);
continue;
}
};
nodes.push(node);
}
nodes.sort_by(|a, b| a.name().cmp(b.name()));
for node in nodes {
self.nav_model
.insert()
.position(position)
.indent(indent)
.icon(icon::from_name(node.icon_name()).size(16).icon())
.text(node.name().to_string())
.data(node);
position += 1;
}
}
pub fn open_project<P: AsRef<Path>>(&mut self, path: P) {
let node = match ProjectNode::new(&path) {
Ok(ok) => ok,
Err(err) => {
log::error!("failed to open project {:?}: {}", path.as_ref(), err);
return;
}
};
let project = match node {
ProjectNode::Folder { name, path, .. } => ProjectNode::Root {
name,
path,
open: true,
},
_ => {
log::error!(
"failed to open project {:?}: not a directory",
path.as_ref()
);
return;
}
};
let id = self
.nav_model
.insert()
.icon(icon::from_name(project.icon_name()).size(16).icon())
.text(project.name().to_string())
.data(project)
.id();
let position = self.nav_model.position(id).unwrap_or(0);
self.open_folder(&path, position + 1, 1);
}
pub fn open_tab(&mut self, path_opt: Option<PathBuf>) {
@ -105,8 +187,8 @@ impl App {
self.tab_model
.insert()
.text(tab.title())
.icon(icon::from_name("text-x-generic").icon())
.data(tab)
.icon(icon::from_name("text-x-generic").size(16).icon())
.data::<Tab>(tab)
.closable()
.activate();
}
@ -148,7 +230,7 @@ impl cosmic::Application for App {
fn init(core: Core, _flags: Self::Flags) -> (Self, Command<Self::Message>) {
let mut app = App {
core,
projects: Vec::new(),
nav_model: nav_bar::Model::builder().build(),
tab_model: segmented_button::Model::builder().build(),
config: Config::new(),
};
@ -162,6 +244,11 @@ impl cosmic::Application for App {
}
}
// Show nav bar only if project is provided
if app.core.nav_bar_active() != app.nav_model.iter().next().is_some() {
app.core.nav_bar_toggle();
}
// Open an empty file if no arguments provided
if app.tab_model.iter().next().is_none() {
app.open_tab(None);
@ -171,7 +258,65 @@ impl cosmic::Application for App {
(app, command)
}
fn update(&mut self, message: Message) -> Command<Self::Message> {
fn nav_model(&self) -> Option<&nav_bar::Model> {
Some(&self.nav_model)
}
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Message> {
let node = match self.nav_model.data_mut::<ProjectNode>(id) {
Some(node) => {
match node {
ProjectNode::Folder { open, .. } => {
*open = !*open;
}
_ => {}
}
node.clone()
}
None => {
log::warn!("no path found for id {:?}", id);
return Command::none();
}
};
self.nav_model
.icon_set(id, icon::from_name(node.icon_name()).size(16).icon());
match node {
ProjectNode::Root { .. } => {
log::warn!("TODO: root node click");
Command::none()
}
ProjectNode::Folder { path, open, .. } => {
let position = self.nav_model.position(id).unwrap_or(0);
let indent = self.nav_model.indent(id).unwrap_or(0);
if open {
self.open_folder(path, position + 1, indent + 1);
} else {
loop {
let child_id = match self.nav_model.entity_at(position + 1) {
Some(some) => some,
None => break,
};
if self.nav_model.indent(child_id).unwrap_or(0) > indent {
self.nav_model.remove(child_id);
} else {
break;
}
}
}
Command::none()
}
ProjectNode::File { path, .. } => {
//TODO: go to already open file if possible
self.open_tab(Some(path.clone()));
self.update_title()
}
}
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::New => {
self.open_tab(None);
@ -181,7 +326,6 @@ impl cosmic::Application for App {
return Command::perform(
async {
if let Some(handle) = rfd::AsyncFileDialog::new().pick_file().await {
println!("{}", handle.path().display());
message::app(Message::Open(handle.path().to_owned()))
} else {
message::none()
@ -311,19 +455,7 @@ impl cosmic::Application for App {
}
};
let mut project_row = widget::row::with_capacity(2);
if !self.projects.is_empty() {
/*TODO: project tree view
let mut project_list = widget::column::with_capacity(self.projects.len());
for project in self.projects.iter() {
project_list = project_list.push(widget::text(&project.name));
}
project_row = project_row.push(project_list);
*/
}
project_row = project_row.push(tab_column);
let content: Element<_> = project_row.into();
let content: Element<_> = tab_column.into();
// Uncomment to debug layout:
//content.explain(cosmic::iced::Color::WHITE)

View file

@ -1,28 +1,80 @@
// SPDX-License-Identifier: GPL-3.0-only
use std::{
fs, io,
path::{Path, PathBuf},
};
pub struct Project {
path: PathBuf,
name: String,
#[derive(Clone, Debug)]
pub enum ProjectNode {
Root {
name: String,
path: PathBuf,
open: bool,
},
Folder {
name: String,
path: PathBuf,
open: bool,
},
File {
name: String,
path: PathBuf,
},
}
impl Project {
impl ProjectNode {
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let path = fs::canonicalize(path)?;
let name = path
.file_name()
.ok_or(io::Error::new(
io::ErrorKind::Other,
format!("Path {:?} has no file name", path),
format!("path {:?} has no file name", path),
))?
.to_str()
.ok_or(io::Error::new(
io::ErrorKind::Other,
format!("Path {:?} is not valid UTF-8", path),
format!("path {:?} is not valid UTF-8", path),
))?
.to_string();
Ok(Self { path, name })
Ok(if path.is_dir() {
Self::Folder {
path,
name,
open: false,
}
} else {
Self::File { path, name }
})
}
pub fn icon_name(&self) -> &str {
match self {
//TODO: different icon for project?
ProjectNode::Root { open, .. } => {
if *open {
"go-down-symbolic"
} else {
"go-next-symbolic"
}
}
ProjectNode::Folder { open, .. } => {
if *open {
"go-down-symbolic"
} else {
"go-next-symbolic"
}
}
ProjectNode::File { .. } => "text-x-generic",
}
}
pub fn name(&self) -> &str {
match self {
ProjectNode::Root { name, .. } => name,
ProjectNode::Folder { name, .. } => name,
ProjectNode::File { name, .. } => name,
}
}
}