feat: Better completion

This commit is contained in:
Dheepak Krishnamurthy 2022-01-25 18:15:02 -07:00
parent aa68c7103d
commit 12b61f5469
4 changed files with 118 additions and 62 deletions

View file

@ -1,4 +1,4 @@
.\" Automatically generated by Pandoc 2.16.2
.\" Automatically generated by Pandoc 2.17.0.1
.\"
.TH "taskwarrior-tui" "1" "" "" ""
.hy

View file

@ -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()));
}
}

View file

@ -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));
}
}
}

View file

@ -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,