2 Commits

Author SHA1 Message Date
01de2390ac Add icons to TUI. 2026-02-22 11:36:20 -05:00
784592658a Fix comments. 2026-02-22 11:04:00 -05:00
10 changed files with 58 additions and 30 deletions

1
Cargo.lock generated
View File

@@ -1170,6 +1170,7 @@ name = "libclide"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"devicons",
"log", "log",
"strum", "strum",
] ]

View File

@@ -11,6 +11,7 @@ members = [".", "libclide", "libclide-macros", ]
anyhow = "1.0.100" anyhow = "1.0.100"
strum = "0.27.2" strum = "0.27.2"
log = { version = "0.4.27", features = [] } log = { version = "0.4.27", features = [] }
devicons = "0.6.12"
[dependencies] [dependencies]
cxx = "1.0.95" cxx = "1.0.95"
@@ -23,12 +24,12 @@ ratatui = "0.30.0"
tui-tree-widget = "0.24.0" tui-tree-widget = "0.24.0"
tui-logger = "0.18.1" tui-logger = "0.18.1"
edtui = "0.11.1" edtui = "0.11.1"
devicons = "0.6.12"
libclide = { path = "./libclide" } libclide = { path = "./libclide" }
libclide-macros = { path = "./libclide-macros" } libclide-macros = { path = "./libclide-macros" }
anyhow = { workspace = true } anyhow = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
log = { workspace = true } log = { workspace = true }
devicons = { workspace = true }
[build-dependencies] [build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6. # The link_qt_object_files feature is required for statically linking Qt 6.

View File

@@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// SPDX-License-Identifier: GNU General Public License v3.0 or later
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use quote::quote;
use syn::{ItemStruct, parse_macro_input}; use syn::{ItemStruct, parse_macro_input};

View File

@@ -7,3 +7,4 @@ edition = "2024"
anyhow = { workspace = true } anyhow = { workspace = true }
strum = { workspace = true } strum = { workspace = true }
log = { workspace = true } log = { workspace = true }
devicons = { workspace = true }

View File

@@ -3,3 +3,16 @@
// SPDX-License-Identifier: GNU General Public License v3.0 or later // SPDX-License-Identifier: GNU General Public License v3.0 or later
pub mod entry_meta; pub mod entry_meta;
use devicons::FileIcon;
use std::path::Path;
pub fn icon<P: AsRef<str>>(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)
}

View File

@@ -3,6 +3,7 @@
// SPDX-License-Identifier: GNU General Public License v3.0 or later // SPDX-License-Identifier: GNU General Public License v3.0 or later
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use devicons::FileIcon;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Debug)] #[derive(Debug)]
@@ -10,6 +11,7 @@ pub struct EntryMeta {
pub abs_path: String, pub abs_path: String,
pub file_name: String, pub file_name: String,
pub is_dir: bool, pub is_dir: bool,
pub icon: FileIcon,
} }
impl EntryMeta { impl EntryMeta {
@@ -41,10 +43,12 @@ impl EntryMeta {
.context(format!("Failed to get file name for path: {abs_path:?}"))? .context(format!("Failed to get file name for path: {abs_path:?}"))?
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let icon = crate::fs::icon(&abs_path);
Ok(EntryMeta { Ok(EntryMeta {
abs_path, abs_path,
file_name, file_name,
is_dir, is_dir,
icon,
}) })
} }
} }

View File

@@ -1,13 +1,40 @@
// SPDX-FileCopyrightText: 2026, Shaun Reed <shaunrd0@gmail.com>
//
// 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 //! 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. //! 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 //! 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, //! 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. //! 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_export]
macro_rules! info { macro_rules! info {
// The target argument can be overridden using one of the following macros.
(logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({
log::info!(logger: $logger, target: $target, $($arg)+) log::info!(logger: $logger, target: $target, $($arg)+)
}); });
@@ -16,8 +43,6 @@ macro_rules! info {
log::info!(target: $target, $($arg)+) 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)+) => ({ (logger: $logger:expr, $($arg:tt)+) => ({
log::info!(logger: $logger, target: Self::ID, $($arg)+) log::info!(logger: $logger, target: Self::ID, $($arg)+)
}); });
@@ -27,7 +52,6 @@ macro_rules! info {
#[macro_export] #[macro_export]
macro_rules! debug { macro_rules! debug {
// The target argument can be overridden using one of the following macros.
(logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({
log::debug!(logger: $logger, target: $target, $($arg)+) log::debug!(logger: $logger, target: $target, $($arg)+)
}); });
@@ -36,8 +60,6 @@ macro_rules! debug {
log::debug!(target: $target, $($arg)+) 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)+) => ({ (logger: $logger:expr, $($arg:tt)+) => ({
log::debug!(logger: $logger, target: Self::ID, $($arg)+) log::debug!(logger: $logger, target: Self::ID, $($arg)+)
}); });
@@ -47,7 +69,6 @@ macro_rules! debug {
#[macro_export] #[macro_export]
macro_rules! warn { macro_rules! warn {
// The target argument can be overridden using one of the following macros.
(logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({
log::warn!(logger: $logger, target: $target, $($arg)+) log::warn!(logger: $logger, target: $target, $($arg)+)
}); });
@@ -56,8 +77,6 @@ macro_rules! warn {
log::warn!(target: $target, $($arg)+) 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)+) => ({ (logger: $logger:expr, $($arg:tt)+) => ({
log::warn!(logger: $logger, target: Self::ID, $($arg)+) log::warn!(logger: $logger, target: Self::ID, $($arg)+)
}); });
@@ -67,7 +86,6 @@ macro_rules! warn {
#[macro_export] #[macro_export]
macro_rules! error { macro_rules! error {
// The target argument can be overridden using one of the following macros.
(logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({
log::error!(logger: $logger, target: $target, $($arg)+) log::error!(logger: $logger, target: $target, $($arg)+)
}); });
@@ -76,8 +94,6 @@ macro_rules! error {
log::error!(target: $target, $($arg)+) 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)+) => ({ (logger: $logger:expr, $($arg:tt)+) => ({
log::error!(logger: $logger, target: Self::ID, $($arg)+) log::error!(logger: $logger, target: Self::ID, $($arg)+)
}); });
@@ -87,7 +103,6 @@ macro_rules! error {
#[macro_export] #[macro_export]
macro_rules! trace { macro_rules! trace {
// The target argument can be overridden using one of the following macros.
(logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({ (logger: $logger:expr, target: $target:expr, $($arg:tt)+) => ({
log::trace!(logger: $logger, target: $target, $($arg)+) log::trace!(logger: $logger, target: $target, $($arg)+)
}); });
@@ -96,8 +111,6 @@ macro_rules! trace {
log::trace!(target: $target, $($arg)+) 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)+) => ({ (logger: $logger:expr, $($arg:tt)+) => ({
log::trace!(logger: $logger, target: Self::ID, $($arg)+) log::trace!(logger: $logger, target: Self::ID, $($arg)+)
}); });

View File

@@ -3,7 +3,6 @@
// SPDX-License-Identifier: GNU General Public License v3.0 or later // SPDX-License-Identifier: GNU General Public License v3.0 or later
use cxx_qt_lib::{QModelIndex, QString}; use cxx_qt_lib::{QModelIndex, QString};
use devicons::FileIcon;
use dirs; use dirs;
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
@@ -142,13 +141,6 @@ impl qobject::FileSystem {
} }
fn icon(self: std::pin::Pin<&mut Self>, path: &QString) -> QString { fn icon(self: std::pin::Pin<&mut Self>, path: &QString) -> QString {
let str = path.to_string(); QString::from(libclide::fs::icon(path.to_string().as_str()).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())
} }
} }

View File

@@ -4,7 +4,6 @@
use anyhow::{Context, Result, anyhow}; use anyhow::{Context, Result, anyhow};
use clap::Parser; use clap::Parser;
use log::{info, trace};
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
pub mod gui; pub mod gui;
@@ -56,7 +55,7 @@ impl AppContext {
// If no path was provided, use the current directory. // If no path was provided, use the current directory.
None => std::env::current_dir().context("Failed to obtain 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 { Ok(Self {
path, path,
@@ -80,7 +79,7 @@ fn main() -> Result<()> {
RunMode::GuiAttached => gui::run(app_context), RunMode::GuiAttached => gui::run(app_context),
RunMode::Tui => tui::run(app_context), RunMode::Tui => tui::run(app_context),
RunMode::Gui => { 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()?) 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()) .stdout(Stdio::null())

View File

@@ -63,7 +63,7 @@ impl<'a> Explorer<'a> {
} else { } else {
children.push(TreeItem::new_leaf( children.push(TreeItem::new_leaf(
entry_meta.abs_path.clone(), 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. // For a file tree this is fine because we shouldn't list the same object twice.
TreeItem::new( TreeItem::new(
path_meta.abs_path.clone(), path_meta.abs_path.clone(),
path_meta.file_name.clone(), format!("{} {}", path_meta.icon.icon, path_meta.file_name.as_str()),
children, children,
) )
.context(format!( .context(format!(