diff --git a/.gitignore b/.gitignore index 53eaa21..3f2d71b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target **/*.rs.bk +navi.log diff --git a/Cargo.lock b/Cargo.lock index bbdffee..a379f1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,6 +244,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "edit" version = "0.1.4" @@ -412,9 +418,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -455,6 +461,7 @@ dependencies = [ "crossterm", "dns_common", "dns_common_derive", + "dunce", "edit", "etcetera", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index a9dc3d1..fa054cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,9 @@ dns_common_derive = { version = "0.2.1" } dns_common = { version = "0.2.1", default-features = false, features = ["yaml", "json"] } unicode-width = "0.1.10" +[target.'cfg(windows)'.dependencies] +dunce = "1" + [lib] name = "navi" path = "src/lib.rs" diff --git a/docs/config_file.md b/docs/config_file.md index d7db573..25498c0 100644 --- a/docs/config_file.md +++ b/docs/config_file.md @@ -29,3 +29,9 @@ Run the following command to generate a config file with the default parameters: ```sh navi info config-example > "$(navi info config-path)" ``` + +### Logging + +The log file will be created under the same directory where the config locates. + +And you can use the `RUST_LOG` env to set the log level, e.g. `RUST_LOG=debug navi`. diff --git a/docs/config_file_example.yaml b/docs/config_file_example.yaml index 2344dec..1eb7ab7 100644 --- a/docs/config_file_example.yaml +++ b/docs/config_file_example.yaml @@ -19,14 +19,17 @@ finder: # overrides_var: --tac # equivalent to the --fzf-overrides-var option # cheats: -# paths: -# - /path/to/some/dir -# - /path/to/another/dir +# paths: +# - /path/to/some/dir # on unix-like os +# - F:\\path\\to\\dir # on Windows # path: /path/to/some/dir # (DEPRECATED) equivalent to the --path option # search: # tags: git,!checkout # equivalent to the --tag-rules option shell: - command: bash # shell used for shell out. possible values: bash, zsh, dash, ... + # Shell used for shell out. Possible values: bash, zsh, dash, ... + # For Windows, use `cmd.exe` instead. + command: bash + # finder_command: bash # similar, but for fzf's internals diff --git a/src/bin/main.rs b/src/bin/main.rs index ad67e71..8c4856a 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,6 +1,6 @@ extern crate navi; -use std::fmt::Debug; +use dns_common::prelude::*; use thiserror::Error; #[derive(Error, Debug)] @@ -24,6 +24,36 @@ impl FileAnIssue { } } -fn main() -> Result<(), anyhow::Error> { - navi::handle().map_err(|e| FileAnIssue::new(e).into()) +fn main() -> anyhow::Result<()> { + if let Err(err) = init_logger() { + // may need redir stderr to a file to show this log initialization error + eprintln!("failed to initialize logging: {err:?}"); + } + navi::handle().map_err(|e| { + error!("{e:?}"); + FileAnIssue::new(e).into() + }) +} + +fn init_logger() -> anyhow::Result<()> { + const FILE_NAME: &str = "navi.log"; + let mut file = navi::default_config_pathbuf()?; + file.set_file_name(FILE_NAME); + + // If config path doesn't exist, navi won't log. + if file.parent().map(|p| !p.exists()).unwrap_or(true) { + return Ok(()); + } + + let writer = std::fs::File::create(&file).with_context(|| format!("{file:?} is not created"))?; + tracing::subscriber::set_global_default( + tracing_subscriber::fmt() + .with_ansi(false) + .with_writer(writer) + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .finish(), + )?; + debug!("tracing initialized"); + + Ok(()) } diff --git a/src/commands/core/actor.rs b/src/commands/core/actor.rs index 9d49717..91a1846 100644 --- a/src/commands/core/actor.rs +++ b/src/commands/core/actor.rs @@ -41,9 +41,10 @@ fn prompt_finder( } } - let child = shell::out() - .stdout(Stdio::piped()) - .arg(suggestion_command) + let mut cmd = shell::out(); + cmd.stdout(Stdio::piped()).arg(suggestion_command); + debug!(cmd = ?cmd); + let child = cmd .spawn() .map_err(|e| ShellSpawnError::new(suggestion_command, e))?; @@ -236,9 +237,10 @@ pub fn act( clipboard::copy(interpolated_snippet)?; } _ => { - shell::out() - .arg(&interpolated_snippet[..]) - .spawn() + let mut cmd = shell::out(); + cmd.arg(&interpolated_snippet[..]); + debug!(cmd = ?cmd); + cmd.spawn() .map_err(|e| ShellSpawnError::new(&interpolated_snippet[..], e))? .wait() .context("bash was not running")?; diff --git a/src/commands/core/mod.rs b/src/commands/core/mod.rs index ded24f1..00e3a35 100644 --- a/src/commands/core/mod.rs +++ b/src/commands/core/mod.rs @@ -13,6 +13,7 @@ use crate::welcome; pub fn init(fetcher: Box) -> Result<()> { let config = &CONFIG; let opts = FinderOpts::snippet_default(); + debug!("opts = {opts:#?}"); // let fetcher = config.fetcher(); let (raw_selection, (variables, files)) = config @@ -32,6 +33,7 @@ pub fn init(fetcher: Box) -> Result<()> { }) .context("Failed getting selection and variables from finder")?; + debug!(raw_selection = ?raw_selection); let extractions = deser::terminal::read(&raw_selection, config.best_match()); if extractions.is_err() { @@ -44,7 +46,9 @@ pub fn init(fetcher: Box) -> Result<()> { } pub fn get_fetcher() -> Result> { - match CONFIG.source() { + let source = CONFIG.source(); + debug!(source = ?source); + match source { Source::Cheats(query) => { let lines = cheatsh::call(&query)?; let fetcher = Box::new(StaticFetcher::new(lines)); diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 783cbad..4537177 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -12,6 +12,7 @@ use crate::prelude::*; pub fn handle() -> Result<()> { use crate::config::Command::*; + debug!("CONFIG = {:#?}", &*CONFIG); match CONFIG.cmd() { None => commands::core::main(), diff --git a/src/commands/preview/var_stdin.rs b/src/commands/preview/var_stdin.rs index 4b8ddfb..78cc8ef 100755 --- a/src/commands/preview/var_stdin.rs +++ b/src/commands/preview/var_stdin.rs @@ -30,11 +30,10 @@ impl Runnable for Input { if !extra.is_empty() { print!(""); - shell::out() - .arg(extra) - .spawn() - .map_err(|e| ShellSpawnError::new(extra, e))? - .wait()?; + let mut cmd = shell::out(); + cmd.arg(extra); + debug!(?cmd); + cmd.spawn().map_err(|e| ShellSpawnError::new(extra, e))?.wait()?; } } diff --git a/src/common/fs.rs b/src/common/fs.rs index 32c8b1e..cf8a737 100644 --- a/src/common/fs.rs +++ b/src/common/fs.rs @@ -78,6 +78,11 @@ fn follow_symlink(pathbuf: PathBuf) -> Result { fn exe_pathbuf() -> Result { let pathbuf = std::env::current_exe().context("Unable to acquire executable's path")?; + + #[cfg(target_family = "windows")] + let pathbuf = dunce::canonicalize(pathbuf)?; + + debug!(current_exe = ?pathbuf); follow_symlink(pathbuf) } diff --git a/src/config/cli.rs b/src/config/cli.rs index 7e2ec20..c6f86f9 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -117,6 +117,7 @@ pub enum Command { Info(commands::info::Input), } +#[derive(Debug)] pub enum Source { Filesystem(Option), Tldr(String), diff --git a/src/config/env.rs b/src/config/env.rs index 7afd27e..ba428fc 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -2,6 +2,7 @@ use crate::env_var; use crate::finder::FinderChoice; use crate::prelude::*; +#[derive(Debug)] pub struct EnvConfig { pub config_yaml: Option, pub config_path: Option, diff --git a/src/config/mod.rs b/src/config/mod.rs index 774f930..b652608 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -12,6 +12,7 @@ use yaml::YamlConfig; lazy_static! { pub static ref CONFIG: Config = Config::new(); } +#[derive(Debug)] pub struct Config { yaml: YamlConfig, clap: ClapConfig, @@ -69,7 +70,7 @@ impl Config { if p.is_empty() { None } else { - Some(p.join(":")) + Some(p.join(crate::filesystem::JOIN_SEPARATOR)) } }) .or_else(|| self.yaml.cheats.path.clone()) diff --git a/src/config/yaml.rs b/src/config/yaml.rs index 8c4a693..70a5a17 100644 --- a/src/config/yaml.rs +++ b/src/config/yaml.rs @@ -6,7 +6,7 @@ use crate::prelude::*; use crossterm::style::Color as TerminalColor; use serde::de; -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct Color(#[serde(deserialize_with = "color_deserialize")] TerminalColor); impl Color { @@ -24,7 +24,7 @@ where .map_err(|_| de::Error::custom(format!("Failed to deserialize color: {s}"))) } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(default)] pub struct ColorWidth { pub color: Color, @@ -32,7 +32,7 @@ pub struct ColorWidth { pub min_width: u16, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(default)] pub struct Style { pub tag: ColorWidth, @@ -40,7 +40,7 @@ pub struct Style { pub snippet: ColorWidth, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(default)] pub struct Finder { #[serde(deserialize_with = "finder_deserialize")] @@ -58,27 +58,27 @@ where .map_err(|_| de::Error::custom(format!("Failed to deserialize finder: {s}"))) } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, Debug)] #[serde(default)] pub struct Cheats { pub path: Option, pub paths: Vec, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, Debug)] #[serde(default)] pub struct Search { pub tags: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] #[serde(default)] pub struct Shell { pub command: String, pub finder_command: Option, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, Debug)] #[serde(default)] pub struct YamlConfig { pub style: Style, diff --git a/src/filesystem.rs b/src/filesystem.rs index 6340f82..b8c4a03 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -12,6 +12,13 @@ use std::path::MAIN_SEPARATOR; use walkdir::WalkDir; +/// Multiple paths are joint by a platform-specific separator. +/// FIXME: it's actually incorrect to assume a path doesn't containing this separator +#[cfg(target_family = "windows")] +pub const JOIN_SEPARATOR: &str = ";"; +#[cfg(not(target_family = "windows"))] +pub const JOIN_SEPARATOR: &str = ":"; + pub fn all_cheat_files(path: &Path) -> Vec { WalkDir::new(path) .follow_links(true) @@ -23,7 +30,7 @@ pub fn all_cheat_files(path: &Path) -> Vec { } fn paths_from_path_param(env_var: &str) -> impl Iterator { - env_var.split(':').filter(|folder| folder != &"") + env_var.split(JOIN_SEPARATOR).filter(|folder| folder != &"") } fn compiled_default_path(path: Option<&str>) -> Option { @@ -125,6 +132,7 @@ fn interpolate_paths(paths: String) -> String { newtext } +#[derive(Debug)] pub struct Fetcher { path: Option, files: RefCell>, @@ -165,7 +173,9 @@ impl fetcher::Fetcher for Fetcher { None => folder.to_string(), }; let folder_pathbuf = PathBuf::from(interpolated_folder); - for file in all_cheat_files(&folder_pathbuf) { + let cheat_files = all_cheat_files(&folder_pathbuf); + debug!("read cheat files in `{folder_pathbuf:?}`: {cheat_files:#?}"); + for file in cheat_files { self.files.borrow_mut().push(file.clone()); let index = self.files.borrow().len() - 1; let read_file_result = { @@ -180,6 +190,7 @@ impl fetcher::Fetcher for Fetcher { } } + debug!("FilesystemFetcher = {self:#?}"); Ok(found_something) } @@ -280,4 +291,12 @@ mod tests { assert_eq!(expected, cheats.to_string_lossy().to_string()) } + + #[test] + #[cfg(target_family = "windows")] + fn multiple_paths() { + let p = r#"C:\Users\Administrator\AppData\Roaming\navi\config.yaml"#; + let paths = &[p; 2].join(JOIN_SEPARATOR); + assert_eq!(paths_from_path_param(paths).collect::>(), [p; 2]); + } } diff --git a/src/finder/mod.rs b/src/finder/mod.rs index b18888f..9b4ccfd 100644 --- a/src/finder/mod.rs +++ b/src/finder/mod.rs @@ -152,11 +152,13 @@ impl FinderChoice { }); } - let child = command + command .env("SHELL", CONFIG.finder_shell()) .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn(); + .stdout(Stdio::piped()); + debug!(cmd = ?command); + + let child = command.spawn(); let mut child = match child { Ok(x) => x, diff --git a/src/lib.rs b/src/lib.rs index cbae52d..bc2bdf6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,5 @@ #[macro_use] extern crate lazy_static; -// #[macro_use] -// extern crate anyhow; mod clients; mod commands; @@ -16,4 +14,4 @@ mod prelude; mod structures; mod welcome; -pub use commands::handle; +pub use {commands::handle, filesystem::default_config_pathbuf};