diff options
Diffstat (limited to 'pkgs/by-name/ts/tskm/src/cli.rs')
-rw-r--r-- | pkgs/by-name/ts/tskm/src/cli.rs | 133 |
1 files changed, 123 insertions, 10 deletions
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs index 1c72b3c2..c1eba387 100644 --- a/pkgs/by-name/ts/tskm/src/cli.rs +++ b/pkgs/by-name/ts/tskm/src/cli.rs @@ -1,13 +1,13 @@ -use std::path::PathBuf; +use std::{ffi::OsStr, path::PathBuf}; use anyhow::{bail, Result}; -use clap::{ArgAction, Parser, Subcommand}; +use clap::{builder::StyledStr, ArgAction, Parser, Subcommand}; +use clap_complete::{ArgValueCompleter, CompletionCandidate}; use url::Url; use crate::{ interface::{input::Input, project::ProjectName}, - state::State, - task, + state, task, }; #[derive(Parser, Debug)] @@ -17,8 +17,10 @@ use crate::{ /// `tskm` effectively combines multiple applications together: /// - `taskwarrior` projects are raised connected to `firefox` profiles, making it possible to “open” /// a project. +/// /// - Every `taskwarrior` project has a determined `neorg` path, so that extra information for a /// `project` can be stored in this `norg` file. +/// /// - `tskm` can track inputs for you. These are URLs with optional tags which you can that /// “review” to open tasks based on them. pub struct CliArgs { @@ -79,14 +81,14 @@ pub enum NeorgCommand { /// Open the `neorg` project associated with id of the task. Task { /// The working set id of the task - #[arg(value_parser = task_from_working_set_id)] + #[arg(value_parser = task_from_working_set_id, add = ArgValueCompleter::new(complete_task_id))] id: task::Task, }, } fn task_from_working_set_id(id: &str) -> Result<task::Task> { let id: usize = id.parse()?; - let mut state = State::new_ro()?; + let mut state = state::State::new_ro()?; let Some(task) = task::Task::from_working_set(id, &mut state)? else { bail!("Working set id '{id}' is not valid!") @@ -104,7 +106,7 @@ pub enum OpenCommand { /// Opens Firefox with either the supplied project or the currently active project profile. Project { /// The project to open. - #[arg(value_parser = task::Project::from_project_string)] + #[arg(value_parser = task::Project::from_project_string, add = ArgValueCompleter::new(complete_project))] project: task::Project, /// The URL to open. @@ -123,7 +125,7 @@ pub enum OpenCommand { /// List all open tabs in the project. ListTabs { /// The project to open. - #[arg(value_parser = task::Project::from_project_string)] + #[arg(value_parser = task::Project::from_project_string, add = ArgValueCompleter::new(complete_project))] project: Option<task::Project>, }, } @@ -133,7 +135,10 @@ pub enum InputCommand { /// Add URLs as inputs to be categorized. Add { inputs: Vec<Input> }, /// Remove URLs - Remove { inputs: Vec<Input> }, + Remove { + #[arg(add = ArgValueCompleter::new(complete_input_url))] + inputs: Vec<Input>, + }, /// Add all URLs in the file as inputs to be categorized. /// @@ -144,10 +149,118 @@ pub enum InputCommand { /// It takes a project in which to open the URLs. Review { /// Opens all the URLs in this project. - #[arg(value_parser = task::Project::from_project_string)] + #[arg(value_parser = task::Project::from_project_string, add = ArgValueCompleter::new(complete_project))] project: task::Project, }, /// List all the previously added inputs. List, } + +fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> { + fn format_task( + task: task::Task, + current: &str, + state: &mut state::State, + ) -> Option<CompletionCandidate> { + let id = { + let Ok(base) = task.working_set_id(state) else { + return None; + }; + base.to_string() + }; + + if !id.starts_with(current) { + return None; + } + + let description = { + let Ok(base) = task.description(state) else { + return None; + }; + StyledStr::from(base) + }; + + Some(CompletionCandidate::new(id).help(Some(description))) + } + + let mut output = vec![]; + + let Some(current) = current.to_str() else { + return output; + }; + + let Ok(mut state) = state::State::new_ro() else { + return output; + }; + + let Ok(pending) = state.replica().pending_tasks() else { + return output; + }; + + let Ok(current_project) = task::Project::get_current() else { + return output; + }; + + if let Some(current_project) = current_project { + for t in pending { + let task = task::Task::from(&t); + if let Ok(project) = task.project(&mut state) { + if project == current_project { + if let Some(out) = format_task(task, current, &mut state) { + output.push(out); + } else { + continue; + } + } + } + } + } else { + for t in pending { + let task = task::Task::from(&t); + if let Some(out) = format_task(task, current, &mut state) { + output.push(out); + } + } + } + + output +} +fn complete_project(current: &OsStr) -> Vec<CompletionCandidate> { + let mut output = vec![]; + + let Some(current) = current.to_str() else { + return output; + }; + + let Ok(all) = task::Project::all() else { + return output; + }; + + for a in all { + if a.to_project_display().starts_with(current) { + output.push(CompletionCandidate::new(a.to_project_display())); + } + } + + output +} +fn complete_input_url(current: &OsStr) -> Vec<CompletionCandidate> { + let mut output = vec![]; + + let Some(current) = current.to_str() else { + return output; + }; + + let Ok(all) = Input::all() else { + return output; + }; + + for a in all { + if a.to_string().starts_with(current) { + output.push(CompletionCandidate::new(a.to_string())); + } + } + + output +} |