diff options
Diffstat (limited to '')
-rw-r--r-- | pkgs/by-name/ts/tskm/src/cli.rs | 133 | ||||
-rw-r--r-- | pkgs/by-name/ts/tskm/src/main.rs | 42 | ||||
-rw-r--r-- | pkgs/by-name/ts/tskm/src/task/mod.rs | 10 |
3 files changed, 140 insertions, 45 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 +} diff --git a/pkgs/by-name/ts/tskm/src/main.rs b/pkgs/by-name/ts/tskm/src/main.rs index f4416c6d..77f2dcca 100644 --- a/pkgs/by-name/ts/tskm/src/main.rs +++ b/pkgs/by-name/ts/tskm/src/main.rs @@ -1,8 +1,11 @@ use anyhow::Result; -use clap::Parser; -use state::State; +use clap::{CommandFactory, Parser}; -use crate::interface::{input, neorg, open, project}; +use crate::{ + cli::{CliArgs, Command}, + interface::{input, neorg, open, project}, + state::State, +}; pub mod cli; pub mod interface; @@ -10,38 +13,9 @@ pub mod rofi; pub mod state; pub mod task; -use crate::cli::{CliArgs, Command}; - fn main() -> Result<(), anyhow::Error> { - // TODO: Support these completions for the respective types <2025-04-04> - // - // ID_GENERATION_FUNCTION - // ```sh - // context="$(task _get rc.context)" - // if [ "$context" ]; then - // filter="project:$context" - // else - // filter="0-10000" - // fi - // tasks="$(task "$filter" _ids)" - // - // if [ "$tasks" ]; then - // echo "$tasks" | xargs task _zshids | awk -F: -v q="'" '{gsub(/'\''/, q "\\" q q ); print $1 ":" q $2 q}' - // fi - // ``` - // - // ARGUMENTS: - // ID | *([0-9]) := [[%ID_GENERATION_FUNCTION]] - // The function displays all possible IDs of the eligible tasks. - // - // WS := %ALL_WORKSPACES - // All possible workspaces. - // - // P := %ALL_PROJECTS_PIPE - // The possible project. - // - // F := [[fd . --max-depth 3]] - // A URL-Input file to use as source. + clap_complete::CompleteEnv::with_factory(CliArgs::command).complete(); + let args = CliArgs::parse(); stderrlog::new() diff --git a/pkgs/by-name/ts/tskm/src/task/mod.rs b/pkgs/by-name/ts/tskm/src/task/mod.rs index 03a12faa..989f273a 100644 --- a/pkgs/by-name/ts/tskm/src/task/mod.rs +++ b/pkgs/by-name/ts/tskm/src/task/mod.rs @@ -66,6 +66,14 @@ impl Task { pub fn uuid(&self) -> &taskchampion::Uuid { &self.uuid } + #[must_use] + pub fn working_set_id(&self, state: &mut State) -> Result<usize> { + Ok(state + .replica() + .working_set()? + .by_uuid(self.uuid) + .expect("The task should be in the working set")) + } fn as_task(&self, state: &mut State) -> Result<taskchampion::Task> { Ok(state @@ -121,7 +129,7 @@ impl Task { .expect("Every task should have a project") .to_owned() }; - let project = Project::from_project_string(output.as_str()) + let project = Project::from_project_string(output.as_str().trim()) .expect("This comes from tw, it should be valid"); Ok(project) } |