mirror of
https://github.com/denisidoro/navi
synced 2024-11-25 04:50:21 +00:00
Add support for config file (#518)
This commit is contained in:
parent
d8d0d81368
commit
7fb2b53463
28 changed files with 718 additions and 331 deletions
55
Cargo.lock
generated
55
Cargo.lock
generated
|
@ -169,6 +169,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
|
||||
|
||||
[[package]]
|
||||
name = "edit"
|
||||
version = "0.1.3"
|
||||
|
@ -251,6 +257,12 @@ version = "0.2.92"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.3"
|
||||
|
@ -318,6 +330,8 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"regex",
|
||||
"remove_dir_all 0.7.0",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
"shellwords",
|
||||
"strip-ansi-escapes",
|
||||
"thiserror",
|
||||
|
@ -560,6 +574,38 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
|
||||
dependencies = [
|
||||
"dtoa",
|
||||
"linked-hash-map",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellwords"
|
||||
version = "1.1.0"
|
||||
|
@ -776,3 +822,12 @@ name = "winapi-x86_64-pc-windows-gnu"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
|
|
@ -27,6 +27,8 @@ thiserror = "1.0.24"
|
|||
strip-ansi-escapes = "0.1.0"
|
||||
edit = "0.1.3"
|
||||
remove_dir_all = "0.7.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_yaml = "0.8"
|
||||
|
||||
[lib]
|
||||
name = "navi"
|
||||
|
|
32
src/actor.rs
32
src/actor.rs
|
@ -1,4 +1,7 @@
|
|||
use crate::clipboard;
|
||||
use crate::config::Action;
|
||||
|
||||
use crate::config::CONFIG;
|
||||
use crate::env_var;
|
||||
use crate::extractor;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
|
@ -6,8 +9,6 @@ use crate::finder::Finder;
|
|||
use crate::shell;
|
||||
use crate::shell::ShellSpawnError;
|
||||
use crate::structures::cheat::{Suggestion, VariableMap};
|
||||
use crate::structures::config::Action;
|
||||
use crate::structures::config::Config;
|
||||
use crate::writer;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
@ -17,7 +18,6 @@ use std::process::Stdio;
|
|||
|
||||
fn prompt_finder(
|
||||
variable_name: &str,
|
||||
config: &Config,
|
||||
suggestion: Option<&Suggestion>,
|
||||
variable_count: usize,
|
||||
) -> Result<String> {
|
||||
|
@ -66,7 +66,7 @@ fn prompt_finder(
|
|||
};
|
||||
|
||||
let overrides = {
|
||||
let mut o = config.fzf_overrides.clone();
|
||||
let mut o = CONFIG.fzf_overrides_var();
|
||||
if let Some(io) = initial_opts {
|
||||
if io.overrides.is_some() {
|
||||
o = io.overrides.clone()
|
||||
|
@ -110,8 +110,8 @@ NAVIEOF
|
|||
opts.suggestion_type = SuggestionType::Disabled;
|
||||
};
|
||||
|
||||
let (output, _, _) = config
|
||||
.finder
|
||||
let (output, _, _) = CONFIG
|
||||
.finder()
|
||||
.call(opts, |stdin, _| {
|
||||
stdin
|
||||
.write_all(suggestions.as_bytes())
|
||||
|
@ -130,12 +130,7 @@ fn unique_result_count(results: &[&str]) -> usize {
|
|||
vars.len()
|
||||
}
|
||||
|
||||
fn replace_variables_from_snippet(
|
||||
snippet: &str,
|
||||
tags: &str,
|
||||
variables: VariableMap,
|
||||
config: &Config,
|
||||
) -> Result<String> {
|
||||
fn replace_variables_from_snippet(snippet: &str, tags: &str, variables: VariableMap) -> Result<String> {
|
||||
let mut interpolated_snippet = String::from(snippet);
|
||||
let variables_found: Vec<&str> = writer::VAR_REGEX.find_iter(snippet).map(|m| m.as_str()).collect();
|
||||
let variable_count = unique_result_count(&variables_found);
|
||||
|
@ -150,11 +145,10 @@ fn replace_variables_from_snippet(
|
|||
e
|
||||
} else if let Some(suggestion) = variables.get_suggestion(&tags, &variable_name) {
|
||||
let mut new_suggestion = suggestion.clone();
|
||||
new_suggestion.0 =
|
||||
replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone(), config)?;
|
||||
prompt_finder(variable_name, &config, Some(&new_suggestion), variable_count)?
|
||||
new_suggestion.0 = replace_variables_from_snippet(&new_suggestion.0, tags, variables.clone())?;
|
||||
prompt_finder(variable_name, Some(&new_suggestion), variable_count)?
|
||||
} else {
|
||||
prompt_finder(variable_name, &config, None, variable_count)?
|
||||
prompt_finder(variable_name, None, variable_count)?
|
||||
};
|
||||
|
||||
env_var::set(env_variable_name, &value);
|
||||
|
@ -172,7 +166,6 @@ fn replace_variables_from_snippet(
|
|||
// TODO: make it depend on less inputs
|
||||
pub fn act(
|
||||
extractions: Result<extractor::Output>,
|
||||
config: Config,
|
||||
files: Vec<String>,
|
||||
variables: Option<VariableMap>,
|
||||
) -> Result<()> {
|
||||
|
@ -180,7 +173,7 @@ pub fn act(
|
|||
|
||||
if key == "ctrl-o" {
|
||||
edit::edit_file(Path::new(&files[file_index.expect("No files found")]))
|
||||
.expect("Cound not open file in external editor");
|
||||
.expect("Could not open file in external editor");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -193,12 +186,11 @@ pub fn act(
|
|||
snippet,
|
||||
tags,
|
||||
variables.expect("No variables received from finder"),
|
||||
&config,
|
||||
)
|
||||
.context("Failed to replace variables from snippet")?,
|
||||
);
|
||||
|
||||
match config.action() {
|
||||
match CONFIG.action() {
|
||||
Action::Print => {
|
||||
println!("{}", interpolated_snippet);
|
||||
}
|
||||
|
|
|
@ -25,5 +25,5 @@ impl FileAnIssue {
|
|||
}
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
navi::handle_config(navi::config_from_env()).map_err(|e| FileAnIssue::new(e).into())
|
||||
navi::handle().map_err(|e| FileAnIssue::new(e).into())
|
||||
}
|
||||
|
|
14
src/cheat_variable.rs
Normal file
14
src/cheat_variable.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use crate::shell::{self, ShellSpawnError};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn map_expand() -> Result<()> {
|
||||
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
|
||||
shell::command()
|
||||
.arg("-c")
|
||||
.arg(cmd)
|
||||
.spawn()
|
||||
.map_err(|e| ShellSpawnError::new(cmd, e))?
|
||||
.wait()?;
|
||||
Ok(())
|
||||
}
|
|
@ -3,25 +3,15 @@ use crate::finder::FinderChoice;
|
|||
use crate::handler::func::Func;
|
||||
use crate::handler::info::Info;
|
||||
use crate::shell::Shell;
|
||||
|
||||
use clap::{crate_version, AppSettings, Clap};
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
const FINDER_POSSIBLE_VALUES: &[&str] = &[&"fzf", &"skim"];
|
||||
const SHELL_POSSIBLE_VALUES: &[&str] = &[&"bash", &"zsh", &"fish"];
|
||||
const WIDGET_POSSIBLE_VALUES: &[&str] = &[&"bash", &"zsh", &"fish"];
|
||||
const FUNC_POSSIBLE_VALUES: &[&str] = &[&"url::open", &"welcome", &"widget::last_command", &"map::expand"];
|
||||
const INFO_POSSIBLE_VALUES: &[&str] = &[&"cheats-path"];
|
||||
|
||||
impl FromStr for FinderChoice {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"fzf" => Ok(FinderChoice::Fzf),
|
||||
"skim" => Ok(FinderChoice::Skim),
|
||||
_ => Err("no match"),
|
||||
}
|
||||
}
|
||||
}
|
||||
const INFO_POSSIBLE_VALUES: &[&str] = &[&"cheats-path", "config-path"];
|
||||
|
||||
impl FromStr for Shell {
|
||||
type Err = &'static str;
|
||||
|
@ -56,6 +46,7 @@ impl FromStr for Info {
|
|||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"cheats-path" => Ok(Info::CheatsPath),
|
||||
"config-path" => Ok(Info::ConfigPath),
|
||||
_ => Err("no match"),
|
||||
}
|
||||
}
|
||||
|
@ -94,14 +85,14 @@ EXAMPLES:
|
|||
#[clap(setting = AppSettings::ColoredHelp)]
|
||||
#[clap(setting = AppSettings::AllowLeadingHyphen)]
|
||||
#[clap(version = crate_version!())]
|
||||
pub struct Config {
|
||||
/// List of :-separated paths containing .cheat files
|
||||
pub(super) struct ClapConfig {
|
||||
/// Colon-separated list of paths containing .cheat files
|
||||
#[clap(short, long, env = env_var::PATH)]
|
||||
pub path: Option<String>,
|
||||
|
||||
/// Instead of executing a snippet, prints it to stdout
|
||||
#[clap(long)]
|
||||
print: bool,
|
||||
pub print: bool,
|
||||
|
||||
/// Returns the best match
|
||||
#[clap(long)]
|
||||
|
@ -109,36 +100,42 @@ pub struct Config {
|
|||
|
||||
/// Search for cheatsheets using the tldr-pages repository
|
||||
#[clap(long)]
|
||||
tldr: Option<String>,
|
||||
pub tldr: Option<String>,
|
||||
|
||||
/// [Experimental] Comma-separated list that acts as filter for tags. Parts starting with ! represent negation
|
||||
#[clap(long)]
|
||||
tag_rules: Option<String>,
|
||||
pub tag_rules: Option<String>,
|
||||
|
||||
/// Search for cheatsheets using the cheat.sh repository
|
||||
#[clap(long)]
|
||||
cheatsh: Option<String>,
|
||||
pub cheatsh: Option<String>,
|
||||
|
||||
/// Query
|
||||
#[clap(short, long)]
|
||||
query: Option<String>,
|
||||
pub query: Option<String>,
|
||||
|
||||
/// finder overrides for cheat selection
|
||||
#[clap(long, env = env_var::FZF_OVERRIDES)]
|
||||
/// Finder overrides for snippet selection
|
||||
#[clap(long)]
|
||||
pub fzf_overrides: Option<String>,
|
||||
|
||||
/// finder overrides for variable selection
|
||||
#[clap(long, env = env_var::FZF_OVERRIDES_VAR)]
|
||||
/// Finder overrides for variable selection
|
||||
#[clap(long)]
|
||||
pub fzf_overrides_var: Option<String>,
|
||||
|
||||
/// which finder application to use
|
||||
#[clap(long, env = env_var::FINDER, default_value = "fzf", possible_values = FINDER_POSSIBLE_VALUES, case_insensitive = true)]
|
||||
pub finder: FinderChoice,
|
||||
/// Finder application to use
|
||||
#[clap(long, possible_values = FINDER_POSSIBLE_VALUES, case_insensitive = true)]
|
||||
pub finder: Option<FinderChoice>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub cmd: Option<Command>,
|
||||
}
|
||||
|
||||
impl ClapConfig {
|
||||
pub fn new() -> Self {
|
||||
Self::parse()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
pub enum Command {
|
||||
/// [Experimental] Performs ad-hoc, internal functions provided by navi
|
||||
|
@ -172,7 +169,7 @@ pub enum Command {
|
|||
},
|
||||
/// Outputs shell widget source code
|
||||
Widget {
|
||||
#[clap(possible_values = SHELL_POSSIBLE_VALUES, case_insensitive = true, default_value = "bash")]
|
||||
#[clap(possible_values = WIDGET_POSSIBLE_VALUES, case_insensitive = true, default_value = "bash")]
|
||||
shell: Shell,
|
||||
},
|
||||
/// Shows info
|
||||
|
@ -204,57 +201,13 @@ pub enum Action {
|
|||
Execute,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn source(&self) -> Source {
|
||||
if let Some(query) = self.tldr.clone() {
|
||||
Source::Tldr(query)
|
||||
} else if let Some(query) = self.cheatsh.clone() {
|
||||
Source::Cheats(query)
|
||||
} else {
|
||||
Source::Filesystem(self.path.clone(), self.tag_rules.clone())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn action(&self) -> Action {
|
||||
if self.print {
|
||||
Action::Print
|
||||
} else {
|
||||
Action::Execute
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_query(&self) -> Option<String> {
|
||||
let q = self.query.clone();
|
||||
if q.is_some() {
|
||||
return q;
|
||||
}
|
||||
if self.best_match {
|
||||
match self.source() {
|
||||
Source::Tldr(q) => Some(q),
|
||||
Source::Cheats(q) => Some(q),
|
||||
_ => Some(String::from("")),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config_from_env() -> Config {
|
||||
Config::parse()
|
||||
}
|
||||
|
||||
pub fn config_from_iter(args: Vec<&str>) -> Config {
|
||||
Config::parse_from(args)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_shell_possible_values() {
|
||||
for v in SHELL_POSSIBLE_VALUES {
|
||||
fn test_widget_possible_values() {
|
||||
for v in WIDGET_POSSIBLE_VALUES {
|
||||
assert_eq!(true, Shell::from_str(v).is_ok())
|
||||
}
|
||||
}
|
136
src/config/mod.rs
Normal file
136
src/config/mod.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
mod cli;
|
||||
mod yaml;
|
||||
|
||||
use crate::finder::FinderChoice;
|
||||
|
||||
use crate::terminal::style::Color;
|
||||
pub use cli::*;
|
||||
use std::process;
|
||||
|
||||
use yaml::YamlConfig;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CONFIG: Config = Config::new();
|
||||
}
|
||||
pub struct Config {
|
||||
yaml: YamlConfig,
|
||||
clap: ClapConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
match YamlConfig::get() {
|
||||
Ok(yaml) => Self {
|
||||
yaml,
|
||||
clap: ClapConfig::new(),
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("Error parsing config file: {}", e);
|
||||
process::exit(42)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best_match(&self) -> bool {
|
||||
self.clap.best_match
|
||||
}
|
||||
|
||||
pub fn cmd(&self) -> Option<&Command> {
|
||||
self.clap.cmd.as_ref()
|
||||
}
|
||||
|
||||
pub fn source(&self) -> Source {
|
||||
if let Some(query) = self.clap.tldr.clone() {
|
||||
Source::Tldr(query)
|
||||
} else if let Some(query) = self.clap.cheatsh.clone() {
|
||||
Source::Cheats(query)
|
||||
} else {
|
||||
Source::Filesystem(self.path(), self.tag_rules())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Option<String> {
|
||||
self.clap.path.clone().or_else(|| self.yaml.cheats.path.clone())
|
||||
}
|
||||
|
||||
pub fn finder(&self) -> FinderChoice {
|
||||
self.clap.finder.unwrap_or(self.yaml.finder.command)
|
||||
}
|
||||
|
||||
pub fn fzf_overrides(&self) -> Option<String> {
|
||||
self.clap
|
||||
.fzf_overrides
|
||||
.clone()
|
||||
.or_else(|| self.yaml.finder.overrides.clone())
|
||||
}
|
||||
|
||||
pub fn fzf_overrides_var(&self) -> Option<String> {
|
||||
self.clap
|
||||
.fzf_overrides_var
|
||||
.clone()
|
||||
.or_else(|| self.yaml.finder.overrides_var.clone())
|
||||
}
|
||||
|
||||
pub fn shell(&self) -> String {
|
||||
self.yaml.shell.command.clone()
|
||||
}
|
||||
|
||||
pub fn tag_rules(&self) -> Option<String> {
|
||||
self.clap
|
||||
.tag_rules
|
||||
.clone()
|
||||
.or_else(|| self.yaml.search.tags.clone())
|
||||
}
|
||||
|
||||
pub fn tag_color(&self) -> Color {
|
||||
self.yaml.style.tag.color.get()
|
||||
}
|
||||
|
||||
pub fn comment_color(&self) -> Color {
|
||||
self.yaml.style.comment.color.get()
|
||||
}
|
||||
|
||||
pub fn snippet_color(&self) -> Color {
|
||||
self.yaml.style.snippet.color.get()
|
||||
}
|
||||
|
||||
pub fn tag_width_percentage(&self) -> u16 {
|
||||
self.yaml.style.tag.width_percentage
|
||||
}
|
||||
|
||||
pub fn comment_width_percentage(&self) -> u16 {
|
||||
self.yaml.style.comment.width_percentage
|
||||
}
|
||||
|
||||
pub fn tag_min_width(&self) -> u16 {
|
||||
self.yaml.style.tag.min_width
|
||||
}
|
||||
|
||||
pub fn comment_min_width(&self) -> u16 {
|
||||
self.yaml.style.comment.min_width
|
||||
}
|
||||
|
||||
pub fn action(&self) -> Action {
|
||||
if self.clap.print {
|
||||
Action::Print
|
||||
} else {
|
||||
Action::Execute
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_query(&self) -> Option<String> {
|
||||
let q = self.clap.query.clone();
|
||||
if q.is_some() {
|
||||
return q;
|
||||
}
|
||||
if self.best_match() {
|
||||
match self.source() {
|
||||
Source::Tldr(q) => Some(q),
|
||||
Source::Cheats(q) => Some(q),
|
||||
_ => Some(String::from("")),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
176
src/config/yaml.rs
Normal file
176
src/config/yaml.rs
Normal file
|
@ -0,0 +1,176 @@
|
|||
use crate::env_var;
|
||||
use crate::filesystem::default_config_pathbuf;
|
||||
use crate::finder::FinderChoice;
|
||||
use crate::fs;
|
||||
use crate::terminal::style;
|
||||
use anyhow::Result;
|
||||
use serde::{de, Deserialize};
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Color(#[serde(deserialize_with = "color_deserialize")] style::Color);
|
||||
|
||||
impl Color {
|
||||
pub fn from_str(color: &str) -> Self {
|
||||
Self(style::Color::from_str(color).unwrap_or(style::Color::White))
|
||||
}
|
||||
|
||||
pub fn get(&self) -> style::Color {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
fn color_deserialize<'de, D>(deserializer: D) -> Result<style::Color, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
style::Color::from_str(&s).map_err(|_| de::Error::custom(format!("Failed to deserialize color: {}", s)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ColorWidth {
|
||||
pub color: Color,
|
||||
pub width_percentage: u16,
|
||||
pub min_width: u16,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Style {
|
||||
pub tag: ColorWidth,
|
||||
pub comment: ColorWidth,
|
||||
pub snippet: ColorWidth,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Finder {
|
||||
pub command: FinderChoice,
|
||||
pub overrides: Option<String>,
|
||||
pub overrides_var: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Cheats {
|
||||
pub path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Search {
|
||||
pub tags: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct Shell {
|
||||
pub command: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct YamlConfig {
|
||||
pub style: Style,
|
||||
pub finder: Finder,
|
||||
pub cheats: Cheats,
|
||||
pub search: Search,
|
||||
pub shell: Shell,
|
||||
}
|
||||
|
||||
impl YamlConfig {
|
||||
fn from_str(text: &str) -> Result<Self> {
|
||||
serde_yaml::from_str(&text).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
fn from_path(path: &Path) -> Result<Self> {
|
||||
let file = fs::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
serde_yaml::from_reader(reader).map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub fn get() -> Result<Self> {
|
||||
if let Ok(yaml) = env_var::get(env_var::CONFIG_YAML) {
|
||||
return Self::from_str(&yaml);
|
||||
}
|
||||
if let Ok(path_str) = env_var::get(env_var::CONFIG) {
|
||||
let p = PathBuf::from(path_str);
|
||||
return YamlConfig::from_path(&p);
|
||||
}
|
||||
if let Ok(p) = default_config_pathbuf() {
|
||||
if p.exists() {
|
||||
return YamlConfig::from_path(&p);
|
||||
}
|
||||
}
|
||||
Ok(YamlConfig::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorWidth {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
color: Color::from_str("white"),
|
||||
width_percentage: 26,
|
||||
min_width: 20,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
tag: ColorWidth {
|
||||
color: Color::from_str("cyan"),
|
||||
width_percentage: 26,
|
||||
min_width: 20,
|
||||
},
|
||||
comment: ColorWidth {
|
||||
color: Color::from_str("blue"),
|
||||
width_percentage: 42,
|
||||
min_width: 45,
|
||||
},
|
||||
snippet: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Finder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
command: env_var::get(env_var::FINDER)
|
||||
.ok()
|
||||
.and_then(|x| FinderChoice::from_str(&x).ok())
|
||||
.unwrap_or(FinderChoice::Fzf),
|
||||
overrides: env_var::get(env_var::FZF_OVERRIDES).ok(),
|
||||
overrides_var: env_var::get(env_var::FZF_OVERRIDES_VAR).ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Cheats {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: env_var::get(env_var::PATH).ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Search {
|
||||
fn default() -> Self {
|
||||
Self { tags: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Shell {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
command: env_var::get(env_var::SHELL)
|
||||
.ok()
|
||||
.unwrap_or_else(|| "bash".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,13 +11,6 @@ pub const PREVIEW_COLUMN: &str = "NAVI_PREVIEW_COLUMN";
|
|||
pub const PREVIEW_DELIMITER: &str = "NAVI_PREVIEW_DELIMITER";
|
||||
pub const PREVIEW_MAP: &str = "NAVI_PREVIEW_MAP";
|
||||
|
||||
pub const TAG_COLOR: &str = "NAVI_TAG_COLOR";
|
||||
pub const COMMENT_COLOR: &str = "NAVI_COMMENT_COLOR";
|
||||
pub const SNIPPET_COLOR: &str = "NAVI_SNIPPET_COLOR";
|
||||
|
||||
pub const TAG_WIDTH: &str = "NAVI_TAG_WIDTH";
|
||||
pub const COMMENT_WIDTH: &str = "NAVI_COMMENT_WIDTH";
|
||||
|
||||
pub const PATH: &str = "NAVI_PATH";
|
||||
pub const FZF_OVERRIDES: &str = "NAVI_FZF_OVERRIDES";
|
||||
pub const FZF_OVERRIDES_VAR: &str = "NAVI_FZF_OVERRIDES_VAR";
|
||||
|
@ -25,6 +18,9 @@ pub const FINDER: &str = "NAVI_FINDER";
|
|||
|
||||
pub const SHELL: &str = "NAVI_SHELL";
|
||||
|
||||
pub const CONFIG: &str = "NAVI_CONFIG";
|
||||
pub const CONFIG_YAML: &str = "NAVI_CONFIG_YAML";
|
||||
|
||||
pub fn parse<T: FromStr>(varname: &str) -> Option<T> {
|
||||
if let Ok(x) = env::var(varname) {
|
||||
x.parse::<T>().ok()
|
||||
|
|
|
@ -33,6 +33,15 @@ pub fn default_cheat_pathbuf() -> Result<PathBuf> {
|
|||
Ok(pathbuf)
|
||||
}
|
||||
|
||||
pub fn default_config_pathbuf() -> Result<PathBuf> {
|
||||
let base_dirs = BaseDirs::new().ok_or_else(|| anyhow!("Unable to get base dirs"))?;
|
||||
|
||||
let mut pathbuf = PathBuf::from(base_dirs.config_dir());
|
||||
pathbuf.push("navi");
|
||||
pathbuf.push("config.yaml");
|
||||
Ok(pathbuf)
|
||||
}
|
||||
|
||||
pub fn cheat_paths(path: Option<String>) -> Result<String> {
|
||||
if let Some(p) = path {
|
||||
Ok(p)
|
||||
|
@ -124,7 +133,8 @@ impl fetcher::Fetcher for Fetcher {
|
|||
files.push(file.clone());
|
||||
let index = files.len() - 1;
|
||||
let read_file_result = {
|
||||
let lines = read_lines(&file)?;
|
||||
let path = PathBuf::from(&file);
|
||||
let lines = read_lines(&path)?;
|
||||
parser::read_lines(
|
||||
lines,
|
||||
&file,
|
||||
|
|
|
@ -1,24 +1,36 @@
|
|||
use crate::shell;
|
||||
use crate::config::CONFIG;
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::writer;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::process::{self, Output};
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
mod post;
|
||||
pub mod structures;
|
||||
|
||||
pub use post::process;
|
||||
use serde::Deserialize;
|
||||
use std::str::FromStr;
|
||||
use structures::Opts;
|
||||
use structures::SuggestionType;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, Deserialize)]
|
||||
pub enum FinderChoice {
|
||||
Fzf,
|
||||
Skim,
|
||||
}
|
||||
|
||||
impl FromStr for FinderChoice {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"fzf" => Ok(FinderChoice::Fzf),
|
||||
"skim" => Ok(FinderChoice::Skim),
|
||||
_ => Err("no match"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Finder {
|
||||
fn call<F>(&self, opts: Opts, stdin_fn: F) -> Result<(String, Option<VariableMap>, Vec<String>)>
|
||||
where
|
||||
|
@ -142,7 +154,7 @@ impl Finder for FinderChoice {
|
|||
}
|
||||
|
||||
let child = command
|
||||
.env("SHELL", &*shell::SHELL)
|
||||
.env("SHELL", CONFIG.shell())
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn();
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
use crate::config::Config;
|
||||
use crate::filesystem;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Opts {
|
||||
pub query: Option<String>,
|
||||
|
@ -46,3 +50,26 @@ pub enum SuggestionType {
|
|||
/// initial snippet selection
|
||||
SnippetSelection,
|
||||
}
|
||||
|
||||
impl Opts {
|
||||
pub fn from_config(config: &Config) -> Result<Opts> {
|
||||
let opts = Opts {
|
||||
preview: Some(format!("{} preview {{}}", filesystem::exe_string()?)),
|
||||
overrides: config.fzf_overrides(),
|
||||
suggestion_type: SuggestionType::SnippetSelection,
|
||||
query: if config.best_match() {
|
||||
None
|
||||
} else {
|
||||
config.get_query()
|
||||
},
|
||||
filter: if config.best_match() {
|
||||
config.get_query()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(opts)
|
||||
}
|
||||
}
|
||||
|
|
16
src/fs.rs
16
src/fs.rs
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Context, Error, Result};
|
||||
use core::fmt::Display;
|
||||
|
||||
use remove_dir_all::remove_dir_all;
|
||||
use std::fmt::Debug;
|
||||
use std::fs::{self, create_dir_all, File};
|
||||
|
@ -19,11 +19,15 @@ pub struct UnreadableDir {
|
|||
source: anyhow::Error,
|
||||
}
|
||||
|
||||
pub fn read_lines<P>(filename: P) -> Result<impl Iterator<Item = Result<String>>>
|
||||
where
|
||||
P: AsRef<Path> + Display + Copy,
|
||||
{
|
||||
let file = File::open(filename).with_context(|| format!("Failed to open file {}", filename))?;
|
||||
pub fn open(filename: &Path) -> Result<File> {
|
||||
File::open(filename).with_context(|| {
|
||||
let x = pathbuf_to_string(filename).unwrap_or_else(|e| format!("Unable to get path string: {}", e));
|
||||
format!("Failed to open file {}", &x)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_lines(filename: &Path) -> Result<impl Iterator<Item = Result<String>>> {
|
||||
let file = open(filename)?;
|
||||
Ok(io::BufReader::new(file)
|
||||
.lines()
|
||||
.map(|line| line.map_err(Error::from)))
|
||||
|
|
|
@ -1,44 +1,24 @@
|
|||
use crate::actor;
|
||||
use crate::cheatsh;
|
||||
use crate::config::Source;
|
||||
use crate::config::CONFIG;
|
||||
use crate::extractor;
|
||||
use crate::filesystem;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::structures::Opts as FinderOpts;
|
||||
use crate::finder::Finder;
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::structures::config::Config;
|
||||
use crate::structures::config::Source;
|
||||
use crate::structures::fetcher::Fetcher;
|
||||
use crate::tldr;
|
||||
use crate::welcome;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
fn gen_core_finder_opts(config: &Config) -> Result<FinderOpts> {
|
||||
let opts = FinderOpts {
|
||||
preview: Some(format!("{} preview {{}}", filesystem::exe_string()?)),
|
||||
overrides: config.fzf_overrides.clone(),
|
||||
suggestion_type: SuggestionType::SnippetSelection,
|
||||
query: if config.best_match {
|
||||
None
|
||||
} else {
|
||||
config.get_query()
|
||||
},
|
||||
filter: if config.best_match {
|
||||
config.get_query()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(opts)
|
||||
}
|
||||
|
||||
pub fn main(config: Config) -> Result<()> {
|
||||
let opts = gen_core_finder_opts(&config).context("Failed to generate finder options")?;
|
||||
pub fn main() -> Result<()> {
|
||||
let config = &CONFIG;
|
||||
let opts = FinderOpts::from_config(&config)?;
|
||||
|
||||
let (raw_selection, variables, files) = config
|
||||
.finder
|
||||
.finder()
|
||||
.call(opts, |stdin, files| {
|
||||
let fetcher: Box<dyn Fetcher> = match config.source() {
|
||||
Source::Cheats(query) => Box::new(cheatsh::Fetcher::new(query)),
|
||||
|
@ -59,13 +39,13 @@ pub fn main(config: Config) -> Result<()> {
|
|||
})
|
||||
.context("Failed getting selection and variables from finder")?;
|
||||
|
||||
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match);
|
||||
let extractions = extractor::extract_from_selections(&raw_selection, config.best_match());
|
||||
|
||||
if extractions.is_err() {
|
||||
return main(config);
|
||||
return main();
|
||||
}
|
||||
|
||||
actor::act(extractions, config, files, variables)?;
|
||||
actor::act(extractions, files, variables)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::handler;
|
||||
use crate::shell::{self, ShellSpawnError};
|
||||
use crate::structures::config;
|
||||
use crate::cheat_variable;
|
||||
|
||||
use crate::shell::{self};
|
||||
|
||||
use crate::url;
|
||||
use crate::welcome;
|
||||
|
||||
use anyhow::Result;
|
||||
use std::io::{self, Read};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Func {
|
||||
|
@ -16,58 +18,8 @@ pub enum Func {
|
|||
pub fn main(func: &Func, args: Vec<String>) -> Result<()> {
|
||||
match func {
|
||||
Func::UrlOpen => url::open(args),
|
||||
Func::Welcome => handler::handle_config(config::config_from_iter(
|
||||
"navi --path /tmp/navi/irrelevant".split(' ').collect(),
|
||||
)),
|
||||
Func::WidgetLastCommand => widget_last_command(),
|
||||
Func::MapExpand => map_expand(),
|
||||
Func::Welcome => welcome::main(),
|
||||
Func::WidgetLastCommand => shell::widget_last_command(),
|
||||
Func::MapExpand => cheat_variable::map_expand(),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_expand() -> Result<()> {
|
||||
let cmd = r#"sed -e 's/^.*$/"&"/' | tr '\n' ' '"#;
|
||||
shell::command()
|
||||
.arg("-c")
|
||||
.arg(cmd)
|
||||
.spawn()
|
||||
.map_err(|e| ShellSpawnError::new(cmd, e))?
|
||||
.wait()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn widget_last_command() -> Result<()> {
|
||||
let mut text = String::new();
|
||||
io::stdin().read_to_string(&mut text)?;
|
||||
|
||||
let replacements = vec![("|", "ඛ"), ("||", "ග"), ("&&", "ඝ")];
|
||||
|
||||
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
|
||||
|
||||
for p in parts {
|
||||
for (pattern, escaped) in replacements.clone() {
|
||||
if p.contains(pattern) && p != pattern {
|
||||
let replacement = p.replace(pattern, escaped);
|
||||
text = text.replace(&p, &replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut extracted = text.clone();
|
||||
for (pattern, _) in replacements.clone() {
|
||||
let mut new_parts = text.rsplit(pattern);
|
||||
if let Some(extracted_attempt) = new_parts.next() {
|
||||
if extracted_attempt.len() <= extracted.len() {
|
||||
extracted = extracted_attempt.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (pattern, escaped) in replacements.clone() {
|
||||
text = text.replace(&escaped, &pattern);
|
||||
extracted = extracted.replace(&escaped, &pattern);
|
||||
}
|
||||
|
||||
println!("{}", extracted.trim_start());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
use crate::filesystem::default_cheat_pathbuf;
|
||||
use crate::filesystem;
|
||||
use crate::fs::pathbuf_to_string;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Info {
|
||||
CheatsPath,
|
||||
ConfigPath,
|
||||
}
|
||||
|
||||
pub fn main(info: &Info) -> Result<()> {
|
||||
match info {
|
||||
Info::CheatsPath => println!("{}", pathbuf_to_string(&default_cheat_pathbuf()?)?),
|
||||
Info::CheatsPath => println!("{}", pathbuf_to_string(&filesystem::default_cheat_pathbuf()?)?),
|
||||
Info::ConfigPath => println!("{}", pathbuf_to_string(&filesystem::default_config_pathbuf()?)?),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,27 +3,28 @@ pub mod func;
|
|||
pub mod info;
|
||||
pub mod preview;
|
||||
pub mod preview_var;
|
||||
pub mod repo;
|
||||
pub mod repo_add;
|
||||
pub mod repo_browse;
|
||||
pub mod shell;
|
||||
|
||||
use crate::config::Command::{Fn, Info, Preview, PreviewVar, Repo, Widget};
|
||||
use crate::config::{RepoCommand, CONFIG};
|
||||
use crate::handler;
|
||||
use crate::structures::config::Command::{Fn, Info, Preview, PreviewVar, Repo, Widget};
|
||||
use crate::structures::config::{Config, RepoCommand};
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn handle_config(config: Config) -> Result<()> {
|
||||
match config.cmd.as_ref() {
|
||||
None => handler::core::main(config),
|
||||
pub fn handle() -> Result<()> {
|
||||
match CONFIG.cmd() {
|
||||
None => handler::core::main(),
|
||||
|
||||
Some(c) => match c {
|
||||
Preview { line } => handler::preview::main(&line),
|
||||
Preview { line } => handler::preview::main(line),
|
||||
|
||||
PreviewVar {
|
||||
selection,
|
||||
query,
|
||||
variable,
|
||||
} => handler::preview_var::main(&selection, &query, &variable),
|
||||
} => handler::preview_var::main(selection, query, variable),
|
||||
|
||||
Widget { shell } => handler::shell::main(shell).context("Failed to print shell widget code"),
|
||||
|
||||
|
@ -36,13 +37,16 @@ pub fn handle_config(config: Config) -> Result<()> {
|
|||
|
||||
Repo { cmd } => match cmd {
|
||||
RepoCommand::Add { uri } => {
|
||||
handler::repo::add(uri.clone(), &config.finder)
|
||||
handler::repo_add::main(uri.clone())
|
||||
.with_context(|| format!("Failed to import cheatsheets from `{}`", uri))?;
|
||||
handler::core::main(config)
|
||||
handler::core::main()
|
||||
}
|
||||
RepoCommand::Browse => {
|
||||
handler::repo::browse(&config.finder).context("Failed to browse featured cheatsheets")?;
|
||||
handler::core::main(config)
|
||||
let repo =
|
||||
handler::repo_browse::main().context("Failed to browse featured cheatsheets")?;
|
||||
handler::repo_add::main(repo.clone())
|
||||
.with_context(|| format!("Failed to import cheatsheets from `{}`", repo))?;
|
||||
handler::core::main()
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::ui;
|
||||
use crate::writer;
|
||||
use anyhow::Result;
|
||||
|
@ -12,13 +13,15 @@ fn extract_elements(argstr: &str) -> (&str, &str, &str) {
|
|||
}
|
||||
|
||||
pub fn main(line: &str) -> Result<()> {
|
||||
// dbg!(CONFIG.comment_color());
|
||||
|
||||
let (tags, comment, snippet) = extract_elements(line);
|
||||
|
||||
println!(
|
||||
"{comment} {tags} \n{snippet}",
|
||||
comment = ui::style(comment).with(*ui::COMMENT_COLOR),
|
||||
tags = ui::style(format!("[{}]", tags)).with(*ui::TAG_COLOR),
|
||||
snippet = ui::style(writer::fix_newlines(snippet)).with(*ui::SNIPPET_COLOR),
|
||||
comment = ui::style(comment).with(CONFIG.comment_color()),
|
||||
tags = ui::style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
||||
snippet = ui::style(writer::fix_newlines(snippet)).with(CONFIG.snippet_color()),
|
||||
);
|
||||
|
||||
process::exit(0)
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::env_var;
|
||||
use crate::finder;
|
||||
|
||||
use crate::terminal::style::style;
|
||||
use crate::ui;
|
||||
use crate::writer;
|
||||
use anyhow::Result;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::iter;
|
||||
use std::process;
|
||||
|
@ -18,8 +16,8 @@ pub fn main(selection: &str, query: &str, variable: &str) -> Result<()> {
|
|||
let delimiter = env_var::get(env_var::PREVIEW_DELIMITER).ok();
|
||||
let map = env_var::get(env_var::PREVIEW_MAP).ok();
|
||||
|
||||
let active_color = *ui::TAG_COLOR;
|
||||
let inactive_color = *ui::COMMENT_COLOR;
|
||||
let active_color = CONFIG.tag_color();
|
||||
let inactive_color = CONFIG.comment_color();
|
||||
|
||||
let mut colored_snippet = String::from(&snippet);
|
||||
let mut visited_vars: HashSet<&str> = HashSet::new();
|
||||
|
@ -28,8 +26,8 @@ pub fn main(selection: &str, query: &str, variable: &str) -> Result<()> {
|
|||
|
||||
println!(
|
||||
"{comment} {tags}",
|
||||
comment = style(comment).with(*ui::COMMENT_COLOR),
|
||||
tags = style(format!("[{}]", tags)).with(*ui::TAG_COLOR),
|
||||
comment = style(comment).with(CONFIG.comment_color()),
|
||||
tags = style(format!("[{}]", tags)).with(CONFIG.tag_color()),
|
||||
);
|
||||
|
||||
let bracketed_current_variable = format!("<{}>", variable);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::filesystem;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::{Finder, FinderChoice};
|
||||
|
@ -9,51 +10,7 @@ use std::fs;
|
|||
use std::io::Write;
|
||||
use std::path;
|
||||
|
||||
pub fn browse(finder: &FinderChoice) -> Result<()> {
|
||||
let repo_pathbuf = {
|
||||
let mut p = filesystem::tmp_pathbuf()?;
|
||||
p.push("featured");
|
||||
p
|
||||
};
|
||||
|
||||
let repo_path_str = pathbuf_to_string(&repo_pathbuf)?;
|
||||
|
||||
let _ = filesystem::remove_dir(&repo_pathbuf);
|
||||
filesystem::create_dir(&repo_pathbuf)?;
|
||||
|
||||
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
||||
git::shallow_clone(repo_url.as_str(), &repo_path_str)
|
||||
.with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
||||
|
||||
let feature_repos_file = {
|
||||
let mut p = repo_pathbuf.clone();
|
||||
p.push("featured_repos.txt");
|
||||
p
|
||||
};
|
||||
|
||||
let repos = fs::read_to_string(&feature_repos_file).context("Unable to fetch featured repositories")?;
|
||||
|
||||
let opts = FinderOpts {
|
||||
column: Some(1),
|
||||
suggestion_type: SuggestionType::SingleSelection,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (repo, _, _) = finder
|
||||
.call(opts, |stdin, _| {
|
||||
stdin
|
||||
.write_all(repos.as_bytes())
|
||||
.context("Unable to prompt featured repositories")?;
|
||||
Ok(None)
|
||||
})
|
||||
.context("Failed to get repo URL from finder")?;
|
||||
|
||||
filesystem::remove_dir(&repo_pathbuf)?;
|
||||
|
||||
add(repo, finder)
|
||||
}
|
||||
|
||||
pub fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
||||
fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
||||
let opts = FinderOpts {
|
||||
column: Some(1),
|
||||
header: Some("Do you want to import all files from this repo?".to_string()),
|
||||
|
@ -76,8 +33,10 @@ pub fn ask_if_should_import_all(finder: &FinderChoice) -> Result<bool> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn add(uri: String, finder: &FinderChoice) -> Result<()> {
|
||||
let should_import_all = ask_if_should_import_all(finder).unwrap_or(false);
|
||||
pub fn main(uri: String) -> Result<()> {
|
||||
let finder = CONFIG.finder();
|
||||
|
||||
let should_import_all = ask_if_should_import_all(&finder).unwrap_or(false);
|
||||
let (actual_uri, user, repo) = git::meta(uri.as_str());
|
||||
|
||||
let cheat_pathbuf = filesystem::default_cheat_pathbuf()?;
|
56
src/handler/repo_browse.rs
Normal file
56
src/handler/repo_browse.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::filesystem;
|
||||
use crate::finder::structures::{Opts as FinderOpts, SuggestionType};
|
||||
use crate::finder::Finder;
|
||||
use crate::fs::pathbuf_to_string;
|
||||
use crate::git;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn main() -> Result<String> {
|
||||
let finder = CONFIG.finder();
|
||||
|
||||
let repo_pathbuf = {
|
||||
let mut p = filesystem::tmp_pathbuf()?;
|
||||
p.push("featured");
|
||||
p
|
||||
};
|
||||
|
||||
let repo_path_str = pathbuf_to_string(&repo_pathbuf)?;
|
||||
|
||||
let _ = filesystem::remove_dir(&repo_pathbuf);
|
||||
filesystem::create_dir(&repo_pathbuf)?;
|
||||
|
||||
let (repo_url, _, _) = git::meta("denisidoro/cheats");
|
||||
git::shallow_clone(repo_url.as_str(), &repo_path_str)
|
||||
.with_context(|| format!("Failed to clone `{}`", repo_url))?;
|
||||
|
||||
let feature_repos_file = {
|
||||
let mut p = repo_pathbuf.clone();
|
||||
p.push("featured_repos.txt");
|
||||
p
|
||||
};
|
||||
|
||||
let repos = fs::read_to_string(&feature_repos_file).context("Unable to fetch featured repositories")?;
|
||||
|
||||
let opts = FinderOpts {
|
||||
column: Some(1),
|
||||
suggestion_type: SuggestionType::SingleSelection,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (repo, _, _) = finder
|
||||
.call(opts, |stdin, _| {
|
||||
stdin
|
||||
.write_all(repos.as_bytes())
|
||||
.context("Unable to prompt featured repositories")?;
|
||||
Ok(None)
|
||||
})
|
||||
.context("Failed to get repo URL from finder")?;
|
||||
|
||||
filesystem::remove_dir(&repo_pathbuf)?;
|
||||
|
||||
Ok(repo)
|
||||
}
|
|
@ -4,8 +4,10 @@ extern crate lazy_static;
|
|||
extern crate anyhow;
|
||||
|
||||
mod actor;
|
||||
mod cheat_variable;
|
||||
mod cheatsh;
|
||||
mod clipboard;
|
||||
mod config;
|
||||
mod env_var;
|
||||
mod extractor;
|
||||
mod filesystem;
|
||||
|
@ -24,5 +26,4 @@ mod url;
|
|||
mod welcome;
|
||||
mod writer;
|
||||
|
||||
pub use handler::handle_config;
|
||||
pub use structures::config::{config_from_env, config_from_iter};
|
||||
pub use handler::handle;
|
||||
|
|
50
src/shell.rs
50
src/shell.rs
|
@ -1,15 +1,10 @@
|
|||
use crate::env_var;
|
||||
use crate::config::CONFIG;
|
||||
use anyhow::Result;
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Read};
|
||||
use std::process::Command;
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref IS_FISH: bool = env_var::get("SHELL")
|
||||
.unwrap_or_else(|_| "".to_string())
|
||||
.contains(&"fish");
|
||||
pub static ref SHELL: String = env_var::get(env_var::SHELL).unwrap_or_else(|_| "bash".to_string());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Shell {
|
||||
Bash,
|
||||
|
@ -38,5 +33,42 @@ impl ShellSpawnError {
|
|||
}
|
||||
|
||||
pub fn command() -> Command {
|
||||
Command::new(&*SHELL)
|
||||
Command::new(CONFIG.shell())
|
||||
}
|
||||
|
||||
pub fn widget_last_command() -> Result<()> {
|
||||
let mut text = String::new();
|
||||
io::stdin().read_to_string(&mut text)?;
|
||||
|
||||
let replacements = vec![("|", "ඛ"), ("||", "ග"), ("&&", "ඝ")];
|
||||
|
||||
let parts = shellwords::split(&text).unwrap_or_else(|_| text.split('|').map(|s| s.to_string()).collect());
|
||||
|
||||
for p in parts {
|
||||
for (pattern, escaped) in replacements.clone() {
|
||||
if p.contains(pattern) && p != pattern {
|
||||
let replacement = p.replace(pattern, escaped);
|
||||
text = text.replace(&p, &replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut extracted = text.clone();
|
||||
for (pattern, _) in replacements.clone() {
|
||||
let mut new_parts = text.rsplit(pattern);
|
||||
if let Some(extracted_attempt) = new_parts.next() {
|
||||
if extracted_attempt.len() <= extracted.len() {
|
||||
extracted = extracted_attempt.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (pattern, escaped) in replacements.clone() {
|
||||
text = text.replace(&escaped, &pattern);
|
||||
extracted = extracted.replace(&escaped, &pattern);
|
||||
}
|
||||
|
||||
println!("{}", extracted.trim_start());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod cheat;
|
||||
pub mod config;
|
||||
pub mod fetcher;
|
||||
pub mod item;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use anyhow::Result;
|
||||
pub use crossterm::style;
|
||||
use crossterm::terminal;
|
||||
use std::str::FromStr;
|
||||
|
||||
const FALLBACK_WIDTH: u16 = 80;
|
||||
|
||||
fn width_with_shell_out() -> u16 {
|
||||
fn width_with_shell_out() -> Result<u16> {
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
|
||||
|
@ -13,40 +15,53 @@ fn width_with_shell_out() -> u16 {
|
|||
.arg("/dev/stderr")
|
||||
.arg("size")
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.expect("Failed to execute stty")
|
||||
.output()?
|
||||
} else {
|
||||
Command::new("stty")
|
||||
.arg("size")
|
||||
.arg("-F")
|
||||
.arg("/dev/stderr")
|
||||
.stderr(Stdio::inherit())
|
||||
.output()
|
||||
.expect("Failed to execute stty")
|
||||
.output()?
|
||||
};
|
||||
|
||||
match output.status.code() {
|
||||
Some(0) => {
|
||||
if let Some(0) = output.status.code() {
|
||||
let stdout = String::from_utf8(output.stdout).expect("Invalid utf8 output from stty");
|
||||
let mut data = stdout.split_whitespace();
|
||||
data.next();
|
||||
data.next()
|
||||
return data
|
||||
.next()
|
||||
.expect("Not enough data")
|
||||
.parse::<u16>()
|
||||
.expect("Invalid base-10 number")
|
||||
}
|
||||
_ => FALLBACK_WIDTH,
|
||||
.map_err(|_| anyhow!("Invalid width"));
|
||||
}
|
||||
|
||||
Err(anyhow!("Invalid status code"))
|
||||
}
|
||||
|
||||
pub fn width() -> u16 {
|
||||
if let Ok((w, _)) = terminal::size() {
|
||||
w
|
||||
} else {
|
||||
width_with_shell_out()
|
||||
width_with_shell_out().unwrap_or(FALLBACK_WIDTH)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_ansi(ansi: &str) -> Option<style::Color> {
|
||||
style::Color::parse_ansi(&format!("5;{}", ansi))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Color(pub style::Color);
|
||||
|
||||
impl FromStr for Color {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(ansi: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(c) = parse_ansi(ansi) {
|
||||
Ok(Color(c))
|
||||
} else {
|
||||
Err("Invalid color")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
38
src/ui.rs
38
src/ui.rs
|
@ -1,32 +1,20 @@
|
|||
use crate::env_var;
|
||||
|
||||
use crate::config::CONFIG;
|
||||
use crate::terminal;
|
||||
pub use crate::terminal::style::style;
|
||||
use crate::terminal::style::Color;
|
||||
|
||||
use std::cmp::max;
|
||||
|
||||
fn parse_ansi(varname: &str, default: Color) -> Color {
|
||||
let value: Option<String> = env_var::parse(varname);
|
||||
if let Some(v) = value {
|
||||
if let Some(a) = terminal::parse_ansi(&v) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
default
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TAG_COLOR: Color = parse_ansi(env_var::TAG_COLOR, Color::Cyan);
|
||||
pub static ref COMMENT_COLOR: Color = parse_ansi(env_var::COMMENT_COLOR, Color::Blue);
|
||||
pub static ref SNIPPET_COLOR: Color = parse_ansi(env_var::SNIPPET_COLOR, Color::White);
|
||||
pub static ref TAG_WIDTH_PERCENTAGE: u16 = env_var::parse(env_var::TAG_WIDTH).unwrap_or(26);
|
||||
pub static ref COMMENT_WIDTH_PERCENTAGE: u16 = env_var::parse(env_var::COMMENT_WIDTH).unwrap_or(42);
|
||||
}
|
||||
|
||||
pub fn get_widths() -> (usize, usize) {
|
||||
let width = terminal::width();
|
||||
let tag_width = max(20, width * *TAG_WIDTH_PERCENTAGE / 100);
|
||||
let comment_width = max(45, width * *COMMENT_WIDTH_PERCENTAGE / 100);
|
||||
(usize::from(tag_width), usize::from(comment_width))
|
||||
let tag_width_percentage = max(
|
||||
CONFIG.tag_min_width(),
|
||||
width * CONFIG.tag_width_percentage() / 100,
|
||||
);
|
||||
let comment_width_percentage = max(
|
||||
CONFIG.comment_min_width(),
|
||||
width * CONFIG.comment_width_percentage() / 100,
|
||||
);
|
||||
(
|
||||
usize::from(tag_width_percentage),
|
||||
usize::from(comment_width_percentage),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,27 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::finder::structures::Opts as FinderOpts;
|
||||
use crate::finder::Finder;
|
||||
|
||||
use crate::structures::cheat::VariableMap;
|
||||
use crate::structures::item::Item;
|
||||
use crate::writer;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
let config = &CONFIG;
|
||||
let opts = FinderOpts::from_config(&config)?;
|
||||
let _ = config
|
||||
.finder()
|
||||
.call(opts, |stdin, _| {
|
||||
populate_cheatsheet(stdin);
|
||||
Ok(Some(VariableMap::new()))
|
||||
})
|
||||
.context("Failed getting selection and variables from finder")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_msg(tags: &str, comment: &str, snippet: &str, stdin: &mut std::process::ChildStdin) {
|
||||
let item = Item {
|
||||
tags: tags.to_string(),
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::config::CONFIG;
|
||||
use crate::structures::item::Item;
|
||||
use crate::ui;
|
||||
use regex::Regex;
|
||||
|
@ -35,12 +36,12 @@ fn limit_str(text: &str, length: usize) -> String {
|
|||
}
|
||||
|
||||
pub fn write(item: &Item) -> String {
|
||||
let (tag_width, comment_width) = *COLUMN_WIDTHS;
|
||||
let (tag_width_percentage, comment_width_percentage) = *COLUMN_WIDTHS;
|
||||
format!(
|
||||
"{tags_short}{delimiter}{comment_short}{delimiter}{snippet_short}{delimiter}{tags}{delimiter}{comment}{delimiter}{snippet}{delimiter}{file_index}{delimiter}\n",
|
||||
tags_short = ui::style(limit_str(&item.tags, tag_width)).with(*ui::TAG_COLOR),
|
||||
comment_short = ui::style(limit_str(&item.comment, comment_width)).with(*ui::COMMENT_COLOR),
|
||||
snippet_short = ui::style(fix_newlines(&item.snippet)).with(*ui::SNIPPET_COLOR),
|
||||
tags_short = ui::style(limit_str(&item.tags, tag_width_percentage)).with(CONFIG.tag_color()),
|
||||
comment_short = ui::style(limit_str(&item.comment, comment_width_percentage)).with(CONFIG.comment_color()),
|
||||
snippet_short = ui::style(fix_newlines(&item.snippet)).with(CONFIG.snippet_color()),
|
||||
tags = item.tags,
|
||||
comment = item.comment,
|
||||
delimiter = DELIMITER,
|
||||
|
|
Loading…
Reference in a new issue