From b4e14f7f27d8e0053c154e9e01aa0daadb06f553 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:21:04 -0500 Subject: [PATCH 01/19] Fix bug preventing TUI editors from opening. Also fix bugs building file tree for paths including `../`. --- Cargo.lock | 1 - Cargo.toml | 1 - src/tui/explorer.rs | 95 ++++++++++++++++++++++++++++++++------------- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55a5491..d77a4e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,7 +304,6 @@ dependencies = [ "syntect", "tui-logger", "tui-tree-widget", - "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 237e11d..320aeab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ tui-tree-widget = "0.24.0" tui-logger = "0.18.1" edtui = "0.11.1" strum = "0.27.2" -uuid = { version = "1.19.0", features = ["v4"] } devicons = "0.6.12" [build-dependencies] diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index cf295bb..cf03cae 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -4,7 +4,7 @@ use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; -use log::trace; +use log::{info, trace}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -18,12 +18,55 @@ use tui_tree_widget::{Tree, TreeItem, TreeState}; #[derive(Debug)] pub struct Explorer<'a> { - pub(crate) root_path: PathBuf, + root_path: PathBuf, tree_items: TreeItem<'a, String>, tree_state: TreeState, pub(crate) component_state: ComponentState, } +struct EntryMeta { + abs_path: String, + file_name: String, + is_dir: bool, +} + +impl EntryMeta { + /// Normalizes a path, returning an absolute from the root of the filesystem. + /// Does not resolve symlinks and extracts `./` or `../` segments. + fn normalize>(p: P) -> PathBuf { + let path = p.as_ref(); + let mut buf = PathBuf::new(); + + for comp in path.components() { + match comp { + std::path::Component::ParentDir => { + buf.pop(); + } + std::path::Component::CurDir => {} + _ => buf.push(comp), + } + } + + buf + } + + fn new>(p: P) -> Result { + let path = p.as_ref(); + let is_dir = path.is_dir(); + let abs_path = Self::normalize(&path).to_string_lossy().to_string(); + let file_name = Path::new(&abs_path) + .file_name() + .context(format!("Failed to get file name for path: {abs_path:?}"))? + .to_string_lossy() + .to_string(); + Ok(EntryMeta { + abs_path, + file_name, + is_dir, + }) + } +} + impl<'a> Explorer<'a> { pub const ID: &'static str = "Explorer"; @@ -41,46 +84,46 @@ impl<'a> Explorer<'a> { Ok(explorer) } - fn build_tree_from_path(path: PathBuf) -> Result> { + /// Builds the file tree from a path using recursion. + /// The identifiers used for the TreeItems are normalized. Symlinks are not resolved. + /// Resolving symlinks would cause collisions on the TreeItem unique identifiers within the set. + fn build_tree_from_path>(p: P) -> Result> { + let path = p.as_ref(); let mut children = vec![]; - let clean_path = fs::canonicalize(path)?; - if let Ok(entries) = fs::read_dir(&clean_path) { + let path_meta = EntryMeta::new(path)?; + if let Ok(entries) = fs::read_dir(&path_meta.abs_path) { let mut paths = entries .map(|res| res.map(|e| e.path())) .collect::, std::io::Error>>() .context(format!( "Failed to build vector of paths under directory: {:?}", - clean_path + &path_meta.abs_path ))?; paths.sort(); - for path in paths { - if path.is_dir() { - children.push(Self::build_tree_from_path(path)?); + for entry_path in paths { + let entry_meta = EntryMeta::new(&entry_path)?; + if entry_meta.is_dir { + children.push(Self::build_tree_from_path(&entry_meta.abs_path)?); } else { - if let Ok(path) = fs::canonicalize(&path) { - let path_str = path.to_string_lossy().to_string(); - children.push(TreeItem::new_leaf( - path_str + uuid::Uuid::new_v4().to_string().as_str(), - path.file_name() - .context("Failed to get file name from path.")? - .to_string_lossy() - .to_string(), - )); - } + children.push(TreeItem::new_leaf( + entry_meta.abs_path.clone(), + entry_meta.file_name.clone(), + )); } } } + // Note: The first argument is a unique identifier, where no 2 TreeItems may share the same. + // For a file tree this is fine because we shouldn't list the same object twice. TreeItem::new( - clean_path.to_string_lossy().to_string() + uuid::Uuid::new_v4().to_string().as_str(), - clean_path - .file_name() - .context(format!("Failed to get file name from path: {clean_path:?}"))? - .to_string_lossy() - .to_string(), + path_meta.abs_path.clone(), + path_meta.file_name.clone(), children, ) - .context(format!("Failed to build tree from path: {clean_path:?}")) + .context(format!( + "Failed to build tree from path: {:?}", + path_meta.abs_path + )) } pub fn selected(&self) -> Result { -- 2.49.1 From 7d4f23d82a0de27db42a97e344876dc9b3e560a8 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:24:44 -0500 Subject: [PATCH 02/19] Fix bug showing project name in explorer. --- src/tui/explorer.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index cf03cae..fbc15cb 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -17,13 +17,6 @@ use std::path::{Path, PathBuf}; use tui_tree_widget::{Tree, TreeItem, TreeState}; #[derive(Debug)] -pub struct Explorer<'a> { - root_path: PathBuf, - tree_items: TreeItem<'a, String>, - tree_state: TreeState, - pub(crate) component_state: ComponentState, -} - struct EntryMeta { abs_path: String, file_name: String, @@ -67,13 +60,21 @@ impl EntryMeta { } } +#[derive(Debug)] +pub struct Explorer<'a> { + root_path: EntryMeta, + tree_items: TreeItem<'a, String>, + tree_state: TreeState, + pub(crate) component_state: ComponentState, +} + impl<'a> Explorer<'a> { pub const ID: &'static str = "Explorer"; pub fn new(path: &PathBuf) -> Result { trace!(target:Self::ID, "Building {}", Self::ID); let explorer = Explorer { - root_path: path.to_owned(), + root_path: EntryMeta::new(&path)?, tree_items: Self::build_tree_from_path(path.to_owned())?, tree_state: TreeState::default(), component_state: ComponentState::default().with_help_text(concat!( @@ -140,15 +141,11 @@ impl<'a> Explorer<'a> { impl<'a> Widget for &mut Explorer<'a> { fn render(self, area: Rect, buf: &mut Buffer) { if let Ok(tree) = Tree::new(&self.tree_items.children()) { - let file_name = self - .root_path - .file_name() - .unwrap_or_else(|| OsStr::new("Unknown")); StatefulWidget::render( tree.block( Block::default() .borders(Borders::ALL) - .title(file_name.to_string_lossy()) + .title(self.root_path.file_name.clone()) .border_style(Style::default().fg(self.component_state.get_active_color())) .title_style(Style::default().fg(Color::Green)) .title_alignment(Alignment::Center), -- 2.49.1 From 7ad25af13d02da0e50fd180a90462fcfb8253456 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:40:22 -0500 Subject: [PATCH 03/19] Ignore .qmlls.ini. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8e9b114..50b8e35 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ **/target/** **/.qtcreator/** **/.idea/** -**/*.autosave/** \ No newline at end of file +**/*.autosave/** +**/.qmlls.ini -- 2.49.1 From 1119b3db9beca3030d7303dd37057c9f1ba55ed6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:41:17 -0500 Subject: [PATCH 04/19] Add libclide. --- Cargo.lock | 5 +++++ Cargo.toml | 1 + libclide/Cargo.toml | 6 ++++++ libclide/src/lib.rs | 14 ++++++++++++++ 4 files changed, 26 insertions(+) create mode 100644 libclide/Cargo.toml create mode 100644 libclide/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d77a4e6..f6b20ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,7 @@ dependencies = [ "devicons", "dirs", "edtui", + "libclide", "log", "ratatui", "strum", @@ -1121,6 +1122,10 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libclide" +version = "0.1.0" + [[package]] name = "libredox" version = "0.1.12" diff --git a/Cargo.toml b/Cargo.toml index 320aeab..eb1c68d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ tui-logger = "0.18.1" edtui = "0.11.1" strum = "0.27.2" devicons = "0.6.12" +libclide = { path = "./libclide" } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml new file mode 100644 index 0000000..7a3c510 --- /dev/null +++ b/libclide/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "libclide" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/libclide/src/lib.rs b/libclide/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/libclide/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} -- 2.49.1 From d95aa680ff50ebb70ca678718c93820113d9bbb6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:56:28 -0500 Subject: [PATCH 05/19] Add missing headers. --- libclide/src/lib.rs | 17 ++++------------- qml/Components/ClideBreadCrumbs.qml | 4 ++++ qml/Components/ClideMenu.qml | 4 ++++ qml/Components/ClideMenuItem.qml | 4 ++++ 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/libclide/src/lib.rs b/libclide/src/lib.rs index b93cf3f..950c8f3 100644 --- a/libclide/src/lib.rs +++ b/libclide/src/lib.rs @@ -1,14 +1,5 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} +pub mod fs; diff --git a/qml/Components/ClideBreadCrumbs.qml b/qml/Components/ClideBreadCrumbs.qml index 7b02e93..21bec66 100644 --- a/qml/Components/ClideBreadCrumbs.qml +++ b/qml/Components/ClideBreadCrumbs.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 diff --git a/qml/Components/ClideMenu.qml b/qml/Components/ClideMenu.qml index 6fd076e..c84cb13 100644 --- a/qml/Components/ClideMenu.qml +++ b/qml/Components/ClideMenu.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls.Basic diff --git a/qml/Components/ClideMenuItem.qml b/qml/Components/ClideMenuItem.qml index 4b859be..76f27fe 100644 --- a/qml/Components/ClideMenuItem.qml +++ b/qml/Components/ClideMenuItem.qml @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + import QtQuick import QtQuick.Controls.Basic -- 2.49.1 From 289f94300c30284a0df850b5d3513674d5910cda Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 14:59:48 -0500 Subject: [PATCH 06/19] Move EntryMeta to libclide. --- Cargo.lock | 480 +++++++++++++++++++++++----------- libclide/Cargo.lock | 16 ++ libclide/Cargo.toml | 1 + libclide/src/fs.rs | 5 + libclide/src/fs/entry_meta.rs | 50 ++++ src/tui/explorer.rs | 48 +--- 6 files changed, 407 insertions(+), 193 deletions(-) create mode 100644 libclide/Cargo.lock create mode 100644 libclide/src/fs.rs create mode 100644 libclide/src/fs/entry_meta.rs diff --git a/Cargo.lock b/Cargo.lock index f6b20ae..04a2c22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arboard" @@ -161,9 +161,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -176,15 +176,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder-lite" @@ -203,9 +203,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -247,9 +247,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -269,21 +269,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "clide" @@ -405,7 +405,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", @@ -463,7 +463,7 @@ dependencies = [ "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", - "foldhash", + "foldhash 0.2.0", "link-cplusplus", ] @@ -479,7 +479,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -492,14 +492,14 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "cxx-qt" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7c6dea4b551221e1df4349af7ae6af2c906c16860bdab5cada5a957b43cbbc" +checksum = "3fdf26e5ee4375a85799d0fe436662e5a8ef099bd0964b84bce8812c35c7daea" dependencies = [ "cxx", "cxx-qt-build", @@ -511,9 +511,9 @@ dependencies = [ [[package]] name = "cxx-qt-build" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70cbc19fb351a0413632b326ad4862baea7f50d641d2d360b29ad0f772547bc" +checksum = "f9a88c7f8241bfe4dfd19d27397a5845f0f275735a34ee7fb219045da81945e3" dependencies = [ "cc", "codespan-reporting 0.11.1", @@ -528,9 +528,9 @@ dependencies = [ [[package]] name = "cxx-qt-gen" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33744d84f696836347071ad73da233f758f98e5c0b348e2855140173b36bffa2" +checksum = "9b1917c90b10117890e3794f4a0dc764a24704de40711d688ae128b0704490b8" dependencies = [ "clang-format", "convert_case 0.6.0", @@ -538,14 +538,14 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "cxx-qt-lib" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eacfc219a287c422619b7704166bfd3b9b842367bd9124ad5557e6150a2d658" +checksum = "d7b3de184cff59ad29d657021f933380e584d9b70265247f6a96098bef964211" dependencies = [ "cxx", "cxx-qt", @@ -555,14 +555,14 @@ dependencies = [ [[package]] name = "cxx-qt-macro" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb8ce32a983d56470101ff8e61c8a700ba37805b6e942186c6f3dd5d6ad44f6" +checksum = "230b0d8b20d3d02a85885742be4fabc0382fead4e382dd8c9c9ff1827f221e8c" dependencies = [ "cxx-qt-gen", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -576,7 +576,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -594,7 +594,7 @@ dependencies = [ "indexmap", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -617,7 +617,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -628,7 +628,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -639,9 +639,9 @@ checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "2163a0e204a148662b6b6816d4b5d5668a5f2f8df498ccbd5cd0e864e78fecba" dependencies = [ "powerfmt", ] @@ -665,7 +665,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -714,7 +714,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", ] @@ -765,7 +765,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -836,7 +836,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -861,9 +861,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "finl_unicode" @@ -879,9 +879,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -893,6 +893,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foldhash" version = "0.2.0" @@ -942,6 +948,19 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + [[package]] name = "half" version = "2.7.1" @@ -953,6 +972,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + [[package]] name = "hashbrown" version = "0.16.1" @@ -961,7 +989,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.2.0", ] [[package]] @@ -978,9 +1006,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1000,6 +1028,12 @@ dependencies = [ "cc", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1027,7 +1061,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1049,7 +1085,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1085,9 +1121,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" dependencies = [ "once_cell", "wasm-bindgen", @@ -1099,7 +1135,7 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", "portable-atomic", "thiserror 2.0.18", ] @@ -1117,14 +1153,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.180" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libclide" version = "0.1.0" +dependencies = [ + "anyhow", +] [[package]] name = "libredox" @@ -1132,7 +1177,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", ] @@ -1142,7 +1187,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -1193,7 +1238,7 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown", + "hashbrown 0.16.1", ] [[package]] @@ -1208,9 +1253,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmem" @@ -1271,7 +1316,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", @@ -1290,9 +1335,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -1302,7 +1347,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1338,7 +1383,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-graphics", "objc2-foundation", @@ -1350,7 +1395,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", ] @@ -1361,7 +1406,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "dispatch2", "objc2", "objc2-core-foundation", @@ -1380,7 +1425,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -1391,7 +1436,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "objc2", "objc2-core-foundation", ] @@ -1414,7 +1459,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", "once_cell", "onig_sys", @@ -1476,9 +1521,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -1486,9 +1531,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -1496,22 +1541,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", @@ -1557,7 +1602,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1590,11 +1635,11 @@ dependencies = [ [[package]] name = "png" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97baced388464909d42d89643fe4361939af9b7ce7a31ee32a168f832a70f2a0" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "crc32fast", "fdeflate", "flate2", @@ -1603,9 +1648,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "powerfmt" @@ -1613,6 +1658,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1633,9 +1688,9 @@ dependencies = [ [[package]] name = "qt-build-utils" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cfd41d4f115dfc940ea3ea31b3aed77233ad09ab8859a227ed61323025590af" +checksum = "4d074750fd3baba12fdb47388d591ad9ed043e23864a79d129dcf3a1ef6fc8d9" dependencies = [ "anyhow", "cc", @@ -1661,9 +1716,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -1709,9 +1764,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "compact_str", - "hashbrown", + "hashbrown 0.16.1", "indoc", "itertools", "kasuari", @@ -1761,8 +1816,8 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ - "bitflags 2.10.0", - "hashbrown", + "bitflags 2.11.0", + "hashbrown 0.16.1", "indoc", "instability", "itertools", @@ -1780,7 +1835,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -1796,9 +1851,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -1808,9 +1863,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1819,9 +1874,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustc_version" @@ -1838,7 +1893,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", @@ -1853,9 +1908,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -1911,7 +1966,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -1983,9 +2038,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "smallvec" @@ -2023,7 +2078,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2039,9 +2094,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.114" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -2107,7 +2162,7 @@ checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", - "bitflags 2.10.0", + "bitflags 2.11.0", "fancy-regex", "filedescriptor", "finl_unicode", @@ -2167,7 +2222,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2178,7 +2233,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2197,9 +2252,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -2214,15 +2269,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -2268,9 +2323,9 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -2301,6 +2356,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2309,12 +2370,12 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ "atomic", - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "wasm-bindgen", ] @@ -2360,10 +2421,19 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.108" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" dependencies = [ "cfg-if", "once_cell", @@ -2374,9 +2444,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2384,26 +2454,60 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "weezl" version = "0.1.12" @@ -2534,7 +2638,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2545,7 +2649,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] @@ -2660,6 +2764,88 @@ name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "x11rb" @@ -2689,29 +2875,29 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn 2.0.117", ] [[package]] name = "zmij" -version = "1.0.16" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zune-core" diff --git a/libclide/Cargo.lock b/libclide/Cargo.lock new file mode 100644 index 0000000..e5365f2 --- /dev/null +++ b/libclide/Cargo.lock @@ -0,0 +1,16 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "libclide" +version = "0.1.0" +dependencies = [ + "anyhow", +] diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index 7a3c510..60ce35e 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2024" [dependencies] +anyhow = "1.0.102" diff --git a/libclide/src/fs.rs b/libclide/src/fs.rs new file mode 100644 index 0000000..04e84c0 --- /dev/null +++ b/libclide/src/fs.rs @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +pub mod entry_meta; diff --git a/libclide/src/fs/entry_meta.rs b/libclide/src/fs/entry_meta.rs new file mode 100644 index 0000000..d7483b9 --- /dev/null +++ b/libclide/src/fs/entry_meta.rs @@ -0,0 +1,50 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +use std::path::{Path, PathBuf}; +use anyhow::{Context, Result}; + +#[derive(Debug)] +pub struct EntryMeta { + pub abs_path: String, + pub file_name: String, + pub is_dir: bool, +} + +impl EntryMeta { + /// Normalizes a path, returning an absolute from the root of the filesystem. + /// Does not resolve symlinks and extracts `./` or `../` segments. + fn normalize>(p: P) -> PathBuf { + let path = p.as_ref(); + let mut buf = PathBuf::new(); + + for comp in path.components() { + match comp { + std::path::Component::ParentDir => { + buf.pop(); + } + std::path::Component::CurDir => {} + _ => buf.push(comp), + } + } + + buf + } + + pub fn new>(p: P) -> Result { + let path = p.as_ref(); + let is_dir = path.is_dir(); + let abs_path = Self::normalize(&path).to_string_lossy().to_string(); + let file_name = Path::new(&abs_path) + .file_name() + .context(format!("Failed to get file name for path: {abs_path:?}"))? + .to_string_lossy() + .to_string(); + Ok(EntryMeta { + abs_path, + file_name, + is_dir, + }) + } +} diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index fbc15cb..03ccfa6 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -4,61 +4,17 @@ use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; -use log::{info, trace}; +use log::{trace}; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; use ratatui::prelude::Style; use ratatui::style::{Color, Modifier}; use ratatui::widgets::{Block, Borders, StatefulWidget, Widget}; -use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; use tui_tree_widget::{Tree, TreeItem, TreeState}; - -#[derive(Debug)] -struct EntryMeta { - abs_path: String, - file_name: String, - is_dir: bool, -} - -impl EntryMeta { - /// Normalizes a path, returning an absolute from the root of the filesystem. - /// Does not resolve symlinks and extracts `./` or `../` segments. - fn normalize>(p: P) -> PathBuf { - let path = p.as_ref(); - let mut buf = PathBuf::new(); - - for comp in path.components() { - match comp { - std::path::Component::ParentDir => { - buf.pop(); - } - std::path::Component::CurDir => {} - _ => buf.push(comp), - } - } - - buf - } - - fn new>(p: P) -> Result { - let path = p.as_ref(); - let is_dir = path.is_dir(); - let abs_path = Self::normalize(&path).to_string_lossy().to_string(); - let file_name = Path::new(&abs_path) - .file_name() - .context(format!("Failed to get file name for path: {abs_path:?}"))? - .to_string_lossy() - .to_string(); - Ok(EntryMeta { - abs_path, - file_name, - is_dir, - }) - } -} +use libclide::fs::entry_meta::EntryMeta; #[derive(Debug)] pub struct Explorer<'a> { -- 2.49.1 From 0e8910807eb7ad3aa7f7a2925bcee5d5561b513d Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 15:06:08 -0500 Subject: [PATCH 07/19] Add CI. --- .cargo/config.toml | 4 -- .github/actions/setup-qt/action.yml | 20 +++++++++ .github/workflows/build.yaml | 67 +++++++++++++++++++++++++++++ README.md | 2 + 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 .github/actions/setup-qt/action.yml create mode 100644 .github/workflows/build.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml index 6a03545..2f415a1 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,2 @@ [build] rustflags = [ "-C", "link-arg=-fuse-ld=lld", ] - -[env] -QMAKE="/opt/Qt/6.7.3/gcc_64/bin/qmake6" -LD_LIBRARY_PATH="/opt/Qt/6.7.3/gcc_64/lib" diff --git a/.github/actions/setup-qt/action.yml b/.github/actions/setup-qt/action.yml new file mode 100644 index 0000000..ff84d38 --- /dev/null +++ b/.github/actions/setup-qt/action.yml @@ -0,0 +1,20 @@ +name: "Setup Qt" +description: "Install clide dependencies" +inputs: + qt-version: + description: "Qt version to install" + required: true + +runs: + using: "composite" + steps: + - name: Install apt packages + run: | + sudo apt update -y + sudo apt install -y build-essential cmake curl libgl1-mesa-dev python3 python3-pip + shell: bash + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + version: ${{ inputs.qt-version }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..29ad7fa --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,67 @@ +name: Build + +on: + push: + branches: + - '**' + tags: + - 'v*' + pull_request: + +env: + QT_VERSION: 6.7.3 + +jobs: + Build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Qt + uses: ./.github/actions/setup-qt + with: + qt-version: ${{ env.QT_VERSION }} + + - name: Build libclide + run: | + cargo b -p libclide --release + - name: Build clide + run: | + cargo b --release + + Test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Qt + uses: ./.github/actions/setup-qt + with: + qt-version: ${{ env.QT_VERSION }} + + - name: Test libclide + run: | + cargo test -p libclide + - name: Test clide + run: | + cargo test + + Lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Qt + uses: ./.github/actions/setup-qt + with: + qt-version: ${{ env.QT_VERSION }} + + - name: Lint libclide + run: | + cargo clippy --manifest-path libclide/Cargo.toml -- -D warnings + - name: Lint clide + run: | + cargo clippy -- -D warnings diff --git a/README.md b/README.md index 2bb4dfc..0e9d996 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CLIDE +[![Build](https://git.shaunreed.com/shaunrd0/clide/actions/workflows/build.yaml/badge.svg)](https://git.shaunreed.com/shaunrd0/clide/workflows/build.yml) + CLIDE is an extendable command-line driven development environment written in Rust using the Qt UI framework that supports both full and headless Linux environments. The GUI is written in QML compiled through Rust using the cxx-qt crate, while the TUI was implemented using the ratatui crate. -- 2.49.1 From 7490e36a2fddb20161a2685da355059b2dac04d5 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 18:38:07 -0500 Subject: [PATCH 08/19] Fix libclide lints. --- libclide/src/fs/entry_meta.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libclide/src/fs/entry_meta.rs b/libclide/src/fs/entry_meta.rs index d7483b9..8785710 100644 --- a/libclide/src/fs/entry_meta.rs +++ b/libclide/src/fs/entry_meta.rs @@ -35,7 +35,7 @@ impl EntryMeta { pub fn new>(p: P) -> Result { let path = p.as_ref(); let is_dir = path.is_dir(); - let abs_path = Self::normalize(&path).to_string_lossy().to_string(); + let abs_path = Self::normalize(path).to_string_lossy().to_string(); let file_name = Path::new(&abs_path) .file_name() .context(format!("Failed to get file name for path: {abs_path:?}"))? -- 2.49.1 From d5671a5590e3185be45e7580e5aaaa06effa4b9f Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 19:03:18 -0500 Subject: [PATCH 09/19] Fix clide lints. --- build.rs | 2 +- src/gui/filesystem.rs | 8 ++++---- src/main.rs | 2 +- src/tui/about.rs | 4 ++-- src/tui/app.rs | 47 +++++++++++++++++++++---------------------- src/tui/editor.rs | 5 ++--- src/tui/explorer.rs | 32 ++++++++++++++--------------- src/tui/menu_bar.rs | 7 +++---- 8 files changed, 51 insertions(+), 56 deletions(-) diff --git a/build.rs b/build.rs index fce6777..325c73b 100644 --- a/build.rs +++ b/build.rs @@ -1,7 +1,7 @@ use cxx_qt_build::{CxxQtBuilder, QmlModule}; fn main() { - CxxQtBuilder::new_qml_module(QmlModule::new("clide.module").qml_files(&[ + CxxQtBuilder::new_qml_module(QmlModule::new("clide.module").qml_files([ "qml/ClideApplicationView.qml", "qml/ClideEditorView.qml", "qml/ClideExplorerView.qml", diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 3528cfe..4f64599 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -76,7 +76,7 @@ impl qobject::FileSystem { return QString::default(); } let meta = fs::metadata(path.to_string()) - .expect(format!("Failed to get file metadata {path:?}").as_str()); + .unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); if !meta.is_file() { warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); return QString::default(); @@ -114,7 +114,7 @@ impl qobject::FileSystem { output.push_str("\n"); QString::from(output) } else { - return QString::default(); + QString::default() } } @@ -126,7 +126,7 @@ impl qobject::FileSystem { fn set_directory(self: std::pin::Pin<&mut Self>, path: &QString) -> QModelIndex { if !path.is_empty() && fs::metadata(path.to_string()) - .expect(format!("Failed to get metadata for path {path:?}").as_str()) + .unwrap_or_else(|_| panic!("Failed to get metadata for path {path:?}")) .is_dir() { self.set_root_path(path) @@ -147,7 +147,7 @@ impl qobject::FileSystem { if Path::new(&str).is_dir() { // Ensures directories are given a folder icon and not mistakenly resolved to a language. // For example, a directory named `cpp` would otherwise return a C++ icon. - return QString::from(FileIcon::from("dir/").to_string()) + return QString::from(FileIcon::from("dir/").to_string()); } let icon = FileIcon::from(str); QString::from(icon.to_string()) diff --git a/src/main.rs b/src/main.rs index c975f90..80ef06c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,7 +82,7 @@ fn main() -> Result<()> { RunMode::Gui => { trace!(target:"main()", "Starting GUI in a new process"); Command::new(std::env::current_exe()?) - .args(&["--gui", app_context.path.to_str().unwrap()]) + .args(["--gui", app_context.path.to_str().unwrap()]) .stdout(Stdio::null()) .stderr(Stdio::null()) .stdin(Stdio::null()) diff --git a/src/tui/about.rs b/src/tui/about.rs index 7b53b7b..878d6d8 100644 --- a/src/tui/about.rs +++ b/src/tui/about.rs @@ -68,8 +68,8 @@ impl Widget for About { .map(|l| Line::from(Span::raw(*l))) .collect(); - Clear::default().render(kilroy_rect, buf); - Clear::default().render(chunks[1], buf); + Clear.render(kilroy_rect, buf); + Clear.render(chunks[1], buf); Paragraph::new(about_lines) .block( Block::default() diff --git a/src/tui/app.rs b/src/tui/app.rs index d5569eb..730acc1 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later use crate::tui::about::About; -use crate::tui::app::AppComponent::{AppEditor, AppExplorer, AppLogger}; use crate::tui::component::{Action, Component, Focus, FocusState, Visibility, VisibleState}; use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; @@ -26,7 +25,7 @@ use std::time::Duration; #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppComponent { - AppEditor, + Editor, AppExplorer, AppLogger, AppMenuBar, @@ -51,7 +50,7 @@ impl<'a> App<'a> { explorer: Explorer::new(&root_path)?, logger: Logger::new(), menu_bar: MenuBar::new(), - last_active: AppEditor, + last_active: AppComponent::Editor, about: false, }; Ok(app) @@ -87,7 +86,7 @@ impl<'a> App<'a> { fn draw_bottom_status(&self, area: Rect, buf: &mut Buffer) { // Determine help text from the most recently focused component. let help = match self.last_active { - AppEditor => match self.editor_tab.current_editor() { + AppComponent::Editor => match self.editor_tab.current_editor() { Some(editor) => editor.component_state.help_text.clone(), None => { if !self.editor_tab.is_empty() { @@ -96,9 +95,9 @@ impl<'a> App<'a> { "Failed to get current Editor while getting widget help text".to_string() } }, - AppExplorer => self.explorer.component_state.help_text.clone(), - AppLogger => self.logger.component_state.help_text.clone(), - AppMenuBar => self.menu_bar.component_state.help_text.clone(), + AppComponent::AppExplorer => self.explorer.component_state.help_text.clone(), + AppComponent::AppLogger => self.logger.component_state.help_text.clone(), + AppComponent::AppMenuBar => self.menu_bar.component_state.help_text.clone(), }; Paragraph::new( concat!( @@ -132,15 +131,15 @@ impl<'a> App<'a> { info!(target:Self::ID, "Changing widget focus to {:?}", focus); self.clear_focus(); match focus { - AppEditor => match self.editor_tab.current_editor_mut() { + AppComponent::Editor => match self.editor_tab.current_editor_mut() { None => { error!(target:Self::ID, "Failed to get current Editor while changing focus") } Some(editor) => editor.component_state.set_focus(Focus::Active), }, - AppExplorer => self.explorer.component_state.set_focus(Focus::Active), - AppLogger => self.logger.component_state.set_focus(Focus::Active), - AppMenuBar => self.menu_bar.component_state.set_focus(Focus::Active), + AppComponent::AppExplorer => self.explorer.component_state.set_focus(Focus::Active), + AppComponent::AppLogger => self.logger.component_state.set_focus(Focus::Active), + AppComponent::AppMenuBar => self.menu_bar.component_state.set_focus(Focus::Active), } self.last_active = focus; } @@ -255,21 +254,21 @@ impl<'a> Component for App<'a> { } // Handle events for all components. let action = match self.last_active { - AppEditor => self.editor_tab.handle_event(event.clone())?, - AppExplorer => self.explorer.handle_event(event.clone())?, - AppLogger => self.logger.handle_event(event.clone())?, + AppComponent::Editor => self.editor_tab.handle_event(event.clone())?, + AppComponent::AppExplorer => self.explorer.handle_event(event.clone())?, + AppComponent::AppLogger => self.logger.handle_event(event.clone())?, AppMenuBar => self.menu_bar.handle_event(event.clone())?, }; // Components should always handle mouse events for click interaction. - if let Some(mouse) = event.as_mouse_event() { - if mouse.kind == MouseEventKind::Down(MouseButton::Left) { - if let Some(editor) = self.editor_tab.current_editor_mut() { - editor.handle_mouse_events(mouse)?; - } - self.explorer.handle_mouse_events(mouse)?; - self.logger.handle_mouse_events(mouse)?; + if let Some(mouse) = event.as_mouse_event() + && mouse.kind == MouseEventKind::Down(MouseButton::Left) + { + if let Some(editor) = self.editor_tab.current_editor_mut() { + editor.handle_mouse_events(mouse)?; } + self.explorer.handle_mouse_events(mouse)?; + self.logger.handle_mouse_events(mouse)?; } // Handle actions returned from widgets that may need context on other widgets or app state. @@ -349,7 +348,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppExplorer); + self.change_focus(AppComponent::AppExplorer); Ok(Action::Handled) } KeyEvent { @@ -358,7 +357,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppEditor); + self.change_focus(AppComponent::Editor); Ok(Action::Handled) } KeyEvent { @@ -367,7 +366,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppLogger); + self.change_focus(AppComponent::AppLogger); Ok(Action::Handled) } KeyEvent { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 0c20d3e..294e8bd 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -115,9 +115,8 @@ impl Component for Editor { fn handle_event(&mut self, event: Event) -> Result { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. - match self.handle_key_events(key_event)? { - Action::Handled => return Ok(Action::Handled), - _ => {} + if let Action::Handled = self.handle_key_events(key_event)? { + return Ok(Action::Handled) } } self.event_handler.on_event(event, &mut self.state); diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 03ccfa6..9ac817e 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -4,7 +4,8 @@ use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; -use log::{trace}; +use libclide::fs::entry_meta::EntryMeta; +use log::trace; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -14,7 +15,6 @@ use ratatui::widgets::{Block, Borders, StatefulWidget, Widget}; use std::fs; use std::path::{Path, PathBuf}; use tui_tree_widget::{Tree, TreeItem, TreeState}; -use libclide::fs::entry_meta::EntryMeta; #[derive(Debug)] pub struct Explorer<'a> { @@ -30,8 +30,8 @@ impl<'a> Explorer<'a> { pub fn new(path: &PathBuf) -> Result { trace!(target:Self::ID, "Building {}", Self::ID); let explorer = Explorer { - root_path: EntryMeta::new(&path)?, - tree_items: Self::build_tree_from_path(path.to_owned())?, + root_path: EntryMeta::new(path)?, + tree_items: Self::build_tree_from_path(path)?, tree_state: TreeState::default(), component_state: ComponentState::default().with_help_text(concat!( "(↑/k)/(↓/j): Select item | ←/h: Close folder | →/l: Open folder |", @@ -96,7 +96,7 @@ impl<'a> Explorer<'a> { impl<'a> Widget for &mut Explorer<'a> { fn render(self, area: Rect, buf: &mut Buffer) { - if let Ok(tree) = Tree::new(&self.tree_items.children()) { + if let Ok(tree) = Tree::new(self.tree_items.children()) { StatefulWidget::render( tree.block( Block::default() @@ -130,23 +130,21 @@ impl<'a> Component for Explorer<'a> { _ => {} } } - if let Some(mouse_event) = event.as_mouse_event() { - match self.handle_mouse_events(mouse_event)? { - Action::Handled => return Ok(Action::Handled), - _ => {} - } + if let Some(mouse_event) = event.as_mouse_event() + && let Action::Handled = self.handle_mouse_events(mouse_event)? + { + return Ok(Action::Handled); } Ok(Action::Pass) } fn handle_key_events(&mut self, key: KeyEvent) -> Result { - if key.code == KeyCode::Enter { - if let Ok(selected) = self.selected() { - if Path::new(&selected).is_file() { - return Ok(Action::OpenTab); - } - } - // Otherwise fall through and handle Enter in the next match case. + if key.code == KeyCode::Enter + && let Ok(selected) = self.selected() + && Path::new(&selected).is_file() + { + // Open a tab if the selected item is a file. + return Ok(Action::OpenTab); } let changed = match key.code { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index 129d83a..f872584 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -131,7 +131,7 @@ impl MenuBar { opened: MenuBarItem, ) { let popup_area = Self::rect_under_option(title_bar_anchor, area, 27, 10); - Clear::default().render(popup_area, buf); + Clear.render(popup_area, buf); let options = opened.options().iter().map(|i| ListItem::new(i.id())); StatefulWidget::render( List::new(options) @@ -150,15 +150,14 @@ impl MenuBar { } fn rect_under_option(anchor: Rect, area: Rect, width: u16, height: u16) -> Rect { - let rect = Rect { + Rect { x: anchor.x, y: anchor.y + anchor.height, width: width.min(area.width), height, - }; + } // TODO: X offset for item option? It's fine as-is, but it might look nicer. // trace!(target:Self::ID, "Building Rect under MenuBar popup {}", rect); - rect } } -- 2.49.1 From 8fd0bb48de54752ca7c1984791ee938ccdcea034 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 19:16:50 -0500 Subject: [PATCH 10/19] Set up workspace, add formatting CI. --- .github/workflows/{build.yaml => check.yaml} | 22 ++++++++++++++++++-- Cargo.toml | 6 ++++++ libclide/src/fs/entry_meta.rs | 2 +- src/tui/editor.rs | 2 +- 4 files changed, 28 insertions(+), 4 deletions(-) rename .github/workflows/{build.yaml => check.yaml} (71%) diff --git a/.github/workflows/build.yaml b/.github/workflows/check.yaml similarity index 71% rename from .github/workflows/build.yaml rename to .github/workflows/check.yaml index 29ad7fa..958fd3c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/check.yaml @@ -1,4 +1,4 @@ -name: Build +name: Check on: push: @@ -61,7 +61,25 @@ jobs: - name: Lint libclide run: | - cargo clippy --manifest-path libclide/Cargo.toml -- -D warnings + cargo clippy -p libclide -- -D warnings - name: Lint clide run: | cargo clippy -- -D warnings + + Format: + name: Format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Qt + uses: ./.github/actions/setup-qt + with: + qt-version: ${{ env.QT_VERSION }} + + - name: Format libclide + run: | + cargo fmt -p libclide --verbose -- --check + - name: Format clide + run: | + cargo fmt --verbose -- --check diff --git a/Cargo.toml b/Cargo.toml index eb1c68d..4f199da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,9 @@ libclide = { path = "./libclide" } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } + +[workspace] +members = [ + ".", + "libclide", +] \ No newline at end of file diff --git a/libclide/src/fs/entry_meta.rs b/libclide/src/fs/entry_meta.rs index 8785710..4d896d4 100644 --- a/libclide/src/fs/entry_meta.rs +++ b/libclide/src/fs/entry_meta.rs @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: GNU General Public License v3.0 or later -use std::path::{Path, PathBuf}; use anyhow::{Context, Result}; +use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct EntryMeta { diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 294e8bd..99c07b6 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -116,7 +116,7 @@ impl Component for Editor { if let Some(key_event) = event.as_key_event() { // Handle events here that should not be passed on to the vim emulation handler. if let Action::Handled = self.handle_key_events(key_event)? { - return Ok(Action::Handled) + return Ok(Action::Handled); } } self.event_handler.on_event(event, &mut self.state); -- 2.49.1 From 73c467e19e3de9702934a138549dbe431c42e418 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 19:45:30 -0500 Subject: [PATCH 11/19] Renames. --- src/tui/app.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index 730acc1..190420c 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -8,7 +8,6 @@ use crate::tui::editor_tab::EditorTab; use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; -use AppComponent::AppMenuBar; use anyhow::{Context, Result}; use log::{error, info, trace}; use ratatui::DefaultTerminal; @@ -26,9 +25,9 @@ use std::time::Duration; #[derive(Debug, Clone, Copy, PartialEq)] pub enum AppComponent { Editor, - AppExplorer, - AppLogger, - AppMenuBar, + Explorer, + Logger, + MenuBar, } pub struct App<'a> { @@ -95,9 +94,9 @@ impl<'a> App<'a> { "Failed to get current Editor while getting widget help text".to_string() } }, - AppComponent::AppExplorer => self.explorer.component_state.help_text.clone(), - AppComponent::AppLogger => self.logger.component_state.help_text.clone(), - AppComponent::AppMenuBar => self.menu_bar.component_state.help_text.clone(), + AppComponent::Explorer => self.explorer.component_state.help_text.clone(), + AppComponent::Logger => self.logger.component_state.help_text.clone(), + AppComponent::MenuBar => self.menu_bar.component_state.help_text.clone(), }; Paragraph::new( concat!( @@ -137,9 +136,9 @@ impl<'a> App<'a> { } Some(editor) => editor.component_state.set_focus(Focus::Active), }, - AppComponent::AppExplorer => self.explorer.component_state.set_focus(Focus::Active), - AppComponent::AppLogger => self.logger.component_state.set_focus(Focus::Active), - AppComponent::AppMenuBar => self.menu_bar.component_state.set_focus(Focus::Active), + AppComponent::Explorer => self.explorer.component_state.set_focus(Focus::Active), + AppComponent::Logger => self.logger.component_state.set_focus(Focus::Active), + AppComponent::MenuBar => self.menu_bar.component_state.set_focus(Focus::Active), } self.last_active = focus; } @@ -255,9 +254,9 @@ impl<'a> Component for App<'a> { // Handle events for all components. let action = match self.last_active { AppComponent::Editor => self.editor_tab.handle_event(event.clone())?, - AppComponent::AppExplorer => self.explorer.handle_event(event.clone())?, - AppComponent::AppLogger => self.logger.handle_event(event.clone())?, - AppMenuBar => self.menu_bar.handle_event(event.clone())?, + AppComponent::Explorer => self.explorer.handle_event(event.clone())?, + AppComponent::Logger => self.logger.handle_event(event.clone())?, + AppComponent::MenuBar => self.menu_bar.handle_event(event.clone())?, }; // Components should always handle mouse events for click interaction. @@ -348,7 +347,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppComponent::AppExplorer); + self.change_focus(AppComponent::Explorer); Ok(Action::Handled) } KeyEvent { @@ -366,7 +365,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppComponent::AppLogger); + self.change_focus(AppComponent::Logger); Ok(Action::Handled) } KeyEvent { @@ -375,7 +374,7 @@ impl<'a> Component for App<'a> { kind: KeyEventKind::Press, state: _state, } => { - self.change_focus(AppMenuBar); + self.change_focus(AppComponent::MenuBar); Ok(Action::Handled) } KeyEvent { -- 2.49.1 From 65242c2ea9eb764d6aa7ddb9987a423578869544 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 20:08:41 -0500 Subject: [PATCH 12/19] Add workspace dependencies. --- Cargo.lock | 1 + Cargo.toml | 23 ++++++++++++++--------- libclide/Cargo.toml | 3 ++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 04a2c22..ac98bd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1169,6 +1169,7 @@ name = "libclide" version = "0.1.0" dependencies = [ "anyhow", + "strum", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4f199da..6176c2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,17 @@ name = "clide" version = "0.1.0" edition = "2024" +[workspace] +resolver = "3" +members = [ + ".", + "libclide", +] + +[workspace.dependencies] +anyhow = "1.0.100" +strum = "0.27.2" + [dependencies] cxx = "1.0.95" cxx-qt = "0.8.0" @@ -12,20 +23,14 @@ dirs = "6.0.0" syntect = "5.2.0" clap = { version = "4.5.54", features = ["derive"] } ratatui = "0.30.0" -anyhow = "1.0.100" tui-tree-widget = "0.24.0" tui-logger = "0.18.1" edtui = "0.11.1" -strum = "0.27.2" devicons = "0.6.12" libclide = { path = "./libclide" } +anyhow = { workspace = true } +strum = { workspace = true } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. -cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } - -[workspace] -members = [ - ".", - "libclide", -] \ No newline at end of file +cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } \ No newline at end of file diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index 60ce35e..33434a0 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -anyhow = "1.0.102" +anyhow = { workspace = true } +strum = { workspace = true } -- 2.49.1 From 11fd1301711e4c410500c1b9ce11c39d880e07b6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 20:12:04 -0500 Subject: [PATCH 13/19] Update CI badge. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e9d996..8c93c29 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CLIDE -[![Build](https://git.shaunreed.com/shaunrd0/clide/actions/workflows/build.yaml/badge.svg)](https://git.shaunreed.com/shaunrd0/clide/workflows/build.yml) +[![Check](https://github.com/shaunrd0/clide/actions/workflows/check.yaml/badge.svg)](https://github.com/shaunrd0/clide/actions/workflows/check.yaml) CLIDE is an extendable command-line driven development environment written in Rust using the Qt UI framework that supports both full and headless Linux environments. The GUI is written in QML compiled through Rust using the cxx-qt crate, while the TUI was implemented using the ratatui crate. -- 2.49.1 From c21ede292c25192efd56dd0db4feffb6fdcf83c7 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sat, 21 Feb 2026 21:10:12 -0500 Subject: [PATCH 14/19] Share colors for GUI and TUI. The shim is there but it isn't used yet. --- libclide/src/lib.rs | 1 + libclide/src/theme.rs | 5 +++ libclide/src/theme/colors.rs | 54 +++++++++++++++++++++++++++++++ src/gui/colors.rs | 62 ++++++++++++++++++------------------ src/tui/component.rs | 5 +-- 5 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 libclide/src/theme.rs create mode 100644 libclide/src/theme/colors.rs diff --git a/libclide/src/lib.rs b/libclide/src/lib.rs index 950c8f3..baf3411 100644 --- a/libclide/src/lib.rs +++ b/libclide/src/lib.rs @@ -3,3 +3,4 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later pub mod fs; +pub mod theme; diff --git a/libclide/src/theme.rs b/libclide/src/theme.rs new file mode 100644 index 0000000..645977c --- /dev/null +++ b/libclide/src/theme.rs @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +pub mod colors; diff --git a/libclide/src/theme/colors.rs b/libclide/src/theme/colors.rs new file mode 100644 index 0000000..4f52bc1 --- /dev/null +++ b/libclide/src/theme/colors.rs @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +/// Colors shared between the TUI and GUI for the current theme. +pub struct Colors {} +impl Colors { + pub const HOVERED: &str = "#303234"; + pub const UNHOVERED: &str = "#3c3f41"; + pub const PRESSED: &str = "#4b4f51"; + pub const MENUBAR: &str = "#262626"; + pub const MENUBAR_BORDER: &str = "#575757"; + pub const SCROLLBAR: &str = "#4b4f51"; + pub const SCROLLBAR_ACTIVE: &str = "#4b4f51"; + pub const SCROLLBAR_GUTTER: &str = "#3b3b3b"; + pub const LINENUMBER: &str = "#94989b"; + pub const ACTIVE: &str = "#d1d33f"; + pub const INACTIVE: &str = "#FFF"; + pub const EDITOR_BACKGROUND: &str = "#1E1F22"; + pub const EDITOR_TEXT: &str = "#acaea3"; + pub const EDITOR_HIGHLIGHTED_TEXT: &str = "#ccced3"; + pub const EDITOR_HIGHLIGHT: &str = "#ccced3"; + pub const GUTTER: &str = "#1e1f22"; + pub const EXPLORER_HOVERED: &str = "#4c5053"; + pub const EXPLORER_TEXT: &str = "#FFF"; + pub const EXPLORER_TEXT_SELECTED: &str = "#262626"; + pub const EXPLORER_BACKGROUND: &str = "#1E1F22"; + pub const EXPLORER_FOLDER: &str = "#54585b"; + pub const EXPLORER_FOLDER_OPEN: &str = "#393B40"; + pub const TERMINAL_BACKGROUND: &str = "#111111"; + pub const INFO_LOG: &str = "#C4FFFF"; + pub const DEBUG_LOG: &str = "#9148AF"; + pub const WARN_LOG: &str = "#C4A958"; + pub const ERROR_LOG: &str = "#ff5555"; + pub const TRACE_LOG: &str = "#ffaa00"; + + /// Converts a CSS hex color string (e.g., "#RRGGBB" or "#RGB") to u32 in 0x00RRGGBB format. + pub fn css_to_u32(css: &str) -> u32 { + let hex = css.trim_start_matches('#'); + // Expand shorthand #RGB to #RRGGBB + let hex_full = match hex.len() { + 3 => hex + .chars() + .map(|c| std::iter::repeat(c).take(2).collect::()) + .collect::(), + 6 => hex.to_string(), + _ => panic!("Invalid hex color length: {hex:?}"), + }; + // Parse the hex string as u32, masking to ensure the top (alpha) byte is 0x00. + u32::from_str_radix(&hex_full, 16) + .map(|rgb| rgb & 0x00FF_FFFF) + .unwrap_or_else(|e| panic!("Failed to parse hex: {e:?}")) + } +} diff --git a/src/gui/colors.rs b/src/gui/colors.rs index 9fb5ab3..8500527 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -2,8 +2,10 @@ // // SPDX-License-Identifier: GNU General Public License v3.0 or later -#[cxx_qt::bridge] +use cxx_qt_lib::QColor; +use libclide::theme::colors::Colors; +#[cxx_qt::bridge] pub mod qobject { unsafe extern "C++" { include!("cxx-qt-lib/qcolor.h"); @@ -46,8 +48,6 @@ pub mod qobject { } } -use cxx_qt_lib::QColor; - pub struct RustColorsImpl { hovered: QColor, unhovered: QColor, @@ -82,34 +82,34 @@ pub struct RustColorsImpl { impl Default for RustColorsImpl { fn default() -> Self { Self { - hovered: QColor::try_from("#303234").unwrap(), - unhovered: QColor::try_from("#3c3f41").unwrap(), - pressed: QColor::try_from("#4b4f51").unwrap(), - menubar: QColor::try_from("#262626").unwrap(), - menubar_border: QColor::try_from("#575757").unwrap(), - scrollbar: QColor::try_from("#4b4f51").unwrap(), - scrollbar_active: QColor::try_from("#4b4f51").unwrap(), - scrollbar_gutter: QColor::try_from("#3b3b3b").unwrap(), - linenumber: QColor::try_from("#94989b").unwrap(), - active: QColor::try_from("#a9acb0").unwrap(), - inactive: QColor::try_from("#FFF").unwrap(), - editor_background: QColor::try_from("#1E1F22").unwrap(), - editor_text: QColor::try_from("#acaea3").unwrap(), - editor_highlighted_text: QColor::try_from("#ccced3").unwrap(), - editor_highlight: QColor::try_from("#ccced3").unwrap(), - gutter: QColor::try_from("#1e1f22").unwrap(), - explorer_hovered: QColor::try_from("#4c5053").unwrap(), - explorer_text: QColor::try_from("#FFF").unwrap(), - explorer_text_selected: QColor::try_from("#262626").unwrap(), - explorer_background: QColor::try_from("#1E1F22").unwrap(), - explorer_folder: QColor::try_from("#54585b").unwrap(), - explorer_folder_open: QColor::try_from("#393B40").unwrap(), - terminal_background: QColor::try_from("#111111").unwrap(), - info_log: QColor::try_from("#C4FFFF").unwrap(), - debug_log: QColor::try_from("#9148AF").unwrap(), - warn_log: QColor::try_from("#C4A958").unwrap(), - error_log: QColor::try_from("#ff5555").unwrap(), - trace_log: QColor::try_from("#ffaa00").unwrap(), + hovered: QColor::try_from(Colors::HOVERED).unwrap(), + unhovered: QColor::try_from(Colors::UNHOVERED).unwrap(), + pressed: QColor::try_from(Colors::PRESSED).unwrap(), + menubar: QColor::try_from(Colors::MENUBAR).unwrap(), + menubar_border: QColor::try_from(Colors::MENUBAR_BORDER).unwrap(), + scrollbar: QColor::try_from(Colors::SCROLLBAR).unwrap(), + scrollbar_active: QColor::try_from(Colors::SCROLLBAR_ACTIVE).unwrap(), + scrollbar_gutter: QColor::try_from(Colors::SCROLLBAR_GUTTER).unwrap(), + linenumber: QColor::try_from(Colors::LINENUMBER).unwrap(), + active: QColor::try_from(Colors::ACTIVE).unwrap(), + inactive: QColor::try_from(Colors::INACTIVE).unwrap(), + editor_background: QColor::try_from(Colors::EDITOR_BACKGROUND).unwrap(), + editor_text: QColor::try_from(Colors::EDITOR_TEXT).unwrap(), + editor_highlighted_text: QColor::try_from(Colors::EDITOR_HIGHLIGHTED_TEXT).unwrap(), + editor_highlight: QColor::try_from(Colors::EDITOR_HIGHLIGHT).unwrap(), + gutter: QColor::try_from(Colors::GUTTER).unwrap(), + explorer_hovered: QColor::try_from(Colors::EXPLORER_HOVERED).unwrap(), + explorer_text: QColor::try_from(Colors::EXPLORER_TEXT).unwrap(), + explorer_text_selected: QColor::try_from(Colors::EXPLORER_TEXT_SELECTED).unwrap(), + explorer_background: QColor::try_from(Colors::EXPLORER_BACKGROUND).unwrap(), + explorer_folder: QColor::try_from(Colors::EXPLORER_FOLDER).unwrap(), + explorer_folder_open: QColor::try_from(Colors::EXPLORER_FOLDER_OPEN).unwrap(), + terminal_background: QColor::try_from(Colors::TERMINAL_BACKGROUND).unwrap(), + info_log: QColor::try_from(Colors::INFO_LOG).unwrap(), + debug_log: QColor::try_from(Colors::DEBUG_LOG).unwrap(), + warn_log: QColor::try_from(Colors::WARN_LOG).unwrap(), + error_log: QColor::try_from(Colors::ERROR_LOG).unwrap(), + trace_log: QColor::try_from(Colors::TRACE_LOG).unwrap(), } } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 27b3999..ee6e844 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -7,6 +7,7 @@ use crate::tui::component::Focus::Inactive; use Focus::Active; use anyhow::Result; +use libclide::theme::colors::Colors; use log::trace; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; use ratatui::style::Color; @@ -98,8 +99,8 @@ pub enum Focus { impl Focus { pub(crate) fn get_active_color(&self) -> Color { match self { - Active => Color::LightYellow, - Inactive => Color::White, + Active => Color::from_u32(Colors::css_to_u32(Colors::ACTIVE)), + Inactive => Color::from_u32(Colors::css_to_u32(Colors::INACTIVE)), } } } -- 2.49.1 From be969ef3359eba48527ec0f6865c3bd2596541e6 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 22 Feb 2026 10:09:53 -0500 Subject: [PATCH 15/19] Add macros for logging. --- .github/workflows/check.yaml | 12 ++++ Cargo.lock | 11 ++++ Cargo.toml | 11 ++-- libclide-macros/Cargo.toml | 12 ++++ libclide-macros/src/lib.rs | 27 +++++++++ libclide/Cargo.toml | 1 + libclide/src/lib.rs | 1 + libclide/src/log.rs | 5 ++ libclide/src/log/macros.rs | 106 +++++++++++++++++++++++++++++++++++ libclide/src/theme/colors.rs | 2 +- src/gui.rs | 3 +- src/gui/filesystem.rs | 3 +- src/tui.rs | 18 +++--- src/tui/about.rs | 7 +-- src/tui/app.rs | 29 +++++----- src/tui/component.rs | 5 +- src/tui/editor.rs | 17 +++--- src/tui/editor_tab.rs | 31 +++++----- src/tui/explorer.rs | 7 +-- src/tui/logger.rs | 8 +-- src/tui/menu_bar.rs | 9 ++- 21 files changed, 246 insertions(+), 79 deletions(-) create mode 100644 libclide-macros/Cargo.toml create mode 100644 libclide-macros/src/lib.rs create mode 100644 libclide/src/log.rs create mode 100644 libclide/src/log/macros.rs diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 958fd3c..dbd9991 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -26,6 +26,9 @@ jobs: - name: Build libclide run: | cargo b -p libclide --release + - name: Build libclide-macros + run: | + cargo b -p libclide-macros --release - name: Build clide run: | cargo b --release @@ -44,6 +47,9 @@ jobs: - name: Test libclide run: | cargo test -p libclide + - name: Test libclide-macros + run: | + cargo test -p libclide-macros - name: Test clide run: | cargo test @@ -62,6 +68,9 @@ jobs: - name: Lint libclide run: | cargo clippy -p libclide -- -D warnings + - name: Lint libclide-macros + run: | + cargo clippy -p libclide-macros -- -D warnings - name: Lint clide run: | cargo clippy -- -D warnings @@ -80,6 +89,9 @@ jobs: - name: Format libclide run: | cargo fmt -p libclide --verbose -- --check + - name: Format libclide-macros + run: | + cargo fmt -p libclide-macros -- --check - name: Format clide run: | cargo fmt --verbose -- --check diff --git a/Cargo.lock b/Cargo.lock index ac98bd2..fecb64b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,7 @@ dependencies = [ "dirs", "edtui", "libclide", + "libclide-macros", "log", "ratatui", "strum", @@ -1169,9 +1170,19 @@ name = "libclide" version = "0.1.0" dependencies = [ "anyhow", + "log", "strum", ] +[[package]] +name = "libclide-macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "libredox" version = "0.1.12" diff --git a/Cargo.toml b/Cargo.toml index 6176c2d..f07c71a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,20 +5,17 @@ edition = "2024" [workspace] resolver = "3" -members = [ - ".", - "libclide", -] +members = [".", "libclide", "libclide-macros", ] [workspace.dependencies] anyhow = "1.0.100" strum = "0.27.2" +log = { version = "0.4.27", features = [] } [dependencies] cxx = "1.0.95" cxx-qt = "0.8.0" cxx-qt-lib = { version = "0.8.0", features = ["qt_full", "qt_gui", "qt_qml"] } -log = { version = "0.4.27", features = [] } dirs = "6.0.0" syntect = "5.2.0" clap = { version = "4.5.54", features = ["derive"] } @@ -28,9 +25,11 @@ tui-logger = "0.18.1" edtui = "0.11.1" devicons = "0.6.12" libclide = { path = "./libclide" } +libclide-macros = { path = "./libclide-macros" } anyhow = { workspace = true } strum = { workspace = true } +log = { workspace = true } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. -cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } \ No newline at end of file +cxx-qt-build = { version = "0.8.0", features = ["link_qt_object_files"] } diff --git a/libclide-macros/Cargo.toml b/libclide-macros/Cargo.toml new file mode 100644 index 0000000..bf6d860 --- /dev/null +++ b/libclide-macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "libclide-macros" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" \ No newline at end of file diff --git a/libclide-macros/src/lib.rs b/libclide-macros/src/lib.rs new file mode 100644 index 0000000..9f69c0b --- /dev/null +++ b/libclide-macros/src/lib.rs @@ -0,0 +1,27 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{ItemStruct, parse_macro_input}; + +#[proc_macro_attribute] +pub fn log_id(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as ItemStruct); + + let struct_name = &input.ident; + let generics = &input.generics; + + // This is the important part + let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); + + let struct_name_str = struct_name.to_string(); + + let expanded = quote! { + #input + + impl #impl_generics #struct_name #type_generics #where_clause { + #[allow(unused)] + pub const ID: &'static str = #struct_name_str; + } + }; + + TokenStream::from(expanded) +} diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index 33434a0..7c037f0 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -6,3 +6,4 @@ edition = "2024" [dependencies] anyhow = { workspace = true } strum = { workspace = true } +log = { workspace = true } diff --git a/libclide/src/lib.rs b/libclide/src/lib.rs index baf3411..400e3b7 100644 --- a/libclide/src/lib.rs +++ b/libclide/src/lib.rs @@ -3,4 +3,5 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later pub mod fs; +pub mod log; pub mod theme; diff --git a/libclide/src/log.rs b/libclide/src/log.rs new file mode 100644 index 0000000..dbb82f2 --- /dev/null +++ b/libclide/src/log.rs @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + +pub mod macros; diff --git a/libclide/src/log/macros.rs b/libclide/src/log/macros.rs new file mode 100644 index 0000000..50baf84 --- /dev/null +++ b/libclide/src/log/macros.rs @@ -0,0 +1,106 @@ +//! Logging targets allow filtering of log messages by their source. By default, the log crate sets +//! the target to the module path where the log macro was invoked if no target is provided. +//! +//! These macros essentially disable using the default target and instead require the target to be +//! explicitly set. This is to avoid implicit pooling of log messages under the same default target, +//! which can make it difficult to filter log messages by their source. + +#[macro_export] +macro_rules! info { + // The target argument can be overridden using one of the following macros. + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + log::info!(logger: $logger, target: $target, $($arg)+) + }); + + (target: $target:expr, $($arg:tt)+) => ({ + log::info!(target: $target, $($arg)+) + }); + + // The target argument will default to Self::ID if not provided. + // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. + (logger: $logger:expr, $($arg:tt)+) => ({ + log::info!(logger: $logger, target: Self::ID, $($arg)+) + }); + + ($($arg:tt)+) => (log::info!(target: Self::ID, $($arg)+)) +} + +#[macro_export] +macro_rules! debug { + // The target argument can be overridden using one of the following macros. + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + log::debug!(logger: $logger, target: $target, $($arg)+) + }); + + (target: $target:expr, $($arg:tt)+) => ({ + log::debug!(target: $target, $($arg)+) + }); + + // The target argument will default to Self::ID if not provided. + // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. + (logger: $logger:expr, $($arg:tt)+) => ({ + log::debug!(logger: $logger, target: Self::ID, $($arg)+) + }); + + ($($arg:tt)+) => (log::debug!(target: Self::ID, $($arg)+)) +} + +#[macro_export] +macro_rules! warn { + // The target argument can be overridden using one of the following macros. + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + log::warn!(logger: $logger, target: $target, $($arg)+) + }); + + (target: $target:expr, $($arg:tt)+) => ({ + log::warn!(target: $target, $($arg)+) + }); + + // The target argument will default to Self::ID if not provided. + // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. + (logger: $logger:expr, $($arg:tt)+) => ({ + log::warn!(logger: $logger, target: Self::ID, $($arg)+) + }); + + ($($arg:tt)+) => (log::warn!(target: Self::ID, $($arg)+)) +} + +#[macro_export] +macro_rules! error { + // The target argument can be overridden using one of the following macros. + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + log::error!(logger: $logger, target: $target, $($arg)+) + }); + + (target: $target:expr, $($arg:tt)+) => ({ + log::error!(target: $target, $($arg)+) + }); + + // The target argument will default to Self::ID if not provided. + // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. + (logger: $logger:expr, $($arg:tt)+) => ({ + log::error!(logger: $logger, target: Self::ID, $($arg)+) + }); + + ($($arg:tt)+) => (log::error!(target: Self::ID, $($arg)+)) +} + +#[macro_export] +macro_rules! trace { + // The target argument can be overridden using one of the following macros. + (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ + log::trace!(logger: $logger, target: $target, $($arg)+) + }); + + (target: $target:expr, $($arg:tt)+) => ({ + log::trace!(target: $target, $($arg)+) + }); + + // The target argument will default to Self::ID if not provided. + // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. + (logger: $logger:expr, $($arg:tt)+) => ({ + log::trace!(logger: $logger, target: Self::ID, $($arg)+) + }); + + ($($arg:tt)+) => (log::trace!(target: Self::ID, $($arg)+)) +} diff --git a/libclide/src/theme/colors.rs b/libclide/src/theme/colors.rs index 4f52bc1..c019f15 100644 --- a/libclide/src/theme/colors.rs +++ b/libclide/src/theme/colors.rs @@ -41,7 +41,7 @@ impl Colors { let hex_full = match hex.len() { 3 => hex .chars() - .map(|c| std::iter::repeat(c).take(2).collect::()) + .map(|c| std::iter::repeat_n(c, 2).collect::()) .collect::(), 6 => hex.to_string(), _ => panic!("Invalid hex color length: {hex:?}"), diff --git a/src/gui.rs b/src/gui.rs index 48b1e0b..9ca92c7 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -5,13 +5,12 @@ use crate::AppContext; use anyhow::Result; use cxx_qt_lib::{QMapPair, QMapPair_QString_QVariant, QString, QVariant}; -use log::trace; pub mod colors; pub mod filesystem; pub fn run(app_context: AppContext) -> Result<()> { - trace!(target:"gui::run()", "Starting the GUI editor at {:?}", app_context.path); + libclide::trace!(target:"gui::run()", "Starting the GUI editor at {:?}", app_context.path); use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QUrl}; diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 4f64599..12dc077 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -5,7 +5,6 @@ use cxx_qt_lib::{QModelIndex, QString}; use devicons::FileIcon; use dirs; -use log::warn; use std::fs; use std::path::Path; use syntect::easy::HighlightLines; @@ -78,7 +77,7 @@ impl qobject::FileSystem { let meta = fs::metadata(path.to_string()) .unwrap_or_else(|_| panic!("Failed to get file metadata {path:?}")); if !meta.is_file() { - warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); + libclide::warn!(target:"FileSystem", "Attempted to open file {path:?} that is not a valid file"); return QString::default(); } let path_str = path.to_string(); diff --git a/src/tui.rs b/src/tui.rs index 2652ca7..88a55d7 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -13,7 +13,8 @@ mod menu_bar; use crate::AppContext; use anyhow::{Context, Result}; -use log::{LevelFilter, debug, info, trace}; +use libclide_macros::log_id; +use log::LevelFilter; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::crossterm::event::{ @@ -28,24 +29,23 @@ use tui_logger::{ TuiLoggerFile, TuiLoggerLevelOutput, init_logger, set_default_level, set_log_file, }; +#[log_id] struct Tui { terminal: Terminal>, root_path: std::path::PathBuf, } pub fn run(app_context: AppContext) -> Result<()> { - trace!(target:Tui::ID, "Starting TUI"); + libclide::trace!(target:Tui::ID, "Starting TUI"); Tui::new(app_context)?.start() } impl Tui { - pub const ID: &str = "Tui"; - fn new(app_context: AppContext) -> Result { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", Self::ID); init_logger(LevelFilter::Trace)?; set_default_level(LevelFilter::Trace); - debug!(target:Self::ID, "Logging initialized"); + libclide::debug!("Logging initialized"); let mut dir = env::temp_dir(); dir.push("clide.log"); @@ -57,7 +57,7 @@ impl Tui { .output_file(false) .output_separator(':'); set_log_file(file_options); - debug!(target:Self::ID, "Logging to file: {dir:?}"); + libclide::debug!("Logging to file: {dir:?}"); Ok(Self { terminal: Terminal::new(CrosstermBackend::new(stdout()))?, @@ -66,7 +66,7 @@ impl Tui { } fn start(self) -> Result<()> { - info!(target:Self::ID, "Starting the TUI editor at {:?}", self.root_path); + libclide::info!("Starting the TUI editor at {:?}", self.root_path); ratatui::crossterm::execute!( stdout(), EnterAlternateScreen, @@ -83,7 +83,7 @@ impl Tui { } fn stop() -> Result<()> { - info!(target:Self::ID, "Stopping the TUI editor"); + libclide::info!("Stopping the TUI editor"); disable_raw_mode()?; ratatui::crossterm::execute!( stdout(), diff --git a/src/tui/about.rs b/src/tui/about.rs index 878d6d8..f5334f4 100644 --- a/src/tui/about.rs +++ b/src/tui/about.rs @@ -2,19 +2,18 @@ // // SPDX-License-Identifier: GNU General Public License v3.0 or later +use libclide_macros::log_id; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph, Widget, Wrap}; +#[log_id] pub struct About {} impl About { - #[allow(unused)] - pub const ID: &str = "About"; - pub fn new() -> Self { - // trace!(target:Self::id(), "Building {}", Self::id()); + // libclide::trace!("Building {}", Self::id()); Self {} } } diff --git a/src/tui/app.rs b/src/tui/app.rs index 190420c..3e02c82 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -9,7 +9,7 @@ use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; use anyhow::{Context, Result}; -use log::{error, info, trace}; +use libclide_macros::log_id; use ratatui::DefaultTerminal; use ratatui::buffer::Buffer; use ratatui::crossterm::event; @@ -30,6 +30,7 @@ pub enum AppComponent { MenuBar, } +#[log_id] pub struct App<'a> { editor_tab: EditorTab, explorer: Explorer<'a>, @@ -40,10 +41,8 @@ pub struct App<'a> { } impl<'a> App<'a> { - pub const ID: &'static str = "App"; - pub fn new(root_path: PathBuf) -> Result { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!(target:Self::ID, "Building {}", Self::ID); let app = Self { editor_tab: EditorTab::new(), explorer: Explorer::new(&root_path)?, @@ -57,13 +56,13 @@ impl<'a> App<'a> { /// Logic that should be executed once on application startup. pub fn start(&mut self) -> Result<()> { - trace!(target:Self::ID, "Starting App"); + libclide::trace!(target:Self::ID, "Starting App"); Ok(()) } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { self.start()?; - trace!(target:Self::ID, "Entering App run loop"); + libclide::trace!(target:Self::ID, "Entering App run loop"); loop { terminal.draw(|f| { f.render_widget(&mut self, f.area()); @@ -89,7 +88,7 @@ impl<'a> App<'a> { Some(editor) => editor.component_state.help_text.clone(), None => { if !self.editor_tab.is_empty() { - error!(target:Self::ID, "Failed to get Editor while drawing bottom status bar"); + libclide::error!(target:Self::ID, "Failed to get Editor while drawing bottom status bar"); } "Failed to get current Editor while getting widget help text".to_string() } @@ -113,26 +112,26 @@ impl<'a> App<'a> { } fn clear_focus(&mut self) { - info!(target:Self::ID, "Clearing all widget focus"); + libclide::info!(target:Self::ID, "Clearing all widget focus"); self.explorer.component_state.set_focus(Focus::Inactive); self.explorer.component_state.set_focus(Focus::Inactive); self.logger.component_state.set_focus(Focus::Inactive); self.menu_bar.component_state.set_focus(Focus::Inactive); match self.editor_tab.current_editor_mut() { None => { - error!(target:Self::ID, "Failed to get current Editor while clearing focus") + libclide::error!(target:Self::ID, "Failed to get current Editor while clearing focus") } Some(editor) => editor.component_state.set_focus(Focus::Inactive), } } fn change_focus(&mut self, focus: AppComponent) { - info!(target:Self::ID, "Changing widget focus to {:?}", focus); + libclide::info!(target:Self::ID, "Changing widget focus to {:?}", focus); self.clear_focus(); match focus { AppComponent::Editor => match self.editor_tab.current_editor_mut() { None => { - error!(target:Self::ID, "Failed to get current Editor while changing focus") + libclide::error!(target:Self::ID, "Failed to get current Editor while changing focus") } Some(editor) => editor.component_state.set_focus(Focus::Active), }, @@ -275,13 +274,13 @@ impl<'a> Component for App<'a> { Action::Quit | Action::Handled => Ok(action), Action::Save => match self.editor_tab.current_editor_mut() { None => { - error!(target:Self::ID, "Failed to get current editor while handling App Action::Save"); + libclide::error!(target:Self::ID, "Failed to get current editor while handling App Action::Save"); Ok(Action::Noop) } Some(editor) => match editor.save() { Ok(_) => Ok(Action::Handled), Err(e) => { - error!(target:Self::ID, "Failed to save editor contents: {e}"); + libclide::error!(target:Self::ID, "Failed to save editor contents: {e}"); Ok(Action::Noop) } }, @@ -300,14 +299,14 @@ impl<'a> Component for App<'a> { Err(_) => Ok(Action::Noop), }, Action::ReloadFile => { - trace!(target:Self::ID, "Reloading file for current editor"); + libclide::trace!(target:Self::ID, "Reloading file for current editor"); if let Some(editor) = self.editor_tab.current_editor_mut() { editor .reload_contents() .map(|_| Action::Handled) .context("Failed to handle Action::ReloadFile") } else { - error!(target:Self::ID, "Failed to get current editor while handling App Action::ReloadFile"); + libclide::error!(target:Self::ID, "Failed to get current editor while handling App Action::ReloadFile"); Ok(Action::Noop) } } diff --git a/src/tui/component.rs b/src/tui/component.rs index ee6e844..30ffd47 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -8,7 +8,7 @@ use crate::tui::component::Focus::Inactive; use Focus::Active; use anyhow::Result; use libclide::theme::colors::Colors; -use log::trace; +use libclide_macros::log_id; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; use ratatui::style::Color; @@ -63,6 +63,7 @@ pub trait Component { } #[derive(Debug, Clone, Default)] +#[log_id] pub struct ComponentState { pub(crate) focus: Focus, pub(crate) vis: Visibility, @@ -75,7 +76,7 @@ impl ComponentState { } fn new() -> Self { - trace!(target:Self::id(), "Building {}", Self::id()); + libclide::trace!(target:Self::id(), "Building {}", Self::id()); Self { focus: Active, vis: Visibility::Visible, diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 99c07b6..42adcb1 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; -use log::{error, trace}; +use libclide_macros::log_id; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; @@ -16,6 +16,7 @@ use ratatui::widgets::{Block, Borders, Padding, Widget}; use std::path::PathBuf; use syntect::parsing::SyntaxSet; +#[log_id] pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, @@ -25,10 +26,8 @@ pub struct Editor { } impl Editor { - pub const ID: &str = "Editor"; - pub fn new(path: &std::path::Path) -> Self { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!(target:Self::ID, "Building {}", Self::ID); Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), @@ -42,10 +41,10 @@ impl Editor { } pub fn reload_contents(&mut self) -> Result<()> { - trace!(target:Self::ID, "Reloading editor file contents {:?}", self.file_path); + libclide::trace!(target:Self::ID, "Reloading editor file contents {:?}", self.file_path); match self.file_path.clone() { None => { - error!(target:Self::ID, "Failed to reload editor contents with None file_path"); + libclide::error!(target:Self::ID, "Failed to reload editor contents with None file_path"); bail!("Failed to reload editor contents with None file_path") } Some(path) => self.set_contents(&path), @@ -53,7 +52,7 @@ impl Editor { } pub fn set_contents(&mut self, path: &std::path::Path) -> Result<()> { - trace!(target:Self::ID, "Setting Editor contents from path {:?}", path); + libclide::trace!(target:Self::ID, "Setting Editor contents from path {:?}", path); if let Ok(contents) = std::fs::read_to_string(path) { let lines: Vec<_> = contents .lines() @@ -69,10 +68,10 @@ impl Editor { pub fn save(&self) -> Result<()> { if let Some(path) = &self.file_path { - trace!(target:Self::ID, "Saving Editor contents {:?}", path); + libclide::trace!(target:Self::ID, "Saving Editor contents {:?}", path); return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); }; - error!(target:Self::ID, "Failed saving Editor contents; file_path was None"); + libclide::error!(target:Self::ID, "Failed saving Editor contents; file_path was None"); bail!("File not saved. No file path set.") } } diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index 21d8112..ec7da9e 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -5,7 +5,7 @@ use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use anyhow::{Context, Result, anyhow}; -use log::{error, info, trace, warn}; +use libclide_macros::log_id; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::Rect; @@ -16,6 +16,7 @@ use std::collections::HashMap; // Render the tabs with keys as titles // Tab keys can be file names. // Render the editor using the key as a reference for lookup +#[log_id] pub struct EditorTab { pub(crate) editors: HashMap, tab_order: Vec, @@ -23,10 +24,8 @@ pub struct EditorTab { } impl EditorTab { - pub const ID: &str = "EditorTab"; - pub fn new() -> Self { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!(target:Self::ID, "Building {}", Self::ID); Self { editors: HashMap::new(), tab_order: Vec::new(), @@ -36,7 +35,7 @@ impl EditorTab { pub fn next_editor(&mut self) { let next = (self.current_editor + 1) % self.tab_order.len(); - trace!(target:Self::ID, "Moving from {} to next editor tab at {}", self.current_editor, next); + libclide::trace!(target:Self::ID, "Moving from {} to next editor tab at {}", self.current_editor, next); self.set_tab_focus(Focus::Active, next); self.current_editor = next; } @@ -46,7 +45,7 @@ impl EditorTab { .current_editor .checked_sub(1) .unwrap_or(self.tab_order.len() - 1); - trace!(target:Self::ID, "Moving from {} to previous editor tab at {}", self.current_editor, prev); + libclide::trace!(target:Self::ID, "Moving from {} to previous editor tab at {}", self.current_editor, prev); self.set_tab_focus(Focus::Active, prev); self.current_editor = prev; } @@ -55,7 +54,7 @@ impl EditorTab { match self.tab_order.get(index) { None => { if !self.tab_order.is_empty() { - error!(target:Self::ID, "Failed to get editor tab key with invalid index {index}"); + libclide::error!(target:Self::ID, "Failed to get editor tab key with invalid index {index}"); } None } @@ -73,15 +72,15 @@ impl EditorTab { } pub fn set_current_tab_focus(&mut self, focus: Focus) { - trace!(target:Self::ID, "Setting current tab {} focus to {:?}", self.current_editor, focus); + libclide::trace!(target:Self::ID, "Setting current tab {} focus to {:?}", self.current_editor, focus); self.set_tab_focus(focus, self.current_editor) } pub fn set_tab_focus(&mut self, focus: Focus, index: usize) { - trace!(target:Self::ID, "Setting tab {} focus to {:?}", index, focus); + libclide::trace!(target:Self::ID, "Setting tab {} focus to {:?}", index, focus); if focus == Focus::Active && index != self.current_editor { // If we are setting another tab to active, disable the current one. - trace!( + libclide::trace!( target:Self::ID, "New tab {} focus set to Active; Setting current tab {} to Inactive", index, @@ -91,11 +90,11 @@ impl EditorTab { } match self.get_editor_key(index) { None => { - error!(target:Self::ID, "Failed setting tab focus for invalid key {index}"); + libclide::error!(target:Self::ID, "Failed setting tab focus for invalid key {index}"); } Some(key) => match self.editors.get_mut(&key) { None => { - error!( + libclide::error!( target:Self::ID, "Failed to update tab focus at index {} with invalid key: {}", self.current_editor, @@ -108,12 +107,12 @@ impl EditorTab { } pub fn open_tab(&mut self, path: &std::path::Path) -> Result<()> { - trace!(target:Self::ID, "Opening new EditorTab with path {:?}", path); + libclide::trace!(target:Self::ID, "Opening new EditorTab with path {:?}", path); if self .editors .contains_key(&path.to_string_lossy().to_string()) { - warn!(target:Self::ID, "EditorTab already opened with this file"); + libclide::warn!(target:Self::ID, "EditorTab already opened with this file"); return Ok(()); } @@ -138,12 +137,12 @@ impl EditorTab { .to_owned(); match self.editors.remove(&key) { None => { - error!(target:Self::ID, "Failed to remove editor tab {key} with invalid index {index}") + libclide::error!(target:Self::ID, "Failed to remove editor tab {key} with invalid index {index}") } Some(_) => { self.prev_editor(); self.tab_order.remove(index); - info!(target:Self::ID, "Closed editor tab {key} at index {index}") + libclide::info!(target:Self::ID, "Closed editor tab {key} at index {index}") } } Ok(()) diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 9ac817e..4bb63f0 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -5,7 +5,7 @@ use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use libclide::fs::entry_meta::EntryMeta; -use log::trace; +use libclide_macros::log_id; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -17,6 +17,7 @@ use std::path::{Path, PathBuf}; use tui_tree_widget::{Tree, TreeItem, TreeState}; #[derive(Debug)] +#[log_id] pub struct Explorer<'a> { root_path: EntryMeta, tree_items: TreeItem<'a, String>, @@ -25,10 +26,8 @@ pub struct Explorer<'a> { } impl<'a> Explorer<'a> { - pub const ID: &'static str = "Explorer"; - pub fn new(path: &PathBuf) -> Result { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", Self::ID); let explorer = Explorer { root_path: EntryMeta::new(path)?, tree_items: Self::build_tree_from_path(path)?, diff --git a/src/tui/logger.rs b/src/tui/logger.rs index d88cf68..122d5fc 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; -use log::{LevelFilter, trace}; +use libclide_macros::log_id; +use log::LevelFilter; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -13,16 +14,15 @@ use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget, TuiWidgetEvent, Tui /// Any log written as info!(target:self.id(), "message") will work with this logger. /// The logger is bound to info!, debug!, error!, trace! macros within Tui::new(). +#[log_id] pub struct Logger { state: TuiWidgetState, pub(crate) component_state: ComponentState, } impl Logger { - pub const ID: &str = "Logger"; - pub fn new() -> Self { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!(target:Self::ID, "Building {}", Self::ID); let state = TuiWidgetState::new(); state.transition(TuiWidgetEvent::HideKey); Self { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index f872584..a197188 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -7,7 +7,7 @@ use crate::tui::menu_bar::MenuBarItemOption::{ About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; use anyhow::Context; -use log::trace; +use libclide_macros::log_id; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -80,6 +80,7 @@ impl MenuBarItem { } } +#[log_id] pub struct MenuBar { selected: MenuBarItem, opened: Option, @@ -88,11 +89,9 @@ pub struct MenuBar { } impl MenuBar { - pub const ID: &str = "MenuBar"; - const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection"; pub fn new() -> Self { - trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", Self::ID); Self { selected: MenuBarItem::File, opened: None, @@ -157,7 +156,7 @@ impl MenuBar { height, } // TODO: X offset for item option? It's fine as-is, but it might look nicer. - // trace!(target:Self::ID, "Building Rect under MenuBar popup {}", rect); + // trace!("Building Rect under MenuBar popup {}", rect); } } -- 2.49.1 From 5b356781badd61f2d76653a9ef00b287143af4be Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 22 Feb 2026 10:25:00 -0500 Subject: [PATCH 16/19] Fix comments. --- libclide-macros/src/lib.rs | 4 ++++ libclide/src/log/macros.rs | 43 +++++++++++++++++++++++++------------- src/main.rs | 5 ++--- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/libclide-macros/src/lib.rs b/libclide-macros/src/lib.rs index 9f69c0b..03d4fda 100644 --- a/libclide-macros/src/lib.rs +++ b/libclide-macros/src/lib.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + use proc_macro::TokenStream; use quote::quote; use syn::{ItemStruct, parse_macro_input}; diff --git a/libclide/src/log/macros.rs b/libclide/src/log/macros.rs index 50baf84..ab2827e 100644 --- a/libclide/src/log/macros.rs +++ b/libclide/src/log/macros.rs @@ -1,13 +1,40 @@ +// SPDX-FileCopyrightText: 2026, Shaun Reed +// +// SPDX-License-Identifier: GNU General Public License v3.0 or later + //! Logging targets allow filtering of log messages by their source. By default, the log crate sets //! the target to the module path where the log macro was invoked if no target is provided. //! //! These macros essentially disable using the default target and instead require the target to be //! explicitly set. This is to avoid implicit pooling of log messages under the same default target, //! which can make it difficult to filter log messages by their source. +//! +//! The target argument can be overridden using one of the following macros. +//! ``` +//! libclide::log!(target: "CustomTarget", "This log message will have the target 'CustomTarget'"); +//! ``` +//! +//! The target argument will default to Self::ID if not provided. +//! This is an error if Self::ID is not defined, forcing you to use the explicit form. +//! ``` +//! libclide::log!("This log message will use target Self::ID, the name of the struct it was invoked in"); +//! ``` +//! +//! Self::ID can be defined using the `#[log_id]` attribute macro, which will automatically generate +//! a constant ID field with the name of the struct as its value. +//! ``` +//! #[log_id] +//! struct MyStruct; +//! impl MyStruct { +//! fn my_method(&self) { +//! libclide::log!("This log message will use target Self::ID, which is 'MyStruct'"); +//! } +//! } +//! ``` +//! #[macro_export] macro_rules! info { - // The target argument can be overridden using one of the following macros. (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ log::info!(logger: $logger, target: $target, $($arg)+) }); @@ -16,8 +43,6 @@ macro_rules! info { log::info!(target: $target, $($arg)+) }); - // The target argument will default to Self::ID if not provided. - // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. (logger: $logger:expr, $($arg:tt)+) => ({ log::info!(logger: $logger, target: Self::ID, $($arg)+) }); @@ -27,7 +52,6 @@ macro_rules! info { #[macro_export] macro_rules! debug { - // The target argument can be overridden using one of the following macros. (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ log::debug!(logger: $logger, target: $target, $($arg)+) }); @@ -36,8 +60,6 @@ macro_rules! debug { log::debug!(target: $target, $($arg)+) }); - // The target argument will default to Self::ID if not provided. - // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. (logger: $logger:expr, $($arg:tt)+) => ({ log::debug!(logger: $logger, target: Self::ID, $($arg)+) }); @@ -47,7 +69,6 @@ macro_rules! debug { #[macro_export] macro_rules! warn { - // The target argument can be overridden using one of the following macros. (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ log::warn!(logger: $logger, target: $target, $($arg)+) }); @@ -56,8 +77,6 @@ macro_rules! warn { log::warn!(target: $target, $($arg)+) }); - // The target argument will default to Self::ID if not provided. - // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. (logger: $logger:expr, $($arg:tt)+) => ({ log::warn!(logger: $logger, target: Self::ID, $($arg)+) }); @@ -67,7 +86,6 @@ macro_rules! warn { #[macro_export] macro_rules! error { - // The target argument can be overridden using one of the following macros. (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ log::error!(logger: $logger, target: $target, $($arg)+) }); @@ -76,8 +94,6 @@ macro_rules! error { log::error!(target: $target, $($arg)+) }); - // The target argument will default to Self::ID if not provided. - // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. (logger: $logger:expr, $($arg:tt)+) => ({ log::error!(logger: $logger, target: Self::ID, $($arg)+) }); @@ -87,7 +103,6 @@ macro_rules! error { #[macro_export] macro_rules! trace { - // The target argument can be overridden using one of the following macros. (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ log::trace!(logger: $logger, target: $target, $($arg)+) }); @@ -96,8 +111,6 @@ macro_rules! trace { log::trace!(target: $target, $($arg)+) }); - // The target argument will default to Self::ID if not provided. - // Obviously, this is an error if Self::ID is not defined, forcing you to use the explicit form. (logger: $logger:expr, $($arg:tt)+) => ({ log::trace!(logger: $logger, target: Self::ID, $($arg)+) }); diff --git a/src/main.rs b/src/main.rs index 80ef06c..e8c3851 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ use anyhow::{Context, Result, anyhow}; use clap::Parser; -use log::{info, trace}; use std::process::{Command, Stdio}; pub mod gui; @@ -56,7 +55,7 @@ impl AppContext { // If no path was provided, use the current directory. None => std::env::current_dir().context("Failed to obtain current directory")?, }; - info!(target:"main()", "Root path detected: {path:?}"); + libclide::info!(target:"main()", "Root path detected: {path:?}"); Ok(Self { path, @@ -80,7 +79,7 @@ fn main() -> Result<()> { RunMode::GuiAttached => gui::run(app_context), RunMode::Tui => tui::run(app_context), RunMode::Gui => { - trace!(target:"main()", "Starting GUI in a new process"); + libclide::trace!(target:"main()", "Starting GUI in a new process"); Command::new(std::env::current_exe()?) .args(["--gui", app_context.path.to_str().unwrap()]) .stdout(Stdio::null()) -- 2.49.1 From 2e67c013771e4a27b9ea450daff22f7eee7c9729 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 22 Feb 2026 11:35:25 -0500 Subject: [PATCH 17/19] Add icons to TUI. --- Cargo.lock | 1 + Cargo.toml | 3 ++- libclide/Cargo.toml | 1 + libclide/src/fs.rs | 13 +++++++++++++ libclide/src/fs/entry_meta.rs | 4 ++++ src/gui/filesystem.rs | 10 +--------- src/tui/explorer.rs | 4 ++-- 7 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fecb64b..0ae1fc8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1170,6 +1170,7 @@ name = "libclide" version = "0.1.0" dependencies = [ "anyhow", + "devicons", "log", "strum", ] diff --git a/Cargo.toml b/Cargo.toml index f07c71a..2f18af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ members = [".", "libclide", "libclide-macros", ] anyhow = "1.0.100" strum = "0.27.2" log = { version = "0.4.27", features = [] } +devicons = "0.6.12" [dependencies] cxx = "1.0.95" @@ -23,12 +24,12 @@ ratatui = "0.30.0" tui-tree-widget = "0.24.0" tui-logger = "0.18.1" edtui = "0.11.1" -devicons = "0.6.12" libclide = { path = "./libclide" } libclide-macros = { path = "./libclide-macros" } anyhow = { workspace = true } strum = { workspace = true } log = { workspace = true } +devicons = { workspace = true } [build-dependencies] # The link_qt_object_files feature is required for statically linking Qt 6. diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index 7c037f0..e239386 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -7,3 +7,4 @@ edition = "2024" anyhow = { workspace = true } strum = { workspace = true } log = { workspace = true } +devicons = { workspace = true } diff --git a/libclide/src/fs.rs b/libclide/src/fs.rs index 04e84c0..484ea1e 100644 --- a/libclide/src/fs.rs +++ b/libclide/src/fs.rs @@ -3,3 +3,16 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later pub mod entry_meta; + +use devicons::FileIcon; +use std::path::Path; + +pub fn icon>(p: P) -> FileIcon { + let path = p.as_ref(); + if Path::new(&path).is_dir() { + // Ensures directories are given a folder icon and not mistakenly resolved to a language. + // For example, a directory named `cpp` would otherwise return a C++ icon. + return FileIcon::from("dir/"); + } + FileIcon::from(path) +} diff --git a/libclide/src/fs/entry_meta.rs b/libclide/src/fs/entry_meta.rs index 4d896d4..55749c9 100644 --- a/libclide/src/fs/entry_meta.rs +++ b/libclide/src/fs/entry_meta.rs @@ -3,6 +3,7 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later use anyhow::{Context, Result}; +use devicons::FileIcon; use std::path::{Path, PathBuf}; #[derive(Debug)] @@ -10,6 +11,7 @@ pub struct EntryMeta { pub abs_path: String, pub file_name: String, pub is_dir: bool, + pub icon: FileIcon, } impl EntryMeta { @@ -41,10 +43,12 @@ impl EntryMeta { .context(format!("Failed to get file name for path: {abs_path:?}"))? .to_string_lossy() .to_string(); + let icon = crate::fs::icon(&abs_path); Ok(EntryMeta { abs_path, file_name, is_dir, + icon, }) } } diff --git a/src/gui/filesystem.rs b/src/gui/filesystem.rs index 12dc077..1f58fd0 100644 --- a/src/gui/filesystem.rs +++ b/src/gui/filesystem.rs @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later use cxx_qt_lib::{QModelIndex, QString}; -use devicons::FileIcon; use dirs; use std::fs; use std::path::Path; @@ -142,13 +141,6 @@ impl qobject::FileSystem { } fn icon(self: std::pin::Pin<&mut Self>, path: &QString) -> QString { - let str = path.to_string(); - if Path::new(&str).is_dir() { - // Ensures directories are given a folder icon and not mistakenly resolved to a language. - // For example, a directory named `cpp` would otherwise return a C++ icon. - return QString::from(FileIcon::from("dir/").to_string()); - } - let icon = FileIcon::from(str); - QString::from(icon.to_string()) + QString::from(libclide::fs::icon(path.to_string().as_str()).to_string()) } } diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 4bb63f0..672ca4b 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -63,7 +63,7 @@ impl<'a> Explorer<'a> { } else { children.push(TreeItem::new_leaf( entry_meta.abs_path.clone(), - entry_meta.file_name.clone(), + format!("{} {}", entry_meta.icon.icon, entry_meta.file_name.as_str()), )); } } @@ -73,7 +73,7 @@ impl<'a> Explorer<'a> { // For a file tree this is fine because we shouldn't list the same object twice. TreeItem::new( path_meta.abs_path.clone(), - path_meta.file_name.clone(), + format!("{} {}", path_meta.icon.icon, path_meta.file_name.as_str()), children, ) .context(format!( -- 2.49.1 From b0ed2e6e1f76fd64a7e4685ce527224a78e50a7e Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 22 Feb 2026 11:44:55 -0500 Subject: [PATCH 18/19] Move icon helper. --- libclide-macros/src/lib.rs | 4 ---- libclide/src/fs.rs | 13 +------------ libclide/src/fs/entry_meta.rs | 10 ++++++++++ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/libclide-macros/src/lib.rs b/libclide-macros/src/lib.rs index 03d4fda..9ccb788 100644 --- a/libclide-macros/src/lib.rs +++ b/libclide-macros/src/lib.rs @@ -9,15 +9,11 @@ use syn::{ItemStruct, parse_macro_input}; #[proc_macro_attribute] pub fn log_id(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemStruct); - let struct_name = &input.ident; let generics = &input.generics; - // This is the important part let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); - let struct_name_str = struct_name.to_string(); - let expanded = quote! { #input diff --git a/libclide/src/fs.rs b/libclide/src/fs.rs index 484ea1e..aaee11e 100644 --- a/libclide/src/fs.rs +++ b/libclide/src/fs.rs @@ -4,15 +4,4 @@ pub mod entry_meta; -use devicons::FileIcon; -use std::path::Path; - -pub fn icon>(p: P) -> FileIcon { - let path = p.as_ref(); - if Path::new(&path).is_dir() { - // Ensures directories are given a folder icon and not mistakenly resolved to a language. - // For example, a directory named `cpp` would otherwise return a C++ icon. - return FileIcon::from("dir/"); - } - FileIcon::from(path) -} +pub use entry_meta::icon; diff --git a/libclide/src/fs/entry_meta.rs b/libclide/src/fs/entry_meta.rs index 55749c9..148df0b 100644 --- a/libclide/src/fs/entry_meta.rs +++ b/libclide/src/fs/entry_meta.rs @@ -52,3 +52,13 @@ impl EntryMeta { }) } } + +pub fn icon>(p: P) -> FileIcon { + let path = p.as_ref(); + if Path::new(&path).is_dir() { + // Ensures directories are given a folder icon and not mistakenly resolved to a language. + // For example, a directory named `cpp` would otherwise return a C++ icon. + return FileIcon::from("dir/"); + } + FileIcon::from(path) +} -- 2.49.1 From fed1d43ac90350053ef2c6a01f2398351a927082 Mon Sep 17 00:00:00 2001 From: Shaun Reed Date: Sun, 22 Feb 2026 19:37:09 -0500 Subject: [PATCH 19/19] Loggable trait and derive macro. --- Cargo.lock | 1 + libclide-macros/src/lib.rs | 11 +++--- libclide/Cargo.toml | 1 + libclide/src/log.rs | 4 +++ libclide/src/log/macros.rs | 74 ++++++++------------------------------ src/tui.rs | 7 ++-- src/tui/about.rs | 4 +-- src/tui/app.rs | 33 +++++++++-------- src/tui/component.rs | 5 ++- src/tui/editor.rs | 19 +++++----- src/tui/editor_tab.rs | 41 +++++++++++++-------- src/tui/explorer.rs | 10 +++--- src/tui/logger.rs | 7 ++-- src/tui/menu_bar.rs | 6 ++-- 14 files changed, 99 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ae1fc8..2bb2160 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,6 +1171,7 @@ version = "0.1.0" dependencies = [ "anyhow", "devicons", + "libclide-macros", "log", "strum", ] diff --git a/libclide-macros/src/lib.rs b/libclide-macros/src/lib.rs index 9ccb788..b92f58b 100644 --- a/libclide-macros/src/lib.rs +++ b/libclide-macros/src/lib.rs @@ -6,8 +6,8 @@ use proc_macro::TokenStream; use quote::quote; use syn::{ItemStruct, parse_macro_input}; -#[proc_macro_attribute] -pub fn log_id(_attr: TokenStream, item: TokenStream) -> TokenStream { +#[proc_macro_derive(Loggable)] +pub fn loggable(item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemStruct); let struct_name = &input.ident; let generics = &input.generics; @@ -15,11 +15,8 @@ pub fn log_id(_attr: TokenStream, item: TokenStream) -> TokenStream { let (impl_generics, type_generics, where_clause) = generics.split_for_impl(); let struct_name_str = struct_name.to_string(); let expanded = quote! { - #input - - impl #impl_generics #struct_name #type_generics #where_clause { - #[allow(unused)] - pub const ID: &'static str = #struct_name_str; + impl #impl_generics libclide::log::Loggable for #struct_name #type_generics #where_clause { + const ID: &'static str = #struct_name_str; } }; diff --git a/libclide/Cargo.toml b/libclide/Cargo.toml index e239386..f5014f3 100644 --- a/libclide/Cargo.toml +++ b/libclide/Cargo.toml @@ -8,3 +8,4 @@ anyhow = { workspace = true } strum = { workspace = true } log = { workspace = true } devicons = { workspace = true } +libclide-macros = { path = "../libclide-macros" } diff --git a/libclide/src/log.rs b/libclide/src/log.rs index dbb82f2..04fb61d 100644 --- a/libclide/src/log.rs +++ b/libclide/src/log.rs @@ -3,3 +3,7 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later pub mod macros; + +pub trait Loggable { + const ID: &'static str; +} diff --git a/libclide/src/log/macros.rs b/libclide/src/log/macros.rs index ab2827e..593e070 100644 --- a/libclide/src/log/macros.rs +++ b/libclide/src/log/macros.rs @@ -9,111 +9,67 @@ //! explicitly set. This is to avoid implicit pooling of log messages under the same default target, //! which can make it difficult to filter log messages by their source. //! -//! The target argument can be overridden using one of the following macros. -//! ``` -//! libclide::log!(target: "CustomTarget", "This log message will have the target 'CustomTarget'"); +//! The Loggable trait can be implemented to automatically associate log messages with a struct. //! ``` +//! use libclide_macros::Loggable; //! -//! The target argument will default to Self::ID if not provided. -//! This is an error if Self::ID is not defined, forcing you to use the explicit form. -//! ``` -//! libclide::log!("This log message will use target Self::ID, the name of the struct it was invoked in"); -//! ``` -//! -//! Self::ID can be defined using the `#[log_id]` attribute macro, which will automatically generate -//! a constant ID field with the name of the struct as its value. -//! ``` -//! #[log_id] +//! #[derive(Loggable)] //! struct MyStruct; //! impl MyStruct { //! fn my_method(&self) { -//! libclide::log!("This log message will use target Self::ID, which is 'MyStruct'"); +//! libclide::info!("This log message will use target ::ID, which is 'MyStruct'"); //! } //! } //! ``` //! +//! If the struct does not derive or implement Loggable, the target variant of the log macros must +//! be used instead. +//! ``` +//! libclide::info!(target: "CustomTarget", "This log message will have the target 'CustomTarget'"); +//! ``` +//! #[macro_export] macro_rules! info { - (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ - log::info!(logger: $logger, target: $target, $($arg)+) - }); - (target: $target:expr, $($arg:tt)+) => ({ log::info!(target: $target, $($arg)+) }); - (logger: $logger:expr, $($arg:tt)+) => ({ - log::info!(logger: $logger, target: Self::ID, $($arg)+) - }); - - ($($arg:tt)+) => (log::info!(target: Self::ID, $($arg)+)) + ($($arg:tt)+) => (log::info!(target: ::ID, $($arg)+)) } #[macro_export] macro_rules! debug { - (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ - log::debug!(logger: $logger, target: $target, $($arg)+) - }); - (target: $target:expr, $($arg:tt)+) => ({ log::debug!(target: $target, $($arg)+) }); - (logger: $logger:expr, $($arg:tt)+) => ({ - log::debug!(logger: $logger, target: Self::ID, $($arg)+) - }); - - ($($arg:tt)+) => (log::debug!(target: Self::ID, $($arg)+)) + ($($arg:tt)+) => (log::debug!(target: ::ID, $($arg)+)) } #[macro_export] macro_rules! warn { - (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ - log::warn!(logger: $logger, target: $target, $($arg)+) - }); - (target: $target:expr, $($arg:tt)+) => ({ log::warn!(target: $target, $($arg)+) }); - (logger: $logger:expr, $($arg:tt)+) => ({ - log::warn!(logger: $logger, target: Self::ID, $($arg)+) - }); - - ($($arg:tt)+) => (log::warn!(target: Self::ID, $($arg)+)) + ($($arg:tt)+) => (log::warn!(target: ::ID, $($arg)+)) } #[macro_export] macro_rules! error { - (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ - log::error!(logger: $logger, target: $target, $($arg)+) - }); - (target: $target:expr, $($arg:tt)+) => ({ log::error!(target: $target, $($arg)+) }); - (logger: $logger:expr, $($arg:tt)+) => ({ - log::error!(logger: $logger, target: Self::ID, $($arg)+) - }); - - ($($arg:tt)+) => (log::error!(target: Self::ID, $($arg)+)) + ($($arg:tt)+) => (log::error!(target: ::ID, $($arg)+)) } #[macro_export] macro_rules! trace { - (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ - log::trace!(logger: $logger, target: $target, $($arg)+) - }); - (target: $target:expr, $($arg:tt)+) => ({ log::trace!(target: $target, $($arg)+) }); - (logger: $logger:expr, $($arg:tt)+) => ({ - log::trace!(logger: $logger, target: Self::ID, $($arg)+) - }); - - ($($arg:tt)+) => (log::trace!(target: Self::ID, $($arg)+)) + ($($arg:tt)+) => (log::trace!(target: ::ID, $($arg)+)) } diff --git a/src/tui.rs b/src/tui.rs index 88a55d7..bc5e433 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -13,7 +13,7 @@ mod menu_bar; use crate::AppContext; use anyhow::{Context, Result}; -use libclide_macros::log_id; +use libclide_macros::Loggable; use log::LevelFilter; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; @@ -29,20 +29,19 @@ use tui_logger::{ TuiLoggerFile, TuiLoggerLevelOutput, init_logger, set_default_level, set_log_file, }; -#[log_id] +#[derive(Loggable)] struct Tui { terminal: Terminal>, root_path: std::path::PathBuf, } pub fn run(app_context: AppContext) -> Result<()> { - libclide::trace!(target:Tui::ID, "Starting TUI"); + libclide::trace!(target: "clide::tui::run", "Starting TUI"); Tui::new(app_context)?.start() } impl Tui { fn new(app_context: AppContext) -> Result { - libclide::trace!("Building {}", Self::ID); init_logger(LevelFilter::Trace)?; set_default_level(LevelFilter::Trace); libclide::debug!("Logging initialized"); diff --git a/src/tui/about.rs b/src/tui/about.rs index f5334f4..c5b0d9a 100644 --- a/src/tui/about.rs +++ b/src/tui/about.rs @@ -2,13 +2,13 @@ // // SPDX-License-Identifier: GNU General Public License v3.0 or later -use libclide_macros::log_id; +use libclide_macros::Loggable; use ratatui::buffer::Buffer; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph, Widget, Wrap}; -#[log_id] +#[derive(Loggable)] pub struct About {} impl About { diff --git a/src/tui/app.rs b/src/tui/app.rs index 3e02c82..a0d2f90 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -9,7 +9,8 @@ use crate::tui::explorer::Explorer; use crate::tui::logger::Logger; use crate::tui::menu_bar::MenuBar; use anyhow::{Context, Result}; -use libclide_macros::log_id; +use libclide::log::Loggable; +use libclide_macros::Loggable; use ratatui::DefaultTerminal; use ratatui::buffer::Buffer; use ratatui::crossterm::event; @@ -30,7 +31,7 @@ pub enum AppComponent { MenuBar, } -#[log_id] +#[derive(Loggable)] pub struct App<'a> { editor_tab: EditorTab, explorer: Explorer<'a>, @@ -42,7 +43,7 @@ pub struct App<'a> { impl<'a> App<'a> { pub fn new(root_path: PathBuf) -> Result { - libclide::trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", ::ID); let app = Self { editor_tab: EditorTab::new(), explorer: Explorer::new(&root_path)?, @@ -56,13 +57,13 @@ impl<'a> App<'a> { /// Logic that should be executed once on application startup. pub fn start(&mut self) -> Result<()> { - libclide::trace!(target:Self::ID, "Starting App"); + libclide::trace!("Starting App"); Ok(()) } pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { self.start()?; - libclide::trace!(target:Self::ID, "Entering App run loop"); + libclide::trace!("Entering App run loop"); loop { terminal.draw(|f| { f.render_widget(&mut self, f.area()); @@ -88,7 +89,7 @@ impl<'a> App<'a> { Some(editor) => editor.component_state.help_text.clone(), None => { if !self.editor_tab.is_empty() { - libclide::error!(target:Self::ID, "Failed to get Editor while drawing bottom status bar"); + libclide::error!("Failed to get Editor while drawing bottom status bar"); } "Failed to get current Editor while getting widget help text".to_string() } @@ -112,26 +113,26 @@ impl<'a> App<'a> { } fn clear_focus(&mut self) { - libclide::info!(target:Self::ID, "Clearing all widget focus"); + libclide::info!("Clearing all widget focus"); self.explorer.component_state.set_focus(Focus::Inactive); self.explorer.component_state.set_focus(Focus::Inactive); self.logger.component_state.set_focus(Focus::Inactive); self.menu_bar.component_state.set_focus(Focus::Inactive); match self.editor_tab.current_editor_mut() { None => { - libclide::error!(target:Self::ID, "Failed to get current Editor while clearing focus") + libclide::error!("Failed to get current Editor while clearing focus") } Some(editor) => editor.component_state.set_focus(Focus::Inactive), } } fn change_focus(&mut self, focus: AppComponent) { - libclide::info!(target:Self::ID, "Changing widget focus to {:?}", focus); + libclide::info!("Changing widget focus to {:?}", focus); self.clear_focus(); match focus { AppComponent::Editor => match self.editor_tab.current_editor_mut() { None => { - libclide::error!(target:Self::ID, "Failed to get current Editor while changing focus") + libclide::error!("Failed to get current Editor while changing focus") } Some(editor) => editor.component_state.set_focus(Focus::Active), }, @@ -274,13 +275,15 @@ impl<'a> Component for App<'a> { Action::Quit | Action::Handled => Ok(action), Action::Save => match self.editor_tab.current_editor_mut() { None => { - libclide::error!(target:Self::ID, "Failed to get current editor while handling App Action::Save"); + libclide::error!( + "Failed to get current editor while handling App Action::Save" + ); Ok(Action::Noop) } Some(editor) => match editor.save() { Ok(_) => Ok(Action::Handled), Err(e) => { - libclide::error!(target:Self::ID, "Failed to save editor contents: {e}"); + libclide::error!("Failed to save editor contents: {e}"); Ok(Action::Noop) } }, @@ -299,14 +302,16 @@ impl<'a> Component for App<'a> { Err(_) => Ok(Action::Noop), }, Action::ReloadFile => { - libclide::trace!(target:Self::ID, "Reloading file for current editor"); + libclide::trace!("Reloading file for current editor"); if let Some(editor) = self.editor_tab.current_editor_mut() { editor .reload_contents() .map(|_| Action::Handled) .context("Failed to handle Action::ReloadFile") } else { - libclide::error!(target:Self::ID, "Failed to get current editor while handling App Action::ReloadFile"); + libclide::error!( + "Failed to get current editor while handling App Action::ReloadFile" + ); Ok(Action::Noop) } } diff --git a/src/tui/component.rs b/src/tui/component.rs index 30ffd47..5cd3304 100644 --- a/src/tui/component.rs +++ b/src/tui/component.rs @@ -8,7 +8,7 @@ use crate::tui::component::Focus::Inactive; use Focus::Active; use anyhow::Result; use libclide::theme::colors::Colors; -use libclide_macros::log_id; +use libclide_macros::Loggable; use ratatui::crossterm::event::{Event, KeyEvent, MouseEvent}; use ratatui::style::Color; @@ -62,8 +62,7 @@ pub trait Component { } } -#[derive(Debug, Clone, Default)] -#[log_id] +#[derive(Debug, Clone, Default, Loggable)] pub struct ComponentState { pub(crate) focus: Focus, pub(crate) vis: Visibility, diff --git a/src/tui/editor.rs b/src/tui/editor.rs index 42adcb1..fdd7dc3 100644 --- a/src/tui/editor.rs +++ b/src/tui/editor.rs @@ -7,7 +7,8 @@ use anyhow::{Context, Result, bail}; use edtui::{ EditorEventHandler, EditorState, EditorTheme, EditorView, LineNumbers, Lines, SyntaxHighlighter, }; -use libclide_macros::log_id; +use libclide::log::Loggable; +use libclide_macros::Loggable; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::{Alignment, Rect}; @@ -16,18 +17,18 @@ use ratatui::widgets::{Block, Borders, Padding, Widget}; use std::path::PathBuf; use syntect::parsing::SyntaxSet; -#[log_id] +#[derive(Loggable)] pub struct Editor { pub state: EditorState, pub event_handler: EditorEventHandler, - pub file_path: Option, + pub file_path: Option, syntax_set: SyntaxSet, pub(crate) component_state: ComponentState, } impl Editor { pub fn new(path: &std::path::Path) -> Self { - libclide::trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", ::ID); Editor { state: EditorState::default(), event_handler: EditorEventHandler::default(), @@ -41,10 +42,10 @@ impl Editor { } pub fn reload_contents(&mut self) -> Result<()> { - libclide::trace!(target:Self::ID, "Reloading editor file contents {:?}", self.file_path); + libclide::trace!("Reloading editor file contents {:?}", self.file_path); match self.file_path.clone() { None => { - libclide::error!(target:Self::ID, "Failed to reload editor contents with None file_path"); + libclide::error!("Failed to reload editor contents with None file_path"); bail!("Failed to reload editor contents with None file_path") } Some(path) => self.set_contents(&path), @@ -52,7 +53,7 @@ impl Editor { } pub fn set_contents(&mut self, path: &std::path::Path) -> Result<()> { - libclide::trace!(target:Self::ID, "Setting Editor contents from path {:?}", path); + libclide::trace!("Setting Editor contents from path {:?}", path); if let Ok(contents) = std::fs::read_to_string(path) { let lines: Vec<_> = contents .lines() @@ -68,10 +69,10 @@ impl Editor { pub fn save(&self) -> Result<()> { if let Some(path) = &self.file_path { - libclide::trace!(target:Self::ID, "Saving Editor contents {:?}", path); + libclide::trace!("Saving Editor contents {:?}", path); return std::fs::write(path, self.state.lines.to_string()).map_err(|e| e.into()); }; - libclide::error!(target:Self::ID, "Failed saving Editor contents; file_path was None"); + libclide::error!("Failed saving Editor contents; file_path was None"); bail!("File not saved. No file path set.") } } diff --git a/src/tui/editor_tab.rs b/src/tui/editor_tab.rs index ec7da9e..234ed04 100644 --- a/src/tui/editor_tab.rs +++ b/src/tui/editor_tab.rs @@ -5,7 +5,8 @@ use crate::tui::component::{Action, Component, Focus, FocusState}; use crate::tui::editor::Editor; use anyhow::{Context, Result, anyhow}; -use libclide_macros::log_id; +use libclide::log::Loggable; +use libclide_macros::Loggable; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers}; use ratatui::layout::Rect; @@ -16,7 +17,7 @@ use std::collections::HashMap; // Render the tabs with keys as titles // Tab keys can be file names. // Render the editor using the key as a reference for lookup -#[log_id] +#[derive(Loggable)] pub struct EditorTab { pub(crate) editors: HashMap, tab_order: Vec, @@ -25,7 +26,7 @@ pub struct EditorTab { impl EditorTab { pub fn new() -> Self { - libclide::trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", ::ID); Self { editors: HashMap::new(), tab_order: Vec::new(), @@ -35,7 +36,11 @@ impl EditorTab { pub fn next_editor(&mut self) { let next = (self.current_editor + 1) % self.tab_order.len(); - libclide::trace!(target:Self::ID, "Moving from {} to next editor tab at {}", self.current_editor, next); + libclide::trace!( + "Moving from {} to next editor tab at {}", + self.current_editor, + next + ); self.set_tab_focus(Focus::Active, next); self.current_editor = next; } @@ -45,7 +50,11 @@ impl EditorTab { .current_editor .checked_sub(1) .unwrap_or(self.tab_order.len() - 1); - libclide::trace!(target:Self::ID, "Moving from {} to previous editor tab at {}", self.current_editor, prev); + libclide::trace!( + "Moving from {} to previous editor tab at {}", + self.current_editor, + prev + ); self.set_tab_focus(Focus::Active, prev); self.current_editor = prev; } @@ -54,7 +63,7 @@ impl EditorTab { match self.tab_order.get(index) { None => { if !self.tab_order.is_empty() { - libclide::error!(target:Self::ID, "Failed to get editor tab key with invalid index {index}"); + libclide::error!("Failed to get editor tab key with invalid index {index}"); } None } @@ -72,16 +81,19 @@ impl EditorTab { } pub fn set_current_tab_focus(&mut self, focus: Focus) { - libclide::trace!(target:Self::ID, "Setting current tab {} focus to {:?}", self.current_editor, focus); + libclide::trace!( + "Setting current tab {} focus to {:?}", + self.current_editor, + focus + ); self.set_tab_focus(focus, self.current_editor) } pub fn set_tab_focus(&mut self, focus: Focus, index: usize) { - libclide::trace!(target:Self::ID, "Setting tab {} focus to {:?}", index, focus); + libclide::trace!("Setting tab {} focus to {:?}", index, focus); if focus == Focus::Active && index != self.current_editor { // If we are setting another tab to active, disable the current one. libclide::trace!( - target:Self::ID, "New tab {} focus set to Active; Setting current tab {} to Inactive", index, self.current_editor @@ -90,12 +102,11 @@ impl EditorTab { } match self.get_editor_key(index) { None => { - libclide::error!(target:Self::ID, "Failed setting tab focus for invalid key {index}"); + libclide::error!("Failed setting tab focus for invalid key {index}"); } Some(key) => match self.editors.get_mut(&key) { None => { libclide::error!( - target:Self::ID, "Failed to update tab focus at index {} with invalid key: {}", self.current_editor, self.tab_order[self.current_editor] @@ -107,12 +118,12 @@ impl EditorTab { } pub fn open_tab(&mut self, path: &std::path::Path) -> Result<()> { - libclide::trace!(target:Self::ID, "Opening new EditorTab with path {:?}", path); + libclide::trace!("Opening new EditorTab with path {:?}", path); if self .editors .contains_key(&path.to_string_lossy().to_string()) { - libclide::warn!(target:Self::ID, "EditorTab already opened with this file"); + libclide::warn!("EditorTab already opened with this file"); return Ok(()); } @@ -137,12 +148,12 @@ impl EditorTab { .to_owned(); match self.editors.remove(&key) { None => { - libclide::error!(target:Self::ID, "Failed to remove editor tab {key} with invalid index {index}") + libclide::error!("Failed to remove editor tab {key} with invalid index {index}") } Some(_) => { self.prev_editor(); self.tab_order.remove(index); - libclide::info!(target:Self::ID, "Closed editor tab {key} at index {index}") + libclide::info!("Closed editor tab {key} at index {index}") } } Ok(()) diff --git a/src/tui/explorer.rs b/src/tui/explorer.rs index 672ca4b..4a63a50 100644 --- a/src/tui/explorer.rs +++ b/src/tui/explorer.rs @@ -5,7 +5,8 @@ use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; use anyhow::{Context, Result, bail}; use libclide::fs::entry_meta::EntryMeta; -use libclide_macros::log_id; +use libclide::log::Loggable; +use libclide_macros::Loggable; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent, MouseEvent, MouseEventKind}; use ratatui::layout::{Alignment, Position, Rect}; @@ -16,8 +17,7 @@ use std::fs; use std::path::{Path, PathBuf}; use tui_tree_widget::{Tree, TreeItem, TreeState}; -#[derive(Debug)] -#[log_id] +#[derive(Debug, Loggable)] pub struct Explorer<'a> { root_path: EntryMeta, tree_items: TreeItem<'a, String>, @@ -27,7 +27,7 @@ pub struct Explorer<'a> { impl<'a> Explorer<'a> { pub fn new(path: &PathBuf) -> Result { - libclide::trace!("Building {}", Self::ID); + libclide::trace!("Building {}", ::ID); let explorer = Explorer { root_path: EntryMeta::new(path)?, tree_items: Self::build_tree_from_path(path)?, @@ -69,7 +69,7 @@ impl<'a> Explorer<'a> { } } - // Note: The first argument is a unique identifier, where no 2 TreeItems may share the same. + // Note: The first argument is a unique identifier, where no. 2 TreeItems may share the same. // For a file tree this is fine because we shouldn't list the same object twice. TreeItem::new( path_meta.abs_path.clone(), diff --git a/src/tui/logger.rs b/src/tui/logger.rs index 122d5fc..a5f7680 100644 --- a/src/tui/logger.rs +++ b/src/tui/logger.rs @@ -3,7 +3,8 @@ // SPDX-License-Identifier: GNU General Public License v3.0 or later use crate::tui::component::{Action, Component, ComponentState, Focus, FocusState}; -use libclide_macros::log_id; +use libclide::log::Loggable; +use libclide_macros::Loggable; use log::LevelFilter; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{Event, KeyCode, KeyEvent}; @@ -14,7 +15,7 @@ use tui_logger::{TuiLoggerLevelOutput, TuiLoggerSmartWidget, TuiWidgetEvent, Tui /// Any log written as info!(target:self.id(), "message") will work with this logger. /// The logger is bound to info!, debug!, error!, trace! macros within Tui::new(). -#[log_id] +#[derive(Loggable)] pub struct Logger { state: TuiWidgetState, pub(crate) component_state: ComponentState, @@ -22,7 +23,7 @@ pub struct Logger { impl Logger { pub fn new() -> Self { - libclide::trace!(target:Self::ID, "Building {}", Self::ID); + libclide::trace!("Building {}", ::ID); let state = TuiWidgetState::new(); state.transition(TuiWidgetEvent::HideKey); Self { diff --git a/src/tui/menu_bar.rs b/src/tui/menu_bar.rs index a197188..c71651e 100644 --- a/src/tui/menu_bar.rs +++ b/src/tui/menu_bar.rs @@ -7,7 +7,7 @@ use crate::tui::menu_bar::MenuBarItemOption::{ About, CloseTab, Exit, Reload, Save, ShowHideExplorer, ShowHideLogger, }; use anyhow::Context; -use libclide_macros::log_id; +use libclide_macros::Loggable; use ratatui::buffer::Buffer; use ratatui::crossterm::event::{KeyCode, KeyEvent}; use ratatui::layout::Rect; @@ -80,7 +80,7 @@ impl MenuBarItem { } } -#[log_id] +#[derive(Debug, Loggable)] pub struct MenuBar { selected: MenuBarItem, opened: Option, @@ -91,7 +91,7 @@ pub struct MenuBar { impl MenuBar { const DEFAULT_HELP: &str = "(←/h)/(→/l): Select option | Enter: Choose selection"; pub fn new() -> Self { - libclide::trace!("Building {}", Self::ID); + libclide::trace!("Building"); Self { selected: MenuBarItem::File, opened: None, -- 2.49.1