about summary refs log tree commit diff stats
path: root/pkgs/by-name/ts/tskm/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs133
-rw-r--r--pkgs/by-name/ts/tskm/src/main.rs42
-rw-r--r--pkgs/by-name/ts/tskm/src/task/mod.rs10
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)
     }