aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/ts/tskm/src
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ts/tskm/src')
-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)
}