mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-25 08:47:18 +02:00
feat: Better completion ✨
This commit is contained in:
parent
aa68c7103d
commit
12b61f5469
4 changed files with 118 additions and 62 deletions
|
@ -1,4 +1,4 @@
|
|||
.\" Automatically generated by Pandoc 2.16.2
|
||||
.\" Automatically generated by Pandoc 2.17.0.1
|
||||
.\"
|
||||
.TH "taskwarrior-tui" "1" "" "" ""
|
||||
.hy
|
||||
|
|
117
src/app.rs
117
src/app.rs
|
@ -150,7 +150,7 @@ fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
|||
.split(popup_layout[1])[1]
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Mode {
|
||||
Tasks(Action),
|
||||
Projects,
|
||||
|
@ -887,8 +887,14 @@ impl TaskwarriorTui {
|
|||
.candidates()
|
||||
.iter()
|
||||
.map(|p| {
|
||||
let lines = vec![Spans::from(p.display.clone())];
|
||||
ListItem::new(lines).style(Style::default().fg(Color::Black))
|
||||
let lines = vec![Spans::from(vec![
|
||||
Span::styled(
|
||||
p.display.replace(p.replacement.clone().as_str(), ""),
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::from(p.replacement.clone()),
|
||||
])];
|
||||
ListItem::new(lines)
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -896,7 +902,7 @@ impl TaskwarriorTui {
|
|||
let items = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE).title(""))
|
||||
.style(self.config.uda_style_report_completion_pane)
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.highlight_style(self.config.uda_style_report_completion_pane_highlight)
|
||||
.highlight_symbol(&self.config.uda_selection_indicator);
|
||||
|
||||
let area = f.size();
|
||||
|
@ -1604,9 +1610,12 @@ impl TaskwarriorTui {
|
|||
pub fn export_tasks(&mut self) -> Result<()> {
|
||||
let mut task = Command::new("task");
|
||||
|
||||
task.arg("rc.json.array=on");
|
||||
task.arg("rc.confirmation=off");
|
||||
task.arg("rc.json.depends.array=on");
|
||||
task.arg("rc.json.array=on")
|
||||
.arg("rc.confirmation=off")
|
||||
.arg("rc.json.depends.array=on")
|
||||
.arg("rc.color=off")
|
||||
.arg("rc._forcecolor=off");
|
||||
// .arg("rc.verbose:override=false");
|
||||
|
||||
if let Some(args) = shlex::split(&format!(
|
||||
r#"rc.report.{}.filter='{}'"#,
|
||||
|
@ -3387,9 +3396,8 @@ impl TaskwarriorTui {
|
|||
pub fn update_completion_list(&mut self) {
|
||||
self.completion_list.clear();
|
||||
|
||||
if let Mode::Tasks(Action::Modify | Action::Annotate | Action::Add | Action::Log) = self.mode {
|
||||
if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode {
|
||||
for s in vec![
|
||||
"+".to_string(),
|
||||
"project:".to_string(),
|
||||
"priority:".to_string(),
|
||||
"due:".to_string(),
|
||||
|
@ -3397,14 +3405,43 @@ impl TaskwarriorTui {
|
|||
"wait:".to_string(),
|
||||
"depends:".to_string(),
|
||||
] {
|
||||
self.completion_list.insert(s);
|
||||
self.completion_list.insert(("attribute".to_string(), s));
|
||||
}
|
||||
}
|
||||
|
||||
if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode {
|
||||
for s in vec![
|
||||
".before:",
|
||||
".under:",
|
||||
".below:",
|
||||
".after:",
|
||||
".over:",
|
||||
".above:",
|
||||
".by:",
|
||||
".none:",
|
||||
".any:",
|
||||
".is:",
|
||||
".equals:",
|
||||
".isnt:",
|
||||
".not:",
|
||||
".has:",
|
||||
".contains:",
|
||||
".hasnt:",
|
||||
".startswith:",
|
||||
".left:",
|
||||
".endswith:",
|
||||
".right:",
|
||||
".word:",
|
||||
".noword:",
|
||||
] {
|
||||
self.completion_list.insert(("modifier".to_string(), s.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode {
|
||||
for priority in &self.config.uda_priority_values {
|
||||
let p = format!("priority:{}", priority);
|
||||
self.completion_list.insert(p);
|
||||
let p = priority.to_string();
|
||||
self.completion_list.insert(("priority".to_string(), p));
|
||||
}
|
||||
let virtual_tags = self.task_report_table.virtual_tags.clone();
|
||||
for task in &self.tasks {
|
||||
|
@ -3412,7 +3449,7 @@ impl TaskwarriorTui {
|
|||
for tag in tags {
|
||||
let t = format!("+{}", &tag);
|
||||
if !virtual_tags.contains(tag) {
|
||||
self.completion_list.insert(t);
|
||||
self.completion_list.insert(("tag".to_string(), t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3420,11 +3457,11 @@ impl TaskwarriorTui {
|
|||
for task in &self.tasks {
|
||||
if let Some(project) = task.project() {
|
||||
let p = if project.contains(' ') {
|
||||
format!(r#"project:"{}""#, &project)
|
||||
format!(r#""{}""#, &project)
|
||||
} else {
|
||||
format!("project:{}", &project)
|
||||
project.to_string()
|
||||
};
|
||||
self.completion_list.insert(p);
|
||||
self.completion_list.insert(("project".to_string(), p));
|
||||
}
|
||||
}
|
||||
for task in &self.tasks {
|
||||
|
@ -3432,7 +3469,7 @@ impl TaskwarriorTui {
|
|||
let now = Local::now();
|
||||
let date = TimeZone::from_utc_datetime(now.offset(), date);
|
||||
let s = format!(
|
||||
"due:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
"'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
date.year(),
|
||||
date.month(),
|
||||
date.day(),
|
||||
|
@ -3440,7 +3477,7 @@ impl TaskwarriorTui {
|
|||
date.minute(),
|
||||
date.second(),
|
||||
);
|
||||
self.completion_list.insert(s);
|
||||
self.completion_list.insert(("due".to_string(), s));
|
||||
}
|
||||
}
|
||||
for task in &self.tasks {
|
||||
|
@ -3448,7 +3485,7 @@ impl TaskwarriorTui {
|
|||
let now = Local::now();
|
||||
let date = TimeZone::from_utc_datetime(now.offset(), date);
|
||||
let s = format!(
|
||||
"wait:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
"'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
date.year(),
|
||||
date.month(),
|
||||
date.day(),
|
||||
|
@ -3456,7 +3493,7 @@ impl TaskwarriorTui {
|
|||
date.minute(),
|
||||
date.second(),
|
||||
);
|
||||
self.completion_list.insert(s);
|
||||
self.completion_list.insert(("wait".to_string(), s));
|
||||
}
|
||||
}
|
||||
for task in &self.tasks {
|
||||
|
@ -3464,7 +3501,7 @@ impl TaskwarriorTui {
|
|||
let now = Local::now();
|
||||
let date = TimeZone::from_utc_datetime(now.offset(), date);
|
||||
let s = format!(
|
||||
"scheduled:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
"'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
date.year(),
|
||||
date.month(),
|
||||
date.day(),
|
||||
|
@ -3472,7 +3509,7 @@ impl TaskwarriorTui {
|
|||
date.minute(),
|
||||
date.second(),
|
||||
);
|
||||
self.completion_list.insert(s);
|
||||
self.completion_list.insert(("scheduled".to_string(), s));
|
||||
}
|
||||
}
|
||||
for task in &self.tasks {
|
||||
|
@ -3480,7 +3517,7 @@ impl TaskwarriorTui {
|
|||
let now = Local::now();
|
||||
let date = TimeZone::from_utc_datetime(now.offset(), date);
|
||||
let s = format!(
|
||||
"end:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
"'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'",
|
||||
date.year(),
|
||||
date.month(),
|
||||
date.day(),
|
||||
|
@ -3488,18 +3525,19 @@ impl TaskwarriorTui {
|
|||
date.minute(),
|
||||
date.second(),
|
||||
);
|
||||
self.completion_list.insert(s);
|
||||
self.completion_list.insert(("end".to_string(), s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.mode == Mode::Tasks(Action::Filter) {
|
||||
self.completion_list.insert("status:pending".into());
|
||||
self.completion_list.insert("status:completed".into());
|
||||
self.completion_list.insert("status:deleted".into());
|
||||
self.completion_list.insert("status:recurring".into());
|
||||
self.completion_list.insert(("status".to_string(), "pending".into()));
|
||||
self.completion_list.insert(("status".to_string(), "completed".into()));
|
||||
self.completion_list.insert(("status".to_string(), "deleted".into()));
|
||||
self.completion_list.insert(("status".to_string(), "recurring".into()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_input_for_completion(&mut self) {
|
||||
match self.mode {
|
||||
Mode::Tasks(Action::Add | Action::Annotate | Action::Log) => {
|
||||
|
@ -4723,4 +4761,27 @@ mod tests {
|
|||
app.render(&mut terminal).unwrap();
|
||||
println!("{}", buffer_view(terminal.backend().buffer()));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
fn test_taskwarrior_tui_completion() {
|
||||
let mut app = TaskwarriorTui::new("next").unwrap();
|
||||
app.mode = Mode::Tasks(Action::Add);
|
||||
app.update_completion_list();
|
||||
let input = "Wash car ";
|
||||
for c in input.chars() {
|
||||
app.handle_input(Key::Char(c)).unwrap();
|
||||
}
|
||||
app.mode = Mode::Tasks(Action::Add);
|
||||
app.update_completion_list();
|
||||
app.handle_input(Key::Tab).unwrap();
|
||||
let backend = TestBackend::new(80, 50);
|
||||
let mut terminal = Terminal::new(backend).unwrap();
|
||||
terminal
|
||||
.draw(|f| {
|
||||
app.draw(f);
|
||||
app.draw(f);
|
||||
})
|
||||
.unwrap();
|
||||
println!("{}", buffer_view(terminal.backend().buffer()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter};
|
||||
use std::{error::Error, io};
|
||||
use tui::{
|
||||
layout::{Constraint, Corner, Direction, Layout},
|
||||
|
@ -32,8 +33,8 @@ pub fn get_start_word_under_cursor(line: &str, cursor_pos: usize) -> usize {
|
|||
}
|
||||
|
||||
pub struct TaskwarriorTuiCompletionHelper {
|
||||
pub candidates: Vec<String>,
|
||||
pub completer: rustyline::completion::FilenameCompleter,
|
||||
pub candidates: Vec<(String, String)>,
|
||||
pub context: String,
|
||||
}
|
||||
|
||||
impl Completer for TaskwarriorTuiCompletionHelper {
|
||||
|
@ -43,8 +44,8 @@ impl Completer for TaskwarriorTuiCompletionHelper {
|
|||
let candidates: Vec<Pair> = self
|
||||
.candidates
|
||||
.iter()
|
||||
.filter_map(|candidate| {
|
||||
if candidate.starts_with(&word[..pos]) {
|
||||
.filter_map(|(context, candidate)| {
|
||||
if context == &self.context && candidate.starts_with(&word[..pos]) {
|
||||
Some(Pair {
|
||||
display: candidate.clone(),
|
||||
replacement: candidate[pos..].to_string(),
|
||||
|
@ -67,35 +68,34 @@ pub struct CompletionList {
|
|||
|
||||
impl CompletionList {
|
||||
pub fn new() -> CompletionList {
|
||||
let completer = FilenameCompleter::new();
|
||||
CompletionList {
|
||||
state: ListState::default(),
|
||||
input: String::new(),
|
||||
pos: 0,
|
||||
helper: TaskwarriorTuiCompletionHelper {
|
||||
candidates: vec![],
|
||||
completer,
|
||||
context: String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_items(items: Vec<String>) -> CompletionList {
|
||||
let completer = FilenameCompleter::new();
|
||||
pub fn with_items(items: Vec<(String, String)>) -> CompletionList {
|
||||
let mut candidates = vec![];
|
||||
for i in items {
|
||||
if !candidates.contains(&i) {
|
||||
candidates.push(i);
|
||||
}
|
||||
}
|
||||
let context = String::new();
|
||||
CompletionList {
|
||||
state: ListState::default(),
|
||||
input: String::new(),
|
||||
pos: 0,
|
||||
helper: TaskwarriorTuiCompletionHelper { candidates, completer },
|
||||
helper: TaskwarriorTuiCompletionHelper { candidates, context },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, item: String) {
|
||||
pub fn insert(&mut self, item: (String, String)) {
|
||||
if !self.helper.candidates.contains(&item) {
|
||||
self.helper.candidates.push(item);
|
||||
}
|
||||
|
@ -171,29 +171,19 @@ impl CompletionList {
|
|||
}
|
||||
|
||||
pub fn input(&mut self, input: String) {
|
||||
if input.contains('.') && input.contains(':') {
|
||||
self.input = input.split_once(':').unwrap().1.to_string();
|
||||
self.helper.context = input.split_once('.').unwrap().0.to_string();
|
||||
} else if input.contains('.') {
|
||||
self.input = format!(".{}", input.split_once('.').unwrap().1);
|
||||
self.helper.context = "modifier".to_string();
|
||||
} else if input.contains(':') {
|
||||
self.input = input.split_once(':').unwrap().1.to_string();
|
||||
self.helper.context = input.split_once(':').unwrap().0.to_string();
|
||||
} else {
|
||||
self.input = input;
|
||||
self.helper.context = "attribute".to_string();
|
||||
}
|
||||
self.pos = self.input.len();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_completion() {
|
||||
let mut completion_list = CompletionList::new();
|
||||
|
||||
completion_list.insert("+test".to_string());
|
||||
completion_list.insert("+shortcut".to_string());
|
||||
completion_list.insert("project:color".to_string());
|
||||
completion_list.insert("due:'2021-04-07T00:00:00'".to_string());
|
||||
|
||||
completion_list.input("due:".to_string());
|
||||
|
||||
for p in completion_list.candidates().iter() {
|
||||
dbg!(format!("{:?}", p.display));
|
||||
dbg!(format!("{:?}", p.replacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,7 @@ pub struct Config {
|
|||
pub uda_style_calendar_title: Style,
|
||||
pub uda_style_calendar_today: Style,
|
||||
pub uda_style_report_completion_pane: Style,
|
||||
pub uda_style_report_completion_pane_highlight: Style,
|
||||
pub uda_shortcuts: Vec<String>,
|
||||
pub uda_background_process: String,
|
||||
pub uda_background_process_period: usize,
|
||||
|
@ -127,6 +128,7 @@ impl Config {
|
|||
let uda_style_calendar_today = Self::get_uda_style("calendar.today", data);
|
||||
let uda_style_context_active = Self::get_uda_style("context.active", data);
|
||||
let uda_style_report_completion_pane = Self::get_uda_style("report.completion-pane", data);
|
||||
let uda_style_report_completion_pane_highlight = Self::get_uda_style("report.completion-pane-highlight", data);
|
||||
let uda_shortcuts = Self::get_uda_shortcuts(data);
|
||||
let uda_background_process = Self::get_uda_background_process(data);
|
||||
let uda_background_process_period = Self::get_uda_background_process_period(data);
|
||||
|
@ -139,6 +141,8 @@ impl Config {
|
|||
let uda_style_context_active = uda_style_context_active.unwrap_or_default();
|
||||
let uda_style_report_completion_pane =
|
||||
uda_style_report_completion_pane.unwrap_or_else(|| Style::default().bg(Color::Rgb(223, 223, 223)));
|
||||
let uda_style_report_completion_pane_highlight =
|
||||
uda_style_report_completion_pane_highlight.unwrap_or(uda_style_report_completion_pane);
|
||||
let uda_quick_tag_name = Self::get_uda_quick_tag_name(data);
|
||||
let uda_task_report_prompt_on_delete = Self::get_uda_task_report_prompt_on_delete(data);
|
||||
let uda_task_report_prompt_on_done = Self::get_uda_task_report_prompt_on_done(data);
|
||||
|
@ -179,6 +183,7 @@ impl Config {
|
|||
uda_style_calendar_title,
|
||||
uda_style_calendar_today,
|
||||
uda_style_report_completion_pane,
|
||||
uda_style_report_completion_pane_highlight,
|
||||
uda_style_report_scrollbar,
|
||||
uda_shortcuts,
|
||||
uda_background_process,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue