Compare commits
4 Commits
b3103d0173
...
f48eb23f0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f48eb23f0d | ||
|
|
f35d1f353e | ||
|
|
6b5b13af6b | ||
|
|
2c40b9b726 |
88
README.md
88
README.md
@ -10,9 +10,89 @@ Keep track of TODOs and deadlines using an interactive markdown TUI.
|
|||||||
This is roughly what the UI looks like in the terminal
|
This is roughly what the UI looks like in the terminal
|
||||||
|
|
||||||
```md
|
```md
|
||||||
Chores
|
[todue] Chores
|
||||||
- [x] Get groceries (2024-06-20; 16:00)
|
[x] (2024-06-20 16:00) Get groceries
|
||||||
- [ ] Do the dishes (2024-06-20; 20:00)
|
[ ] (2024-06-20 20:00) Do the dishes
|
||||||
- [ ] Take out the trash (2024-06-20; 21:00)
|
[ ] (2024-06-20 21:00) Take out the trash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Control scheme
|
||||||
|
|
||||||
|
The control scheme is vim-like and features a minimal line editor as well as datetime-input.
|
||||||
|
|
||||||
|
- `j`/`k`: move focus down/up
|
||||||
|
- `J`/`K`: move focused entry down/up
|
||||||
|
- `<space>`: toggle focused entry completed
|
||||||
|
- `q`: save and quit
|
||||||
|
- `Q`: quit without saving
|
||||||
|
- `g`/`G`: move focus to top/bottom
|
||||||
|
- `s`: cycle sort mode.
|
||||||
|
|
||||||
|
|
||||||
|
### TODO
|
||||||
|
|
||||||
|
Things that might be implemented in the future
|
||||||
|
|
||||||
|
- more controls
|
||||||
|
- `a`/`A`: append to entry text (enters line editor)
|
||||||
|
- `c`/`C`: change entry text (enters line editor)
|
||||||
|
- `i`/`I`: insert before entry text (enters line editor)
|
||||||
|
- `o`/`O`: edit new entry (after/before current - enters line then datetime editor)
|
||||||
|
- `r`: replace entry
|
||||||
|
- `/`/`?`: search entry by text (backwards) (wrapping)
|
||||||
|
- later on regex search
|
||||||
|
- `u`/`<ctrl-z>`: undo
|
||||||
|
- `<ctrl-r>`/`<ctrl-y>`: redo
|
||||||
|
- `z`: collapse/expand current group
|
||||||
|
- `yd`: copy entry date
|
||||||
|
- `yt`: copy entry text
|
||||||
|
- `yy`: copy entire entry
|
||||||
|
- `0`-`9`: as prefix for repeated commands
|
||||||
|
|
||||||
|
- line editor with vim commands (prefixed with mode)
|
||||||
|
- normal: `<esc>`: exit line editor
|
||||||
|
- normal: `a`/`A`: append (to end)
|
||||||
|
- normal: `i`/`I`: insert (at beginning)
|
||||||
|
- normal: `d`: delete
|
||||||
|
- normal: `x`: remove character
|
||||||
|
- normal: `c`/`C`: change
|
||||||
|
- normal: `r`/`R`: replace
|
||||||
|
- normal: `s`/`S`: substitute (equal to `cl` and `cc` respectively)
|
||||||
|
- normal: `v`: visual mode
|
||||||
|
- normal: `y`/`Y`: copy
|
||||||
|
- normal: `p`/`P`: paste
|
||||||
|
- normal: `u`/`<ctrl-z>`: undo
|
||||||
|
- normal: `<ctrl-r>`/`<ctrl-y>`: redo
|
||||||
|
- normal: `f`/`F` and `t`/`T`: find (until) (backwards)
|
||||||
|
- normal: `/`/`?`: search (backwards) (wrapping to beginning of line)
|
||||||
|
- later on regex search
|
||||||
|
- insert: `w`,`b`,`e`: like vim, including uppercase equivalent
|
||||||
|
- insert: `<esc>`: exit line editor
|
||||||
|
- insert: `<ctrl-w>`/`<ctrl-backspace>`: delete last word
|
||||||
|
- insert: `<ctrl-shift-v>`/`<shift-insert>`: paste
|
||||||
|
- visual: `a`/`i`: select all/inside of...
|
||||||
|
|
||||||
|
- datetime editor
|
||||||
|
- highlight date part (YYYY for example)
|
||||||
|
- `d`: remove entire deadline
|
||||||
|
- `<enter>`: go to next part
|
||||||
|
- `0`-`9`: input number (ignoring invalid inputs like months >12)
|
||||||
|
|
||||||
|
- sort mode: cycle through modes and set ascending/descending separately
|
||||||
|
- `r`: insert before entry text (enters line editor)
|
||||||
|
|
||||||
|
- collapsable todo group hierarchy
|
||||||
|
- detect indent width from md
|
||||||
|
- group entries together under previous entry with lower indent level
|
||||||
|
- display expandable groups in tui
|
||||||
|
|
||||||
|
- config
|
||||||
|
- keybinds
|
||||||
|
- some other options (?)
|
||||||
|
|
||||||
|
- some `:`-commands?
|
||||||
|
- regex substitution
|
||||||
|
- set commands for config entries
|
||||||
|
- help command that shows controls
|
||||||
|
- `set` with no key shows explicitly set keys (config and live)
|
||||||
|
- config wizard & write current config state to config file
|
||||||
|
|||||||
63
src/app.rs
Normal file
63
src/app.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
use crate::*;
|
||||||
|
|
||||||
|
use std::io::Stdout;
|
||||||
|
|
||||||
|
#[derive()]
|
||||||
|
pub struct App {
|
||||||
|
pub ui: Ui<Stdout>,
|
||||||
|
pub md_file: String,
|
||||||
|
pub running: bool,
|
||||||
|
pub keymap: Keymap,
|
||||||
|
pub mode: Mode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub fn init() -> anyhow::Result<Self> {
|
||||||
|
let cli = cli::new();
|
||||||
|
let args = cli.clone().get_matches();
|
||||||
|
|
||||||
|
let md_file = match args.get_one::<String>("file") {
|
||||||
|
Some(path) => path.into(),
|
||||||
|
_ => {
|
||||||
|
Log::warn("No filename specified -- falling back to `todo.md`");
|
||||||
|
"todo.md".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !path::Path::new(&md_file).exists() {
|
||||||
|
Log::error_exit_with(
|
||||||
|
ErrorCode::IO,
|
||||||
|
format!("Markdown file `{md_file}` not found. Exiting..."),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let md = fs::read_to_string(&md_file).unwrap();
|
||||||
|
let document = Document::from_md(md)?;
|
||||||
|
|
||||||
|
let ui = Ui::init(io::stdout(), document);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
ui,
|
||||||
|
md_file,
|
||||||
|
running: true,
|
||||||
|
keymap: Keymap::default(),
|
||||||
|
mode: Mode::Normal,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_input(&mut self) -> anyhow::Result<()> {
|
||||||
|
if poll(time::Duration::from_millis(250)).unwrap_or(false) {
|
||||||
|
if let Ok(Key(k)) = read() {
|
||||||
|
let a = self.keymap.map.get(&(self.mode.clone(), k));
|
||||||
|
if let Some(callback) = a {
|
||||||
|
(callback.clone())(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quit(&mut self) {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/control.rs
Normal file
114
src/control.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#![allow(dead_code, unused)] // todo
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use anyhow::*;
|
||||||
|
use crossterm::event::KeyEvent;
|
||||||
|
|
||||||
|
/// Mode of the TUI
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
|
pub enum Mode {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
Insert(EditMode),
|
||||||
|
Datetime,
|
||||||
|
Visual,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mode for the line editor
|
||||||
|
#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
|
pub enum EditMode {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
Insert,
|
||||||
|
Visual,
|
||||||
|
Replace,
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeymapCallback = Box<for<'a> fn(&'a mut App)>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Keymap {
|
||||||
|
pub map: HashMap<(Mode, KeyEvent), KeymapCallback>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keymap {
|
||||||
|
pub fn handle(&self, key: KeyEvent, app: &mut App) -> anyhow::Result<()> {
|
||||||
|
self.map
|
||||||
|
.get(&(app.mode.clone(), key))
|
||||||
|
.map(|f| f(app))
|
||||||
|
.ok_or(anyhow!("Associated mapping not found."))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(&mut self, mode: Mode, key: KeyEvent, f: KeymapCallback) {
|
||||||
|
self.map.insert((mode, key), f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Keymap {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut map = Self {
|
||||||
|
map: HashMap::new(),
|
||||||
|
};
|
||||||
|
use Mode::*;
|
||||||
|
map.register(Normal, Char('q').into(), Box::new(App::quit));
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('Q').into(),
|
||||||
|
Box::new(|app: &mut App| {
|
||||||
|
app.ui.dont_save_on_quit();
|
||||||
|
app.quit()
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('j').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selection(Down).unwrap()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('k').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selection(Up).unwrap()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('J').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selected_entry(Down)),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('K').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selected_entry(Up)),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('G').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selection_to_top()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('g').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.move_selection_to_bottom()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char(' ').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.toggle_active_entry()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('s').into(),
|
||||||
|
Box::new(|app: &mut App| app.ui.cycle_sort_mode()),
|
||||||
|
);
|
||||||
|
map.register(
|
||||||
|
Normal,
|
||||||
|
Char('-').into(),
|
||||||
|
Box::new(|app: &mut App| {
|
||||||
|
Log::info("Debug panic keybind '-' invoked -- panicking...");
|
||||||
|
panic!();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/main.rs
78
src/main.rs
@ -1,4 +1,4 @@
|
|||||||
use std::{
|
pub use std::{
|
||||||
fs,
|
fs,
|
||||||
io::{self, Write},
|
io::{self, Write},
|
||||||
path, process, time,
|
path, process, time,
|
||||||
@ -6,6 +6,12 @@ use std::{
|
|||||||
|
|
||||||
use crossterm::event::{poll, read, Event::*, KeyCode::Char};
|
use crossterm::event::{poll, read, Event::*, KeyCode::Char};
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
use app::*;
|
||||||
|
|
||||||
|
mod control;
|
||||||
|
use control::*;
|
||||||
|
|
||||||
mod md;
|
mod md;
|
||||||
use md::*;
|
use md::*;
|
||||||
|
|
||||||
@ -20,74 +26,42 @@ pub use log::*;
|
|||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let command = cli::new();
|
let mut app = App::init()?;
|
||||||
let args = command.clone().get_matches();
|
|
||||||
|
|
||||||
let md_file = match args.get_one::<String>("file") {
|
|
||||||
Some(path) => path,
|
|
||||||
_ => {
|
|
||||||
Log::warn("No filename specified -- falling back to `todo.md`");
|
|
||||||
&"todo.md".to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !path::Path::new(md_file).exists() {
|
|
||||||
Log::error_exit_with(
|
|
||||||
ErrorCode::IO,
|
|
||||||
format!("Markdown file `{md_file}` not found. Exiting..."),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let md = fs::read_to_string(md_file).unwrap();
|
|
||||||
let document = Document::from_md(md)?;
|
|
||||||
|
|
||||||
let mut ui = Ui::init(io::stdout(), document);
|
|
||||||
let _guard = DropGuard {
|
let _guard = DropGuard {
|
||||||
|
// clean up terminal state even on panics
|
||||||
exec_on_drop: || {
|
exec_on_drop: || {
|
||||||
{
|
// needs two braces to function properly
|
||||||
let _ = Ui::init(&mut io::stdout(), Document::default()).deinit();
|
let _ = Ui::init(&mut io::stdout(), Document::default()).deinit();
|
||||||
}
|
Log::flush();
|
||||||
.into()
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
while app.running {
|
||||||
ui.draw()?;
|
app.ui.draw()?;
|
||||||
if poll(time::Duration::from_millis(250)).unwrap_or(false) {
|
app.handle_input()?;
|
||||||
if let Ok(Key(k)) = read() {
|
|
||||||
match k.code {
|
|
||||||
Char('q') => break,
|
|
||||||
Char('j') => ui.move_selection(Down)?,
|
|
||||||
Char('k') => ui.move_selection(Up)?,
|
|
||||||
Char('J') => ui.move_selected_entry(Down),
|
|
||||||
Char('K') => ui.move_selected_entry(Up),
|
|
||||||
Char('G') => ui.move_selection_to_bottom(),
|
|
||||||
Char('g') => ui.move_selection_to_top(),
|
|
||||||
Char(' ') => ui.toggle_active_entry(),
|
|
||||||
Char('s') => ui.cycle_sort_mode(),
|
|
||||||
Char('Q') => { ui.dont_save_on_quit(); break; },
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ui.save_on_quit {
|
if app.ui.save_on_quit {
|
||||||
Log::info(format!(
|
Log::info(format!(
|
||||||
"Writing updated markdown to file `{md_file}` and exiting..."
|
"Writing updated markdown to file `{}` and exiting...",
|
||||||
|
app.md_file
|
||||||
));
|
));
|
||||||
let file = fs::File::create(md_file);
|
let file = fs::File::create(&app.md_file);
|
||||||
if file.is_err() {
|
if file.is_err() {
|
||||||
Log::error(format!("Failed to open file for writing: `{md_file}`"));
|
Log::error(format!(
|
||||||
|
"Failed to open file for writing: `{}`",
|
||||||
|
app.md_file
|
||||||
|
));
|
||||||
process::exit(ErrorCode::IO.into());
|
process::exit(ErrorCode::IO.into());
|
||||||
}
|
}
|
||||||
write!(file.unwrap(), "{}", ui.document.to_md()).unwrap_or_else(|e| {
|
write!(file.unwrap(), "{}", app.ui.document.to_md()).unwrap_or_else(|e| {
|
||||||
Log::error(format!("Failed to write to file: `{md_file}`: {e}"));
|
Log::error(format!("Failed to write to file: `{}`: {}", app.md_file, e));
|
||||||
process::exit(ErrorCode::IO.into());
|
process::exit(ErrorCode::IO.into());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.deinit()?;
|
app.ui.deinit()?;
|
||||||
Log::flush();
|
Log::flush();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,11 +32,7 @@ impl Markdown for Entry {
|
|||||||
let mut md = format!("- [{}] ", if self.done { "x" } else { " " });
|
let mut md = format!("- [{}] ", if self.done { "x" } else { " " });
|
||||||
match self.deadline {
|
match self.deadline {
|
||||||
Some(deadline) => md += &format!("{} ", deadline.format("(%Y-%m-%d %H:%M)")),
|
Some(deadline) => md += &format!("{} ", deadline.format("(%Y-%m-%d %H:%M)")),
|
||||||
None => {
|
None => md += &str::repeat(" ", "(YYYY-mm-dd HH:MM) ".len()),
|
||||||
md += &std::iter::repeat(' ')
|
|
||||||
.take("(YYYY-mm-dd HH:MM) ".len())
|
|
||||||
.collect::<String>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
md += &self.text;
|
md += &self.text;
|
||||||
md
|
md
|
||||||
|
|||||||
63
src/ui.rs
63
src/ui.rs
@ -17,6 +17,7 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// state of the user interface
|
/// state of the user interface
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Ui<T>
|
pub struct Ui<T>
|
||||||
where
|
where
|
||||||
T: io::Write,
|
T: io::Write,
|
||||||
@ -35,7 +36,7 @@ where
|
|||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub active_entry_idx: usize,
|
pub active_entry_idx: usize,
|
||||||
pub current_scroll_offset: usize,
|
pub current_scroll_offset: usize,
|
||||||
queue_sort_update: bool
|
queue_sort_update: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Ui<T>
|
impl<T> Ui<T>
|
||||||
@ -59,7 +60,7 @@ where
|
|||||||
inactive_color_pair,
|
inactive_color_pair,
|
||||||
inactive_done_color_pair,
|
inactive_done_color_pair,
|
||||||
header_color_pair,
|
header_color_pair,
|
||||||
current_sort_mode: Default,
|
current_sort_mode: SortMode::Default,
|
||||||
active_entry_idx: 0,
|
active_entry_idx: 0,
|
||||||
scrolloff: 8,
|
scrolloff: 8,
|
||||||
current_scroll_offset: 0,
|
current_scroll_offset: 0,
|
||||||
@ -95,6 +96,7 @@ where
|
|||||||
RestorePosition,
|
RestorePosition,
|
||||||
cursor::Show
|
cursor::Show
|
||||||
)?;
|
)?;
|
||||||
|
writeln!(self.ostream)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,11 +211,15 @@ where
|
|||||||
/// update the index of the first *shown* entry using `self.scrolloff`
|
/// update the index of the first *shown* entry using `self.scrolloff`
|
||||||
pub fn update_scroll_offset(&mut self) {
|
pub fn update_scroll_offset(&mut self) {
|
||||||
if self.current_scroll_offset + self.scrolloff >= self.active_entry_idx {
|
if self.current_scroll_offset + self.scrolloff >= self.active_entry_idx {
|
||||||
let diff = (self.current_scroll_offset + self.scrolloff).abs_diff(self.active_entry_idx);
|
let diff =
|
||||||
|
(self.current_scroll_offset + self.scrolloff).abs_diff(self.active_entry_idx);
|
||||||
self.current_scroll_offset = self.current_scroll_offset.saturating_sub(diff);
|
self.current_scroll_offset = self.current_scroll_offset.saturating_sub(diff);
|
||||||
|
} else if (self.current_scroll_offset + self.inner_height()).saturating_sub(self.scrolloff)
|
||||||
} else if (self.current_scroll_offset + self.inner_height()).saturating_sub(self.scrolloff) <= self.active_entry_idx {
|
<= self.active_entry_idx
|
||||||
let diff = (self.current_scroll_offset + self.inner_height()).saturating_sub(self.scrolloff).abs_diff(self.active_entry_idx);
|
{
|
||||||
|
let diff = (self.current_scroll_offset + self.inner_height())
|
||||||
|
.saturating_sub(self.scrolloff)
|
||||||
|
.abs_diff(self.active_entry_idx);
|
||||||
self.current_scroll_offset = (self.current_scroll_offset + diff).min(
|
self.current_scroll_offset = (self.current_scroll_offset + diff).min(
|
||||||
self.document
|
self.document
|
||||||
.entries
|
.entries
|
||||||
@ -269,11 +275,11 @@ where
|
|||||||
|
|
||||||
pub fn cycle_sort_mode(&mut self) {
|
pub fn cycle_sort_mode(&mut self) {
|
||||||
self.current_sort_mode = match self.current_sort_mode {
|
self.current_sort_mode = match self.current_sort_mode {
|
||||||
Default => ByDeadlineDescending,
|
SortMode::Default => SortMode::ByDeadlineDescending,
|
||||||
ByDeadlineDescending => ByDeadlineAscending,
|
SortMode::ByDeadlineDescending => SortMode::ByDeadlineAscending,
|
||||||
ByDeadlineAscending => ByTextAscending,
|
SortMode::ByDeadlineAscending => SortMode::ByTextAscending,
|
||||||
ByTextAscending => ByTextDescending,
|
SortMode::ByTextAscending => SortMode::ByTextDescending,
|
||||||
ByTextDescending => Default,
|
SortMode::ByTextDescending => SortMode::Default,
|
||||||
};
|
};
|
||||||
self.queue_sort_update = true;
|
self.queue_sort_update = true;
|
||||||
}
|
}
|
||||||
@ -281,17 +287,24 @@ where
|
|||||||
pub fn apply_sort_mode(&mut self) {
|
pub fn apply_sort_mode(&mut self) {
|
||||||
if self.queue_sort_update {
|
if self.queue_sort_update {
|
||||||
match self.current_sort_mode {
|
match self.current_sort_mode {
|
||||||
Default => self.document = self.original_document.clone(),
|
SortMode::Default => self.document = self.original_document.clone(),
|
||||||
ByDeadlineDescending => {
|
SortMode::ByDeadlineDescending => {
|
||||||
self.document.entries.sort_by_key(|entry| entry.deadline);
|
self.document.entries.sort_by_key(|entry| entry.deadline);
|
||||||
self.document.entries.reverse();
|
self.document.entries.reverse();
|
||||||
},
|
}
|
||||||
ByDeadlineAscending => self.document.entries.sort_by_key(|entry| entry.deadline),
|
SortMode::ByDeadlineAscending => {
|
||||||
ByTextAscending => self.document.entries.sort_by_key(|entry| entry.text.to_lowercase()),
|
self.document.entries.sort_by_key(|entry| entry.deadline)
|
||||||
ByTextDescending => {
|
}
|
||||||
self.document.entries.sort_by_key(|entry| entry.text.to_lowercase());
|
SortMode::ByTextAscending => self
|
||||||
|
.document
|
||||||
|
.entries
|
||||||
|
.sort_by_key(|entry| entry.text.to_lowercase()),
|
||||||
|
SortMode::ByTextDescending => {
|
||||||
|
self.document
|
||||||
|
.entries
|
||||||
|
.sort_by_key(|entry| entry.text.to_lowercase());
|
||||||
self.document.entries.reverse();
|
self.document.entries.reverse();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
self.queue_sort_update = false;
|
self.queue_sort_update = false;
|
||||||
}
|
}
|
||||||
@ -303,17 +316,27 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Default for Ui<T>
|
||||||
|
where
|
||||||
|
T: Default + io::Write,
|
||||||
|
{
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::init(T::default(), Document::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum MoveDirection {
|
pub enum MoveDirection {
|
||||||
Down,
|
Down,
|
||||||
Up,
|
Up,
|
||||||
}
|
}
|
||||||
pub use MoveDirection::*;
|
pub use MoveDirection::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
pub enum SortMode {
|
pub enum SortMode {
|
||||||
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
ByDeadlineDescending,
|
ByDeadlineDescending,
|
||||||
ByDeadlineAscending,
|
ByDeadlineAscending,
|
||||||
ByTextAscending,
|
ByTextAscending,
|
||||||
ByTextDescending,
|
ByTextDescending,
|
||||||
}
|
}
|
||||||
pub use SortMode::*;
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user