This commit is contained in:
Dheepak Krishnamurthy 2023-09-27 12:02:23 -04:00
parent 6f7d3e5f0b
commit c01c9f6de1
5 changed files with 188 additions and 126 deletions

1
Cargo.lock generated
View file

@ -1998,6 +1998,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3e785f863a3af4c800a2a669d0b64c879b538738e352607e2624d03f868dc01"
dependencies = [
"crossterm",
"serde",
"unicode-width",
]

View file

@ -49,7 +49,7 @@ toml = "0.8.0"
tracing = "0.1.37"
tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tui-input = "0.8.0"
tui-input = { version = "0.8.0", features = ["serde"] }
unicode-segmentation = "1.10.1"
unicode-truncate = "0.2.0"
unicode-width = "0.1.10"

View file

@ -35,7 +35,7 @@ impl App {
pub fn new(tick_rate: f64, frame_rate: f64, report: &str) -> Result<Self> {
let app = TaskReport::new().report(report.into());
let mut config = Config::new()?;
config.taskwarrior_config()?;
config.taskwarrior.taskwarrior_config(report)?;
let mode = Mode::TaskReport;
Ok(Self {
tick_rate,

View file

@ -8,7 +8,7 @@ use ratatui::{prelude::*, widgets::*};
use serde_derive::{Deserialize, Serialize};
use task_hookrs::{import::import, status::TaskStatus, task::Task, uda::UDAValue};
use tokio::sync::mpsc::UnboundedSender;
use tui_input::backend::crossterm::EventHandler;
use tui_input::{backend::crossterm::EventHandler, Input};
use unicode_truncate::UnicodeTruncateStr;
use unicode_width::UnicodeWidthStr;
use uuid::Uuid;
@ -55,26 +55,49 @@ const VIRTUAL_TAGS: [&str; 34] = [
"TEMPLATE",
];
#[derive(Default)]
pub enum Mode {
#[default]
Report,
Filter,
Add,
Annotate,
Subprocess,
Log,
Modify,
HelpPopup,
ContextMenu,
Jump,
DeletePrompt,
UndoPrompt,
DonePrompt,
Error,
}
#[derive(Default)]
pub struct TaskReport {
pub config: Config,
pub command_tx: Option<UnboundedSender<Action>>,
pub last_export: Option<std::time::SystemTime>,
pub report: String,
pub filter: String,
pub current_context_filter: String,
pub tasks: Vec<Task>,
pub rows: Vec<Vec<String>>,
pub row_heights: Vec<u16>,
pub state: TableState,
pub columns: Vec<String>,
pub labels: Vec<String>,
pub date_time_vague_precise: bool,
pub virtual_tags: Vec<String>,
pub description_width: usize,
pub command_tx: Option<UnboundedSender<Action>>,
pub config: Config,
pub current_context: String,
pub current_context_filter: String,
pub current_filter: String,
pub current_selection: usize,
pub current_selection_id: Option<u64>,
pub current_selection_uuid: Option<Uuid>,
pub date_time_vague_precise: bool,
pub description_width: usize,
pub input: Input,
pub labels: Vec<String>,
pub last_export: Option<std::time::SystemTime>,
pub report: String,
pub row_heights: Vec<u16>,
pub rows: Vec<Vec<String>>,
pub state: TableState,
pub task_details: HashMap<Uuid, String>,
pub tasks: Vec<Task>,
pub virtual_tags: Vec<String>,
pub mode: Mode,
}
impl TaskReport {
@ -432,6 +455,29 @@ impl TaskReport {
}
}
pub fn get_context(&mut self) -> Result<()> {
let output = std::process::Command::new("task").arg("_get").arg("rc.context").output()?;
self.current_context = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context = self.current_context.strip_suffix('\n').unwrap_or("").to_string();
// support new format for context
let output = std::process::Command::new("task")
.arg("_get")
.arg(format!("rc.context.{}.read", self.current_context))
.output()?;
self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string();
// If new format is not used, check if old format is used
if self.current_context_filter.is_empty() {
let output =
std::process::Command::new("task").arg("_get").arg(format!("rc.context.{}", self.current_context)).output()?;
self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string();
self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string();
}
Ok(())
}
pub fn task_export(&mut self) -> Result<()> {
let mut task = std::process::Command::new("task");
@ -443,7 +489,9 @@ impl TaskReport {
.arg("rc._forcecolor=off");
// .arg("rc.verbose:override=false");
if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.trim()).trim()) {
if let Some(args) =
shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.current_filter.trim()).trim())
{
for arg in args {
task.arg(arg);
}

View file

@ -15,18 +15,133 @@ const CONFIG: &str = include_str!("../.config/config.json5");
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TaskwarriorConfig {
#[serde(default)]
pub rule_precedence_color: Vec<String>,
#[serde(default)]
pub uda_priority_values: Vec<String>,
#[serde(default)]
pub weekstart: bool,
#[serde(default)]
pub due: usize,
#[serde(default)]
pub color: HashMap<String, Style>,
#[serde(default)]
pub data_location: String,
pub filter: String,
}
impl TaskwarriorConfig {
pub fn taskwarrior_config(&mut self, report: &str) -> Result<()> {
let output = std::process::Command::new("task")
.arg("rc.color=off")
.arg("rc._forcecolor=off")
.arg("rc.defaultwidth=0")
.arg("show")
.output()?;
if !output.status.success() {
let output = std::process::Command::new("task").arg("diagnostics").output()?;
return Err(color_eyre::eyre::eyre!(
"Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
let data = String::from_utf8_lossy(&output.stdout);
self.rule_precedence_color(&data);
self.uda_priority_values(&data);
self.weekstart(&data);
self.due(&data);
self.data_location(&data);
self.color(&data);
self.filter(&data, &report);
Ok(())
}
fn color(&mut self, data: &str) {
let mut color = HashMap::new();
for line in data.split('\n') {
if line.starts_with("color.") {
let mut i = line.split(' ');
let attribute = i.next();
let line = i.collect::<Vec<_>>().join(" ");
let line = line.trim_start_matches(' ');
let style = parse_style(line);
if let Some(attr) = attribute {
color.insert(attr.to_string(), style);
};
}
}
log::info!("{color:?}");
self.color = color;
}
fn filter(&mut self, data: &str, report: &str) {
let filter = if report == "all" {
"".into()
} else if let Some(s) =
// TODO: move this to configuration struct
Self::try_get_config(format!("uda.taskwarrior-tui.task-report.{}.filter", report).as_str(), data)
{
s
} else {
Self::get_config(format!("report.{}.filter", report).as_str(), data).unwrap_or_default()
};
let filter = if filter.trim_start().trim_end().is_empty() { filter } else { format!("{} ", filter) };
self.filter = filter;
}
fn data_location(&mut self, data: &str) {
self.data_location = Self::get_config("data.location", data).unwrap();
}
fn rule_precedence_color(&mut self, data: &str) {
let data = Self::get_config("rule.precedence.color", data).unwrap();
self.rule_precedence_color = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}
fn weekstart(&mut self, data: &str) {
let data = Self::try_get_config("weekstart", data).unwrap_or_default();
self.weekstart = data.eq_ignore_ascii_case("Monday");
}
fn due(&mut self, data: &str) {
self.due = Self::try_get_config("due", data).unwrap_or_default().parse::<usize>().unwrap_or(7)
}
fn uda_priority_values(&mut self, data: &str) {
let data = Self::get_config("uda.priority.values", data).unwrap();
self.uda_priority_values = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}
fn get_config(config: &str, data: &str) -> Result<String> {
Self::try_get_config(config, data).ok_or(color_eyre::eyre::eyre!("Unable to parse `task show {config}`"))
}
fn try_get_config(config: &str, data: &str) -> Option<String> {
let mut config_lines = Vec::new();
for line in data.split('\n') {
if config_lines.is_empty() {
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
} else {
let config = &config.replace('-', "_");
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
}
}
} else {
if !line.starts_with(" ") {
return Some(config_lines.join(" "));
}
config_lines.push(line.trim_start().trim_end().to_string());
}
}
if !config_lines.is_empty() {
return Some(config_lines.join(" "));
}
None
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
@ -131,108 +246,6 @@ impl Config {
Ok(cfg)
}
pub fn taskwarrior_config(&mut self) -> Result<()> {
let output = std::process::Command::new("task")
.arg("rc.color=off")
.arg("rc._forcecolor=off")
.arg("rc.defaultwidth=0")
.arg("show")
.output()?;
if !output.status.success() {
let output = std::process::Command::new("task").arg("diagnostics").output()?;
return Err(color_eyre::eyre::eyre!(
"Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
));
}
let data = String::from_utf8_lossy(&output.stdout);
self.rule_precedence_color(&data);
self.uda_priority_values(&data);
self.weekstart(&data);
self.due(&data);
self.data_location(&data);
self.color(&data);
Ok(())
}
fn color(&mut self, data: &str) {
let mut color = HashMap::new();
for line in data.split('\n') {
if line.starts_with("color.") {
let mut i = line.split(' ');
let attribute = i.next();
let line = i.collect::<Vec<_>>().join(" ");
let line = line.trim_start_matches(' ');
let style = parse_style(line);
if let Some(attr) = attribute {
color.insert(attr.to_string(), style);
};
}
}
log::info!("{color:?}");
self.taskwarrior.color = color;
}
fn data_location(&mut self, data: &str) {
self.taskwarrior.data_location = get_config("data.location", data).unwrap();
}
fn rule_precedence_color(&mut self, data: &str) {
let data = get_config("rule.precedence.color", data).unwrap();
self.taskwarrior.rule_precedence_color = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}
fn weekstart(&mut self, data: &str) {
let data = try_get_config("weekstart", data).unwrap_or_default();
self.taskwarrior.weekstart = data.eq_ignore_ascii_case("Monday");
}
fn due(&mut self, data: &str) {
self.taskwarrior.due = try_get_config("due", data).unwrap_or_default().parse::<usize>().unwrap_or(7)
}
fn uda_priority_values(&mut self, data: &str) {
let data = get_config("uda.priority.values", data).unwrap();
self.taskwarrior.uda_priority_values = data.split(',').map(ToString::to_string).collect::<Vec<_>>();
}
}
fn get_config(config: &str, data: &str) -> Result<String> {
try_get_config(config, data).ok_or(color_eyre::eyre::eyre!("Unable to parse `task show {config}`"))
}
fn try_get_config(config: &str, data: &str) -> Option<String> {
let mut config_lines = Vec::new();
for line in data.split('\n') {
if config_lines.is_empty() {
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
} else {
let config = &config.replace('-', "_");
if line.starts_with(config) {
config_lines.push(line.trim_start_matches(config).trim_start().trim_end().to_string());
}
}
} else {
if !line.starts_with(" ") {
return Some(config_lines.join(" "));
}
config_lines.push(line.trim_start().trim_end().to_string());
}
}
if !config_lines.is_empty() {
return Some(config_lines.join(" "));
}
None
}
#[derive(Clone, Debug, Default, Deref, DerefMut)]