mirror of
https://github.com/kdheepak/taskwarrior-tui.git
synced 2025-08-24 05:26:42 +02:00
Merge pull request #495 from kdheepak/update
feat: Update dependencies ✨
This commit is contained in:
commit
dee5c0c5f6
30 changed files with 7977 additions and 8104 deletions
13
.github/workflows/cd.yml
vendored
13
.github/workflows/cd.yml
vendored
|
@ -110,6 +110,19 @@ jobs:
|
|||
toolchain: stable
|
||||
override: true
|
||||
|
||||
# - name: Build
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: build
|
||||
# args: --release
|
||||
|
||||
# - name: Publish
|
||||
# uses: actions-rs/cargo@v1
|
||||
# with:
|
||||
# command: publish
|
||||
# env:
|
||||
# CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
homebrew:
|
||||
name: Bump Homebrew formula
|
||||
runs-on: macos-latest
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
max_width = 120
|
||||
max_width = 150
|
||||
tab_spaces = 2
|
||||
|
|
923
Cargo.lock
generated
923
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
50
Cargo.toml
50
Cargo.toml
|
@ -17,37 +17,37 @@ default = ["crossterm-backend"]
|
|||
crossterm-backend = ["tui/crossterm", "crossterm"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.56"
|
||||
anyhow = "1.0.71"
|
||||
better-panic = "0.3.0"
|
||||
cassowary = "0.3.0"
|
||||
chrono = "0.4.19"
|
||||
clap = { version = "3.1.6", features = ["derive"] }
|
||||
crossterm = { version = "0.25.0", optional = true, default-features = false, features = [
|
||||
"event-stream"
|
||||
chrono = "0.4.24"
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
crossterm = { version = "0.26.1", optional = true, default-features = false, features = [
|
||||
"event-stream",
|
||||
] }
|
||||
dirs = "4.0.0"
|
||||
futures = "0.3.21"
|
||||
itertools = "0.10.3"
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.28"
|
||||
itertools = "0.10.5"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
log4rs = "1.0.0"
|
||||
path-clean = "0.1.0"
|
||||
log = "0.4.17"
|
||||
log4rs = "1.2.0"
|
||||
path-clean = "1.0.1"
|
||||
rand = "0.8.5"
|
||||
regex = "1.5.5"
|
||||
rustyline = "10.0.0"
|
||||
serde = { version = "1.0.136", features = ["derive"] }
|
||||
serde_json = "1.0.79"
|
||||
shellexpand = "2.1.0"
|
||||
regex = "1.8.3"
|
||||
rustyline = { version = "11.0.0", features = ["with-file-history", "derive"] }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
serde_json = "1.0.96"
|
||||
shellexpand = "3.1.0"
|
||||
shlex = "1.1.0"
|
||||
task-hookrs = { git = "https://github.com/kdheepak/task-hookrs" }
|
||||
tokio = { version = "1.17.0", features = ["full"] }
|
||||
tokio-stream = "0.1.3"
|
||||
tui = { version = "0.19.0", optional = true, default-features = false }
|
||||
unicode-segmentation = "1.9.0"
|
||||
tokio = { version = "1.28.1", features = ["full"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tui = { package = "ratatui", version = "0.20.1" }
|
||||
unicode-segmentation = "1.10.1"
|
||||
unicode-truncate = "0.2.0"
|
||||
unicode-width = "0.1.9"
|
||||
uuid = { version = "0.8.2", features = ["serde", "v4"] }
|
||||
versions = "4.0.0"
|
||||
unicode-width = "0.1.10"
|
||||
uuid = { version = "1.3.3", features = ["serde", "v4"] }
|
||||
versions = "4.1.0"
|
||||
|
||||
[package.metadata.rpm]
|
||||
package = "taskwarrior-tui"
|
||||
|
@ -64,6 +64,6 @@ incremental = true
|
|||
lto = "off"
|
||||
|
||||
[build-dependencies]
|
||||
clap = { version = "3.1.6", features = ["derive"] }
|
||||
clap_complete = "3.1.1"
|
||||
clap = { version = "4.3.0", features = ["derive"] }
|
||||
clap_complete = "4.3.0"
|
||||
shlex = "1.1.0"
|
||||
|
|
|
@ -22,7 +22,6 @@ A Terminal User Interface (TUI) for [Taskwarrior](https://taskwarrior.org/) that
|
|||
|
||||

|
||||
|
||||
|
||||
### Showcase
|
||||
|
||||
<details>
|
||||
|
@ -125,7 +124,11 @@ uda.taskwarrior-tui.report.next.filter=(status:pending or status:waiting)
|
|||
|
||||
### References / Resources
|
||||
|
||||
If you like `taskwarrior-tui`, please consider donating to [me](https://github.com/sponsors/kdheepak), [`@GothenburgBitFactory`](https://github.com/sponsors/GothenburgBitFactory) or a charity of your choice.
|
||||
If you like `taskwarrior-tui`, please consider donating to
|
||||
|
||||
- [`kdheepak`](https://github.com/sponsors/kdheepak)
|
||||
- [`@GothenburgBitFactory`](https://github.com/sponsors/GothenburgBitFactory)
|
||||
- and/or a charity of your choice.
|
||||
|
||||
<details>
|
||||
<summary>Additional resources</summary>
|
||||
|
|
|
@ -23,10 +23,10 @@ _taskwarrior-tui() {
|
|||
'--taskrc=[Sets the .taskrc file using the TASKRC environment variable for taskwarrior]:FILE: ' \
|
||||
'-r+[Sets default report]:STRING: ' \
|
||||
'--report=[Sets default report]:STRING: ' \
|
||||
'-h[Print help information]' \
|
||||
'--help[Print help information]' \
|
||||
'-V[Print version information]' \
|
||||
'--version[Print version information]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
}
|
||||
|
||||
|
@ -36,4 +36,8 @@ _taskwarrior-tui_commands() {
|
|||
_describe -t commands 'taskwarrior-tui commands' commands "$@"
|
||||
}
|
||||
|
||||
_taskwarrior-tui "$@"
|
||||
if [ "$funcstack[1]" = "_taskwarrior-tui" ]; then
|
||||
_taskwarrior-tui "$@"
|
||||
else
|
||||
compdef _taskwarrior-tui taskwarrior-tui
|
||||
fi
|
||||
|
|
|
@ -29,10 +29,10 @@ Register-ArgumentCompleter -Native -CommandName 'taskwarrior-tui' -ScriptBlock {
|
|||
[CompletionResult]::new('--taskrc', 'taskrc', [CompletionResultType]::ParameterName, 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior')
|
||||
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Sets default report')
|
||||
[CompletionResult]::new('--report', 'report', [CompletionResultType]::ParameterName, 'Sets default report')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version information')
|
||||
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', 'V', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', 'version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
_taskwarrior-tui() {
|
||||
local i cur prev opts cmds
|
||||
local i cur prev opts cmd
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
@ -8,8 +8,8 @@ _taskwarrior-tui() {
|
|||
|
||||
for i in ${COMP_WORDS[@]}
|
||||
do
|
||||
case "${i}" in
|
||||
"$1")
|
||||
case "${cmd},${i}" in
|
||||
",$1")
|
||||
cmd="taskwarrior__tui"
|
||||
;;
|
||||
*)
|
||||
|
@ -19,7 +19,7 @@ _taskwarrior-tui() {
|
|||
|
||||
case "${cmd}" in
|
||||
taskwarrior__tui)
|
||||
opts="-h -V -d -c -r --help --version --data --config --taskdata --taskrc --report"
|
||||
opts="-d -c -r -h -V --data --config --taskdata --taskrc --report --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
|
|
|
@ -3,5 +3,5 @@ complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwa
|
|||
complete -c taskwarrior-tui -l taskdata -d 'Sets the .task folder using the TASKDATA environment variable for taskwarrior' -r
|
||||
complete -c taskwarrior-tui -l taskrc -d 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior' -r
|
||||
complete -c taskwarrior-tui -s r -l report -d 'Sets default report' -r
|
||||
complete -c taskwarrior-tui -s h -l help -d 'Print help information'
|
||||
complete -c taskwarrior-tui -s V -l version -d 'Print version information'
|
||||
complete -c taskwarrior-tui -s h -l help -d 'Print help'
|
||||
complete -c taskwarrior-tui -s V -l version -d 'Print version'
|
||||
|
|
2
justfile
Normal file
2
justfile
Normal file
|
@ -0,0 +1,2 @@
|
|||
clean:
|
||||
rm -rf tests/data/.task tests/data/.config
|
591
src/app.rs
591
src/app.rs
File diff suppressed because it is too large
Load diff
|
@ -5,7 +5,7 @@ use std::fmt;
|
|||
|
||||
const COL_WIDTH: usize = 21;
|
||||
|
||||
use chrono::{format::Fixed, Date, Datelike, Duration, FixedOffset, Local, Month, NaiveDate, NaiveDateTime, TimeZone};
|
||||
use chrono::{format::Fixed, DateTime, Datelike, Duration, FixedOffset, Local, Month, NaiveDate, NaiveDateTime, TimeZone};
|
||||
|
||||
use tui::{
|
||||
buffer::Buffer,
|
||||
|
@ -24,7 +24,7 @@ pub struct Calendar<'a> {
|
|||
pub month: u32,
|
||||
pub style: Style,
|
||||
pub months_per_row: usize,
|
||||
pub date_style: Vec<(Date<FixedOffset>, Style)>,
|
||||
pub date_style: Vec<(NaiveDate, Style)>,
|
||||
pub today_style: Style,
|
||||
pub start_on_monday: bool,
|
||||
pub title_background_color: Color,
|
||||
|
@ -32,8 +32,8 @@ pub struct Calendar<'a> {
|
|||
|
||||
impl<'a> Default for Calendar<'a> {
|
||||
fn default() -> Calendar<'a> {
|
||||
let year = Local::today().year();
|
||||
let month = Local::today().month();
|
||||
let year = Local::now().year();
|
||||
let month = Local::now().month();
|
||||
Calendar {
|
||||
block: None,
|
||||
style: Style::default(),
|
||||
|
@ -72,7 +72,7 @@ impl<'a> Calendar<'a> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn date_style(mut self, date_style: Vec<(Date<FixedOffset>, Style)>) -> Self {
|
||||
pub fn date_style(mut self, date_style: Vec<(NaiveDate, Style)>) -> Self {
|
||||
self.date_style = date_style;
|
||||
self
|
||||
}
|
||||
|
@ -112,17 +112,17 @@ impl<'a> Widget for Calendar<'a> {
|
|||
}
|
||||
|
||||
let style = self.style;
|
||||
let today = Local::today();
|
||||
let today = Local::now();
|
||||
|
||||
let year = self.year;
|
||||
let month = self.month;
|
||||
|
||||
let months: Vec<_> = (0..12).collect();
|
||||
|
||||
let mut days: Vec<(Date<FixedOffset>, Date<FixedOffset>)> = months
|
||||
let mut days: Vec<(NaiveDate, NaiveDate)> = months
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let first = Date::from_utc(NaiveDate::from_ymd(year, i + 1, 1), *Local::now().offset());
|
||||
let first = NaiveDate::from_ymd_opt(year, i + 1, 1).unwrap();
|
||||
let num_days = if self.start_on_monday {
|
||||
first.weekday().num_days_from_monday()
|
||||
} else {
|
||||
|
@ -179,7 +179,7 @@ impl<'a> Widget for Calendar<'a> {
|
|||
} else {
|
||||
"Su Mo Tu We Th Fr Sa"
|
||||
};
|
||||
buf.set_string(x as u16, y, days_string, style.add_modifier(Modifier::UNDERLINED));
|
||||
buf.set_string(x, y, days_string, style.add_modifier(Modifier::UNDERLINED));
|
||||
x += 21 + 1;
|
||||
}
|
||||
y += 1;
|
||||
|
@ -202,13 +202,13 @@ impl<'a> Widget for Calendar<'a> {
|
|||
if let Some(i) = index {
|
||||
style = self.date_style[i].1;
|
||||
}
|
||||
if d.1 == Local::today() {
|
||||
if d.1 == Local::now().date_naive() {
|
||||
buf.set_string(x, y, s, self.today_style);
|
||||
} else {
|
||||
buf.set_string(x, y, s, style);
|
||||
}
|
||||
x += 3;
|
||||
d.1 = Date::from_utc(d.1.naive_local() + Duration::days(1), *Local::now().offset());
|
||||
d.1 += Duration::days(1);
|
||||
}
|
||||
moredays |= d.0.month() == d.1.month() || d.1 < d.0;
|
||||
}
|
||||
|
@ -228,24 +228,14 @@ impl<'a> Widget for Calendar<'a> {
|
|||
&mut months
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let first = Date::from_utc(
|
||||
NaiveDate::from_ymd(self.year + new_year as i32, i + 1, 1),
|
||||
*Local::now().offset(),
|
||||
);
|
||||
(
|
||||
first,
|
||||
first - Duration::days(i64::from(first.weekday().num_days_from_sunday())),
|
||||
)
|
||||
let first = NaiveDate::from_ymd_opt(self.year + new_year as i32, i + 1, 1).unwrap();
|
||||
(first, first - Duration::days(i64::from(first.weekday().num_days_from_sunday())))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
|
||||
let x = area.x;
|
||||
let s = format!(
|
||||
"{year:^width$}",
|
||||
year = self.year as usize + new_year,
|
||||
width = area.width as usize
|
||||
);
|
||||
let s = format!("{year:^width$}", year = self.year as usize + new_year, width = area.width as usize);
|
||||
let mut style = Style::default().add_modifier(Modifier::UNDERLINED);
|
||||
if self.year + new_year as i32 == today.year() {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
|
|
12
src/cli.rs
12
src/cli.rs
|
@ -3,7 +3,7 @@ use clap::Arg;
|
|||
const APP_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const APP_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
|
||||
pub fn generate_cli_app() -> clap::Command<'static> {
|
||||
pub fn generate_cli_app() -> clap::Command {
|
||||
let mut app = clap::Command::new(APP_NAME)
|
||||
.version(APP_VERSION)
|
||||
.author("Dheepak Krishnamurthy <@kdheepak>")
|
||||
|
@ -14,7 +14,7 @@ pub fn generate_cli_app() -> clap::Command<'static> {
|
|||
.long("data")
|
||||
.value_name("FOLDER")
|
||||
.help("Sets the data folder for taskwarrior-tui")
|
||||
.takes_value(true),
|
||||
.action(clap::ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("config")
|
||||
|
@ -22,21 +22,21 @@ pub fn generate_cli_app() -> clap::Command<'static> {
|
|||
.long("config")
|
||||
.value_name("FOLDER")
|
||||
.help("Sets the config folder for taskwarrior-tui (currently not used)")
|
||||
.takes_value(true),
|
||||
.action(clap::ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("taskdata")
|
||||
.long("taskdata")
|
||||
.value_name("FOLDER")
|
||||
.help("Sets the .task folder using the TASKDATA environment variable for taskwarrior")
|
||||
.takes_value(true),
|
||||
.action(clap::ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("taskrc")
|
||||
.long("taskrc")
|
||||
.value_name("FILE")
|
||||
.help("Sets the .taskrc file using the TASKRC environment variable for taskwarrior")
|
||||
.takes_value(true),
|
||||
.action(clap::ArgAction::Set),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("report")
|
||||
|
@ -44,7 +44,7 @@ pub fn generate_cli_app() -> clap::Command<'static> {
|
|||
.long("report")
|
||||
.value_name("STRING")
|
||||
.help("Sets default report")
|
||||
.takes_value(true),
|
||||
.action(clap::ArgAction::Set),
|
||||
);
|
||||
|
||||
app.set_bin_name(APP_NAME);
|
||||
|
|
|
@ -8,11 +8,11 @@ use tui::{
|
|||
Terminal,
|
||||
};
|
||||
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::highlight::{Highlighter, MatchingBracketHighlighter};
|
||||
use rustyline::hint::Hinter;
|
||||
use rustyline::line_buffer::LineBuffer;
|
||||
use rustyline::Context;
|
||||
use rustyline::{error::ReadlineError, history::FileHistory};
|
||||
|
||||
use unicode_segmentation::Graphemes;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
@ -46,10 +46,8 @@ impl TaskwarriorTuiCompletionHelper {
|
|||
.iter()
|
||||
.filter_map(|(context, candidate)| {
|
||||
if context == &self.context
|
||||
&& (candidate.starts_with(&word[..pos])
|
||||
|| candidate.to_lowercase().starts_with(&word[..pos].to_lowercase()))
|
||||
&& (!self.input.contains(candidate)
|
||||
|| !self.input.to_lowercase().contains(&candidate.to_lowercase()))
|
||||
&& (candidate.starts_with(&word[..pos]) || candidate.to_lowercase().starts_with(&word[..pos].to_lowercase()))
|
||||
&& (!self.input.contains(candidate) || !self.input.to_lowercase().contains(&candidate.to_lowercase()))
|
||||
{
|
||||
Some((
|
||||
candidate.clone(), // display
|
||||
|
@ -101,11 +99,7 @@ impl CompletionList {
|
|||
state: ListState::default(),
|
||||
current: String::new(),
|
||||
pos: 0,
|
||||
helper: TaskwarriorTuiCompletionHelper {
|
||||
candidates,
|
||||
context,
|
||||
input,
|
||||
},
|
||||
helper: TaskwarriorTuiCompletionHelper { candidates, context, input },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +172,7 @@ impl CompletionList {
|
|||
}
|
||||
|
||||
pub fn candidates(&self) -> Vec<Completion> {
|
||||
let hist = rustyline::history::History::new();
|
||||
let hist = FileHistory::new();
|
||||
let ctx = rustyline::Context::new(&hist);
|
||||
let (pos, candidates) = self.helper.complete(&self.current, self.pos, &ctx).unwrap();
|
||||
candidates
|
||||
|
|
|
@ -158,19 +158,16 @@ impl Config {
|
|||
let uda_background_process = Self::get_uda_background_process(data);
|
||||
let uda_background_process_period = Self::get_uda_background_process_period(data);
|
||||
let uda_style_report_selection = uda_style_report_selection.unwrap_or_default();
|
||||
let uda_style_report_scrollbar =
|
||||
uda_style_report_scrollbar.unwrap_or_else(|| Style::default().fg(Color::Black));
|
||||
let uda_style_report_scrollbar = uda_style_report_scrollbar.unwrap_or_else(|| Style::default().fg(Color::Black));
|
||||
let uda_style_report_scrollbar_area = uda_style_report_scrollbar_area.unwrap_or_default();
|
||||
let uda_style_calendar_title = uda_style_calendar_title.unwrap_or_default();
|
||||
let uda_style_calendar_today =
|
||||
uda_style_calendar_today.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD));
|
||||
let uda_style_calendar_today = uda_style_calendar_today.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD));
|
||||
let uda_style_navbar = uda_style_navbar.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED));
|
||||
let uda_style_command = uda_style_command.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED));
|
||||
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().fg(Color::Black).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_style_report_completion_pane =
|
||||
uda_style_report_completion_pane.unwrap_or_else(|| Style::default().fg(Color::Black).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);
|
||||
|
@ -457,10 +454,7 @@ impl Config {
|
|||
}
|
||||
|
||||
fn get_due(data: &str) -> usize {
|
||||
Self::get_config("due", data)
|
||||
.unwrap_or_default()
|
||||
.parse::<usize>()
|
||||
.unwrap_or(7)
|
||||
Self::get_config("due", data).unwrap_or_default().parse::<usize>().unwrap_or(7)
|
||||
}
|
||||
|
||||
fn get_weekstart(data: &str) -> bool {
|
||||
|
@ -485,10 +479,7 @@ impl Config {
|
|||
fn get_filter(data: &str, report: &str) -> Result<String> {
|
||||
if report == "all" {
|
||||
Ok("".into())
|
||||
} else if let Some(s) = Self::get_config(
|
||||
format!("uda.taskwarrior-tui.task-report.{}.filter", report).as_str(),
|
||||
data,
|
||||
) {
|
||||
} else if let Some(s) = Self::get_config(format!("uda.taskwarrior-tui.task-report.{}.filter", report).as_str(), data) {
|
||||
Ok(s)
|
||||
} else {
|
||||
Ok(Self::get_config(format!("report.{}.filter", report).as_str(), data).unwrap_or_default())
|
||||
|
@ -509,10 +500,7 @@ impl Config {
|
|||
}
|
||||
|
||||
fn get_uda_auto_insert_double_quotes_on_annotate(data: &str) -> bool {
|
||||
Self::get_config(
|
||||
"uda.taskwarrior-tui.task-report.auto-insert-double-quotes-on-annotate",
|
||||
data,
|
||||
)
|
||||
Self::get_config("uda.taskwarrior-tui.task-report.auto-insert-double-quotes-on-annotate", data)
|
||||
.unwrap_or_default()
|
||||
.get_bool()
|
||||
.unwrap_or(true)
|
||||
|
@ -644,13 +632,7 @@ impl Config {
|
|||
let indicator = Self::get_config("uda.taskwarrior-tui.scrollbar.indicator", data);
|
||||
match indicator {
|
||||
None => FULL.to_string(),
|
||||
Some(indicator) => format!(
|
||||
"{}",
|
||||
indicator
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap_or_else(|| FULL.to_string().chars().next().unwrap())
|
||||
),
|
||||
Some(indicator) => format!("{}", indicator.chars().next().unwrap_or_else(|| FULL.to_string().chars().next().unwrap())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,9 +642,7 @@ impl Config {
|
|||
None => DOUBLE_VERTICAL.to_string(),
|
||||
Some(area) => format!(
|
||||
"{}",
|
||||
area.chars()
|
||||
.next()
|
||||
.unwrap_or_else(|| DOUBLE_VERTICAL.to_string().chars().next().unwrap())
|
||||
area.chars().next().unwrap_or_else(|| DOUBLE_VERTICAL.to_string().chars().next().unwrap())
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
12
src/event.rs
12
src/event.rs
|
@ -5,10 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
use tokio::{sync::mpsc, sync::oneshot, task::JoinHandle};
|
||||
|
||||
use crossterm::event::{
|
||||
KeyCode::{
|
||||
BackTab, Backspace, Char, Delete, Down, End, Enter, Esc, Home, Insert, Left, Null, PageDown, PageUp, Right,
|
||||
Tab, Up, F,
|
||||
},
|
||||
KeyCode::{BackTab, Backspace, Char, Delete, Down, End, Enter, Esc, Home, Insert, Left, Null, PageDown, PageUp, Right, Tab, Up, F},
|
||||
KeyModifiers,
|
||||
};
|
||||
|
||||
|
@ -127,11 +124,6 @@ impl EventLoop {
|
|||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
tx,
|
||||
rx,
|
||||
tick_rate,
|
||||
abort,
|
||||
}
|
||||
Self { tx, rx, tick_rate, abort }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use rustyline::error::ReadlineError;
|
||||
use rustyline::history::DefaultHistory;
|
||||
use rustyline::history::History;
|
||||
use rustyline::history::SearchDirection;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub struct HistoryContext {
|
||||
history: History,
|
||||
history: DefaultHistory,
|
||||
history_index: Option<usize>,
|
||||
data_path: PathBuf,
|
||||
}
|
||||
|
||||
impl HistoryContext {
|
||||
pub fn new(filename: &str) -> Self {
|
||||
let history = History::new();
|
||||
let history = DefaultHistory::new();
|
||||
|
||||
let data_path = if let Ok(s) = std::env::var("TASKWARRIOR_TUI_DATA") {
|
||||
PathBuf::from(s)
|
||||
|
@ -23,8 +24,7 @@ impl HistoryContext {
|
|||
.expect("Unable to create configuration directory for taskwarrior-tui")
|
||||
};
|
||||
|
||||
std::fs::create_dir_all(&data_path)
|
||||
.unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path));
|
||||
std::fs::create_dir_all(&data_path).unwrap_or_else(|_| panic!("Unable to create configuration directory in {:?}", &data_path));
|
||||
|
||||
let data_path = data_path.join(filename);
|
||||
|
||||
|
@ -51,7 +51,7 @@ impl HistoryContext {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn history(&self) -> &History {
|
||||
pub fn history(&self) -> &DefaultHistory {
|
||||
&self.history
|
||||
}
|
||||
|
||||
|
@ -83,9 +83,7 @@ impl HistoryContext {
|
|||
} else {
|
||||
let hi = self.history_index().unwrap();
|
||||
|
||||
if hi == self.history.len().saturating_sub(1) && dir == SearchDirection::Forward
|
||||
|| hi == 0 && dir == SearchDirection::Reverse
|
||||
{
|
||||
if hi == self.history.len().saturating_sub(1) && dir == SearchDirection::Forward || hi == 0 && dir == SearchDirection::Reverse {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -96,14 +94,22 @@ impl HistoryContext {
|
|||
};
|
||||
|
||||
log::debug!("Using history index = {} for searching", history_index);
|
||||
return if let Some(history_index) = self.history.starts_with(buf, history_index, dir) {
|
||||
return if let Some(history_index) = self.history.starts_with(buf, history_index, dir).unwrap() {
|
||||
log::debug!("Found index {:?}", history_index);
|
||||
log::debug!("Previous index {:?}", self.history_index);
|
||||
self.history_index = Some(history_index.idx);
|
||||
Some(history_index.entry.to_string())
|
||||
} else if buf.is_empty() {
|
||||
self.history_index = Some(history_index);
|
||||
self.history.get(history_index).cloned()
|
||||
Some(
|
||||
self
|
||||
.history
|
||||
.get(history_index, SearchDirection::Forward)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.entry
|
||||
.to_string(),
|
||||
)
|
||||
} else {
|
||||
log::debug!("History index = {}. Found no match.", history_index);
|
||||
None
|
||||
|
@ -111,10 +117,12 @@ impl HistoryContext {
|
|||
}
|
||||
|
||||
pub fn add(&mut self, buf: &str) {
|
||||
if self.history.add(buf) {
|
||||
if let Ok(x) = self.history.add(buf) {
|
||||
if x {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.history_index = None
|
||||
|
|
|
@ -202,11 +202,7 @@ impl KeyConfig {
|
|||
return Some(KeyCode::Char(line.chars().next().unwrap()));
|
||||
}
|
||||
} else if line.starts_with(&config.replace('-', "_")) {
|
||||
let line = line
|
||||
.trim_start_matches(&config.replace('-', "_"))
|
||||
.trim_start()
|
||||
.trim_end()
|
||||
.to_string();
|
||||
let line = line.trim_start_matches(&config.replace('-', "_")).trim_start().trim_end().to_string();
|
||||
if line.len() == 1 {
|
||||
return Some(KeyCode::Char(line.chars().next().unwrap()));
|
||||
}
|
||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -18,6 +18,7 @@ mod scrollbar;
|
|||
mod table;
|
||||
mod task_report;
|
||||
mod ui;
|
||||
mod utils;
|
||||
|
||||
use log::{debug, error, info, log_enabled, trace, warn, Level, LevelFilter};
|
||||
use log4rs::append::file::FileAppender;
|
||||
|
@ -73,10 +74,7 @@ pub fn initialize_logging() {
|
|||
.build(data_local_dir.join("taskwarrior-tui.log"))
|
||||
.expect("Failed to build log file appender.");
|
||||
|
||||
let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL")
|
||||
.unwrap_or_else(|_| "info".to_string())
|
||||
.as_str()
|
||||
{
|
||||
let levelfilter = match std::env::var("TASKWARRIOR_TUI_LOG_LEVEL").unwrap_or_else(|_| "info".to_string()).as_str() {
|
||||
"off" => LevelFilter::Off,
|
||||
"warn" => LevelFilter::Warn,
|
||||
"info" => LevelFilter::Info,
|
||||
|
@ -128,11 +126,12 @@ fn main() -> Result<()> {
|
|||
|
||||
let matches = cli::generate_cli_app().get_matches();
|
||||
|
||||
let config = matches.value_of("config");
|
||||
let data = matches.value_of("data");
|
||||
let taskrc = matches.value_of("taskrc");
|
||||
let taskdata = matches.value_of("taskdata");
|
||||
let report = matches.value_of("report").unwrap_or("next");
|
||||
let config = matches.get_one::<String>("config");
|
||||
let data = matches.get_one::<String>("data");
|
||||
let taskrc = matches.get_one::<String>("taskrc");
|
||||
let taskdata = matches.get_one::<String>("taskdata");
|
||||
let binding = String::from("next");
|
||||
let report = matches.get_one::<String>("report").unwrap_or(&binding);
|
||||
|
||||
if let Some(e) = config {
|
||||
if env::var("TASKWARRIOR_TUI_CONFIG").is_err() {
|
||||
|
@ -161,10 +160,7 @@ fn main() -> Result<()> {
|
|||
if let Some(e) = taskrc {
|
||||
if env::var("TASKRC").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var(
|
||||
"TASKRC",
|
||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"),
|
||||
)
|
||||
env::set_var("TASKRC", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskrc"))
|
||||
} else {
|
||||
warn!("TASKRC environment variable cannot be set.")
|
||||
}
|
||||
|
@ -173,10 +169,7 @@ fn main() -> Result<()> {
|
|||
if let Some(e) = taskdata {
|
||||
if env::var("TASKDATA").is_err() {
|
||||
// if environment variable is not set, this env::var returns an error
|
||||
env::set_var(
|
||||
"TASKDATA",
|
||||
absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"),
|
||||
)
|
||||
env::set_var("TASKDATA", absolute_path(PathBuf::from(e)).expect("Unable to get path for taskdata"))
|
||||
} else {
|
||||
warn!("TASKDATA environment variable cannot be set.")
|
||||
}
|
||||
|
|
|
@ -62,12 +62,7 @@ impl ContextsState {
|
|||
Self {
|
||||
table_state: TableState::default(),
|
||||
report_height: 0,
|
||||
columns: vec![
|
||||
NAME.to_string(),
|
||||
TYPE.to_string(),
|
||||
DEFINITION.to_string(),
|
||||
ACTIVE.to_string(),
|
||||
],
|
||||
columns: vec![NAME.to_string(), TYPE.to_string(), DEFINITION.to_string(), ACTIVE.to_string()],
|
||||
rows: vec![],
|
||||
}
|
||||
}
|
||||
|
@ -116,12 +111,7 @@ impl ContextsState {
|
|||
let definition = line.replacen(name, "", 1);
|
||||
let definition = definition.replacen(typ, "", 1);
|
||||
let definition = definition.strip_suffix(active).unwrap_or_default();
|
||||
let context = ContextDetails::new(
|
||||
name.to_string(),
|
||||
definition.trim().to_string(),
|
||||
active.to_string(),
|
||||
typ.to_string(),
|
||||
);
|
||||
let context = ContextDetails::new(name.to_string(), definition.trim().to_string(), active.to_string(), typ.to_string());
|
||||
self.rows.push(context);
|
||||
}
|
||||
if self.rows.iter().any(|r| r.active != "no") {
|
||||
|
@ -132,12 +122,7 @@ impl ContextsState {
|
|||
} else {
|
||||
self.rows.insert(
|
||||
0,
|
||||
ContextDetails::new(
|
||||
"none".to_string(),
|
||||
"".to_string(),
|
||||
"yes".to_string(),
|
||||
"read".to_string(),
|
||||
),
|
||||
ContextDetails::new("none".to_string(), "".to_string(), "yes".to_string(), "read".to_string()),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::app::{Mode, TaskwarriorTui};
|
|||
use crate::event::KeyCode;
|
||||
use crate::pane::Pane;
|
||||
use crate::table::TableState;
|
||||
use crate::utils::Changeset;
|
||||
use itertools::Itertools;
|
||||
use std::cmp::min;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
@ -99,14 +100,7 @@ impl ProjectsState {
|
|||
let rows = self
|
||||
.rows
|
||||
.iter()
|
||||
.map(|c| {
|
||||
vec![
|
||||
c.name.clone(),
|
||||
c.remaining.to_string(),
|
||||
c.avg_age.to_string(),
|
||||
c.complete.clone(),
|
||||
]
|
||||
})
|
||||
.map(|c| vec![c.name.clone(), c.remaining.to_string(), c.avg_age.to_string(), c.complete.clone()])
|
||||
.collect();
|
||||
let headers = self.columns.clone();
|
||||
(rows, headers)
|
||||
|
@ -114,9 +108,7 @@ impl ProjectsState {
|
|||
|
||||
pub fn last_line(&self, line: &str) -> bool {
|
||||
let words = line.trim().split(' ').map(|s| s.trim()).collect::<Vec<&str>>();
|
||||
return words.len() == 2
|
||||
&& words[0].chars().map(|c| c.is_numeric()).all(|b| b)
|
||||
&& (words[1] == "project" || words[1] == "projects");
|
||||
return words.len() == 2 && words[0].chars().map(|c| c.is_numeric()).all(|b| b) && (words[1] == "project" || words[1] == "projects");
|
||||
}
|
||||
|
||||
pub fn update_data(&mut self) -> Result<()> {
|
||||
|
@ -191,6 +183,6 @@ fn update_task_filter_by_selection(app: &mut TaskwarriorTui) -> Result<()> {
|
|||
|
||||
let mut filter = current_filter.replace(&last_project_pattern, "");
|
||||
filter = format!("{}{}", filter, new_project_pattern);
|
||||
app.filter.update(filter.as_str(), filter.len());
|
||||
app.filter.update(filter.as_str(), filter.len(), &mut Changeset::default());
|
||||
Ok(())
|
||||
}
|
||||
|
|
12
src/table.rs
12
src/table.rs
|
@ -335,9 +335,7 @@ where
|
|||
ccs.push(match *constraint {
|
||||
Constraint::Length(v) => variables[i] | EQ(MEDIUM) | f64::from(v),
|
||||
Constraint::Percentage(v) => variables[i] | EQ(WEAK) | (f64::from(v * area.width) / 100.0),
|
||||
Constraint::Ratio(n, d) => {
|
||||
variables[i] | EQ(WEAK) | (f64::from(area.width) * f64::from(n) / f64::from(d))
|
||||
}
|
||||
Constraint::Ratio(n, d) => variables[i] | EQ(WEAK) | (f64::from(area.width) * f64::from(n) / f64::from(d)),
|
||||
Constraint::Min(v) => variables[i] | GE(WEAK) | f64::from(v),
|
||||
Constraint::Max(v) => variables[i] | LE(WEAK) | f64::from(v),
|
||||
});
|
||||
|
@ -448,10 +446,7 @@ where
|
|||
});
|
||||
for (i, row) in self.rows.skip(state.offset).take(remaining).enumerate() {
|
||||
let (data, style, symbol) = match row {
|
||||
Row::Data(d) | Row::StyledData(d, _)
|
||||
if Some(i) == state.current_selection().map(|s| s - state.offset) =>
|
||||
{
|
||||
match state.mode {
|
||||
Row::Data(d) | Row::StyledData(d, _) if Some(i) == state.current_selection().map(|s| s - state.offset) => match state.mode {
|
||||
TableMode::MultipleSelection => {
|
||||
if state.marked.contains(&(i + state.offset)) {
|
||||
(d, highlight_style, mark_highlight_symbol.to_string())
|
||||
|
@ -460,8 +455,7 @@ where
|
|||
}
|
||||
}
|
||||
TableMode::SingleSelection => (d, highlight_style, highlight_symbol.to_string()),
|
||||
}
|
||||
}
|
||||
},
|
||||
Row::Data(d) => {
|
||||
if state.marked.contains(&(i + state.offset)) {
|
||||
(d, default_style, mark_symbol.to_string())
|
||||
|
|
|
@ -39,67 +39,37 @@ pub fn vague_format_date_time(from_dt: NaiveDateTime, to_dt: NaiveDateTime, with
|
|||
|
||||
if seconds >= 60 * 60 * 24 * 365 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}y{}mo",
|
||||
minus,
|
||||
seconds / year,
|
||||
(seconds - year * (seconds / year)) / month
|
||||
)
|
||||
format!("{}{}y{}mo", minus, seconds / year, (seconds - year * (seconds / year)) / month)
|
||||
} else {
|
||||
format!("{}{}y", minus, seconds / year)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 * 90 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}mo{}w",
|
||||
minus,
|
||||
seconds / month,
|
||||
(seconds - month * (seconds / month)) / week
|
||||
)
|
||||
format!("{}{}mo{}w", minus, seconds / month, (seconds - month * (seconds / month)) / week)
|
||||
} else {
|
||||
format!("{}{}mo", minus, seconds / month)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 * 14 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}w{}d",
|
||||
minus,
|
||||
seconds / week,
|
||||
(seconds - week * (seconds / week)) / day
|
||||
)
|
||||
format!("{}{}w{}d", minus, seconds / week, (seconds - week * (seconds / week)) / day)
|
||||
} else {
|
||||
format!("{}{}w", minus, seconds / week)
|
||||
};
|
||||
} else if seconds >= 60 * 60 * 24 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}d{}h",
|
||||
minus,
|
||||
seconds / day,
|
||||
(seconds - day * (seconds / day)) / hour
|
||||
)
|
||||
format!("{}{}d{}h", minus, seconds / day, (seconds - day * (seconds / day)) / hour)
|
||||
} else {
|
||||
format!("{}{}d", minus, seconds / day)
|
||||
};
|
||||
} else if seconds >= 60 * 60 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}h{}min",
|
||||
minus,
|
||||
seconds / hour,
|
||||
(seconds - hour * (seconds / hour)) / minute
|
||||
)
|
||||
format!("{}{}h{}min", minus, seconds / hour, (seconds - hour * (seconds / hour)) / minute)
|
||||
} else {
|
||||
format!("{}{}h", minus, seconds / hour)
|
||||
};
|
||||
} else if seconds >= 60 {
|
||||
return if with_remainder {
|
||||
format!(
|
||||
"{}{}min{}s",
|
||||
minus,
|
||||
seconds / minute,
|
||||
(seconds - minute * (seconds / minute))
|
||||
)
|
||||
format!("{}{}min{}s", minus, seconds / minute, (seconds - minute * (seconds / minute)))
|
||||
} else {
|
||||
format!("{}{}min", minus, seconds / minute)
|
||||
};
|
||||
|
@ -396,12 +366,7 @@ impl TaskReportTable {
|
|||
None => "".to_string(),
|
||||
},
|
||||
"tags" => match task.tags() {
|
||||
Some(v) => v
|
||||
.iter()
|
||||
.filter(|t| !self.virtual_tags.contains(t))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
Some(v) => v.iter().filter(|t| !self.virtual_tags.contains(t)).cloned().collect::<Vec<_>>().join(","),
|
||||
None => "".to_string(),
|
||||
},
|
||||
"recur" => match task.recur() {
|
||||
|
|
19
src/utils.rs
Normal file
19
src/utils.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use rustyline::line_buffer::ChangeListener;
|
||||
use rustyline::line_buffer::DeleteListener;
|
||||
use rustyline::line_buffer::Direction;
|
||||
|
||||
/// Undo manager
|
||||
#[derive(Default)]
|
||||
pub struct Changeset {}
|
||||
|
||||
impl DeleteListener for Changeset {
|
||||
fn delete(&mut self, idx: usize, string: &str, _: Direction) {}
|
||||
}
|
||||
|
||||
impl ChangeListener for Changeset {
|
||||
fn insert_char(&mut self, idx: usize, c: char) {}
|
||||
|
||||
fn insert_str(&mut self, idx: usize, string: &str) {}
|
||||
|
||||
fn replace(&mut self, idx: usize, old: &str, new: &str) {}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue