aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2024-10-20 18:14:17 +0200
committerlukeflo2024-10-20 18:14:17 +0200
commit735264bb3d673c943e2e8c4898dba0609f49b3f4 (patch)
tree675710467f814f611c416b56730a8760c9a15349
parent730055489a660b0cdff950f6c7037fe7a07545ae (diff)
downloadbibiman-735264bb3d673c943e2e8c4898dba0609f49b3f4.tar.gz
bibiman-735264bb3d673c943e2e8c4898dba0609f49b3f4.zip
Implement Sorting, jumping multiple entries
- Select a column with `j` and `l` - Sort selected column with `s` (toggles regular and reversed order) - Jump list up/down by 5 entries Vim-style using `C-d` and `C-u`
-rw-r--r--Cargo.lock2
-rw-r--r--Cargo.toml2
-rw-r--r--README.md39
-rw-r--r--src/frontend/entries.rs88
-rw-r--r--src/frontend/handler.rs49
-rw-r--r--src/frontend/keywords.rs8
-rw-r--r--src/frontend/ui.rs101
7 files changed, 217 insertions, 72 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 8a84eaf..02d2522 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -71,7 +71,7 @@ dependencies = [
[[package]]
name = "bibiman"
-version = "0.3.1"
+version = "0.4.2"
dependencies = [
"arboard",
"biblatex",
diff --git a/Cargo.toml b/Cargo.toml
index 0217ca5..1ae10fc 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "bibiman"
-version = "0.4.0"
+version = "0.4.2"
authors = ["lukeflo <lukeflo@some.email.not>"]
license = "GPL-3.0-or-later"
edition = "2021"
diff --git a/README.md b/README.md
index ae34818..c1c35ae 100644
--- a/README.md
+++ b/README.md
@@ -60,30 +60,36 @@ updated:
- [x] **Open related PDF** file (`file` BibLaTeX key) with keypress.
- [x] **Open related URL/DOI** with keypress.
- [x] **Scrollbar** for better navigating.
+- [x] **Sort Entries** by different each column (`Authors`, `Title`, `Year`,
+ `Pubtype`)
- [ ] **Open related notes file** for specific entry.
- [ ] **Add Entry via DOI** as formatted code.
-- [ ] **Sort Entries** by different values (partly possible for author column)
-- [ ] **Support Hayagriva(`.yaml.`)** format as input.
- [ ] **Implement config file** for setting some default values like main
bibfile, PDF-opener, or editor
+- [ ] **Support Hayagriva(`.yaml`)** format as input (_on hold for now_, because
+ the Hayagriva Yaml style doesn't offer keywords; s. issue in
+ [Hayagriva repo](https://github.com/typst/hayagriva/issues/240)).
## Keybindings
Use the following keybindings to manage the TUI:
-| Key | Action |
-| -------------------------------------------------------------------------------- | ------------------------------------------- |
-| **<kbd>j</kbd><kbd>k</kbd>** \| **<kbd>Down</kbd><kbd>Up</kbd>** | Move selected list |
-| **<kbd>g</kbd><kbd>G</kbd>** | Go to first/last entry |
-| **<kbd>PageDown</kbd><kbd>PageUp</kbd>** \| **<kbd>Alt-j</kbd><kbd>Alt-k</kbd>** | Scroll Info window |
-| **<kbd>y</kbd>** | Yank/copy citekey of selected entry |
-| **<kbd>e</kbd>** | Open editor at selected entry |
-| **<kbd>o</kbd>** \| **<kbd>u</kbd>** | Open related PDF \| URL/DOI |
-| **<kbd>TAB</kbd>** | Switch between entries and keywords |
-| **<kbd>/</kbd>** \| **<kbd>Ctrl-f</kbd>** | Enter search mode |
-| **<kbd>Enter</kbd>** | Filter by selected keyword / Confirm search |
-| **<kbd>ESC</kbd>** | Abort search / Reset current list |
-| **<kbd>q</kbd>** \| **<kbd>Ctrl-c</kbd>** | Quit TUI |
+| Key | Action |
+| -------------------------------------- | ------------------------------------------- |
+| `j`,`k` \| `Down`,`Up` | Move down/up by 1 |
+| `Ctrl-d` \| `Ctrl-u` | Move down/up by 5 |
+| `g`,`G` | Go to first/last entry |
+| `h`,`k` | Select previous/next entry column |
+| `s` | Sort current column (toggles) |
+| `PageDown`,`PageUp` \| `Alt-j`,`Alt-k` | Scroll Info window |
+| `y` | Yank/copy citekey of selected entry |
+| `e` | Open editor at selected entry |
+| `o` \| `u` | Open related PDF \| URL/DOI |
+| `TAB` | Switch between entries and keywords |
+| `/` \| `Ctrl-f` | Enter search mode |
+| `Enter` | Filter by selected keyword / Confirm search |
+| `ESC` | Abort search / Reset current list |
+| `q` \| `Ctrl-c` | Quit TUI |
## Search
@@ -122,7 +128,8 @@ Now, `bibiman` also provides the possibility to open PDFs (as value of the
`file` BibLaTeX field), as well as DOIs and URLs.
For selecting the right program, it uses `xdg-open` on Linux, `open` on MacOS,
-and `start` on Windows. _MacOS and Windows are untested right now!_
+and `start` on Windows. _MacOS is untested right now! Windows does not work,
+have to figure this out_
Furhtermore, DOIs have to begin with either `https://doi...` as full URL or
`10.(...)` as regular DOI style. URLs work if they begin with either `http...`
diff --git a/src/frontend/entries.rs b/src/frontend/entries.rs
index 6c227df..d5b0d8c 100644
--- a/src/frontend/entries.rs
+++ b/src/frontend/entries.rs
@@ -24,11 +24,20 @@ use editor_command::EditorBuilder;
use ratatui::widgets::{ScrollbarState, TableState};
use std::process::{Command, Stdio};
+#[derive(Debug)]
+pub enum EntryTableColumn {
+ Authors,
+ Title,
+ Year,
+ Pubtype,
+}
+
// Define list containing entries as table
#[derive(Debug)]
pub struct EntryTable {
pub entry_table_items: Vec<EntryTableItem>,
pub entry_table_at_search_start: Vec<EntryTableItem>,
+ pub entry_table_selected_column: EntryTableColumn,
pub entry_table_reversed_sort: bool,
pub entry_table_state: TableState,
pub entry_scroll_state: ScrollbarState,
@@ -45,6 +54,7 @@ impl EntryTable {
Self {
entry_table_items,
entry_table_at_search_start: Vec::new(),
+ entry_table_selected_column: EntryTableColumn::Authors,
entry_table_reversed_sort: false,
entry_table_state,
entry_scroll_state,
@@ -76,35 +86,39 @@ impl EntryTable {
// Sort entry table by specific column.
// Toggle sorting by hitting same key again
- pub fn sort_entry_table(&mut self, sorting: &str, toggle: bool) {
+ pub fn sort_entry_table(&mut self, toggle: bool) {
if toggle {
self.entry_table_reversed_sort = !self.entry_table_reversed_sort;
}
if self.entry_table_reversed_sort {
- match sorting {
- "author" => self
+ match self.entry_table_selected_column {
+ EntryTableColumn::Authors => self
.entry_table_items
.sort_by(|a, b| b.authors.to_lowercase().cmp(&a.authors.to_lowercase())),
- "title" => self
+ EntryTableColumn::Title => self
.entry_table_items
.sort_by(|a, b| b.title.to_lowercase().cmp(&a.title.to_lowercase())),
- "year" => self
+ EntryTableColumn::Year => self
.entry_table_items
.sort_by(|a, b| b.year.to_lowercase().cmp(&a.year.to_lowercase())),
- _ => {}
+ EntryTableColumn::Pubtype => self
+ .entry_table_items
+ .sort_by(|a, b| b.pubtype.to_lowercase().cmp(&a.pubtype.to_lowercase())),
}
} else if !self.entry_table_reversed_sort {
- match sorting {
- "author" => self
+ match self.entry_table_selected_column {
+ EntryTableColumn::Authors => self
.entry_table_items
.sort_by(|a, b| a.authors.to_lowercase().cmp(&b.authors.to_lowercase())),
- "title" => self
+ EntryTableColumn::Title => self
.entry_table_items
.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())),
- "year" => self
+ EntryTableColumn::Year => self
.entry_table_items
.sort_by(|a, b| a.year.to_lowercase().cmp(&b.year.to_lowercase())),
- _ => {}
+ EntryTableColumn::Pubtype => self
+ .entry_table_items
+ .sort_by(|a, b| a.pubtype.to_lowercase().cmp(&b.pubtype.to_lowercase())),
}
}
}
@@ -189,22 +203,22 @@ impl App {
// Entry Table commands
// Movement
- pub fn select_next_entry(&mut self) {
+ pub fn select_next_entry(&mut self, entries: u16) {
self.entry_table.entry_info_scroll = 0;
self.entry_table.entry_info_scroll_state =
self.entry_table.entry_info_scroll_state.position(0);
- self.entry_table.entry_table_state.select_next();
+ self.entry_table.entry_table_state.scroll_down_by(entries);
self.entry_table.entry_scroll_state = self
.entry_table
.entry_scroll_state
.position(self.entry_table.entry_table_state.selected().unwrap());
}
- pub fn select_previous_entry(&mut self) {
+ pub fn select_previous_entry(&mut self, entries: u16) {
self.entry_table.entry_info_scroll = 0;
self.entry_table.entry_info_scroll_state =
self.entry_table.entry_info_scroll_state.position(0);
- self.entry_table.entry_table_state.select_previous();
+ self.entry_table.entry_table_state.scroll_up_by(entries);
self.entry_table.entry_scroll_state = self
.entry_table
.entry_scroll_state
@@ -230,6 +244,48 @@ impl App {
.position(self.entry_table.entry_table_items.len());
}
+ pub fn select_next_column(&mut self) {
+ match self.entry_table.entry_table_selected_column {
+ EntryTableColumn::Authors => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Title;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Title => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Year;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Year => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Pubtype;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Pubtype => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Authors;
+ self.entry_table.sort_entry_table(false);
+ }
+ }
+ }
+
+ pub fn select_prev_column(&mut self) {
+ match self.entry_table.entry_table_selected_column {
+ EntryTableColumn::Authors => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Pubtype;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Title => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Authors;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Year => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Title;
+ self.entry_table.sort_entry_table(false);
+ }
+ EntryTableColumn::Pubtype => {
+ self.entry_table.entry_table_selected_column = EntryTableColumn::Year;
+ self.entry_table.sort_entry_table(false);
+ }
+ }
+ }
+
// Get the citekey of the selected entry
pub fn get_selected_citekey(&self) -> &str {
let idx = self.entry_table.entry_table_state.selected().unwrap();
@@ -312,7 +368,7 @@ impl App {
BibiSearch::search_entry_list(&mut self.search_struct.search_string, orig_list.clone());
self.entry_table.entry_table_items = filtered_list;
if self.entry_table.entry_table_reversed_sort {
- self.entry_table.sort_entry_table("author", false);
+ self.entry_table.sort_entry_table(false);
}
self.entry_table.entry_scroll_state = ScrollbarState::content_length(
self.entry_table.entry_scroll_state,
diff --git a/src/frontend/handler.rs b/src/frontend/handler.rs
index ec1647e..39ec7a2 100644
--- a/src/frontend/handler.rs
+++ b/src/frontend/handler.rs
@@ -49,23 +49,33 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R
// Keycodes for the tag area
CurrentArea::TagArea => match key_event.code {
KeyCode::Down => {
- app.select_next_tag();
+ app.select_next_tag(1);
}
KeyCode::Up => {
- app.select_previous_tag();
+ app.select_previous_tag(1);
}
KeyCode::Char('j') => {
if key_event.modifiers == KeyModifiers::ALT {
app.scroll_info_down();
} else {
- app.select_next_tag();
+ app.select_next_tag(1);
}
}
KeyCode::Char('k') => {
if key_event.modifiers == KeyModifiers::ALT {
app.scroll_info_up();
} else {
- app.select_previous_tag();
+ app.select_previous_tag(1);
+ }
+ }
+ KeyCode::Char('d') => {
+ if key_event.modifiers == KeyModifiers::CONTROL {
+ app.select_next_tag(5)
+ }
+ }
+ KeyCode::Char('u') => {
+ if key_event.modifiers == KeyModifiers::CONTROL {
+ app.select_previous_tag(5)
}
}
KeyCode::Char('g') | KeyCode::Home => {
@@ -96,23 +106,35 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R
// Keycodes for the entry area
CurrentArea::EntryArea => match key_event.code {
KeyCode::Down => {
- app.select_next_entry();
+ app.select_next_entry(1);
}
KeyCode::Up => {
- app.select_previous_entry();
+ app.select_previous_entry(1);
}
KeyCode::Char('j') => {
if key_event.modifiers == KeyModifiers::ALT {
app.scroll_info_down();
} else {
- app.select_next_entry();
+ app.select_next_entry(1);
}
}
KeyCode::Char('k') => {
if key_event.modifiers == KeyModifiers::ALT {
app.scroll_info_up();
} else {
- app.select_previous_entry();
+ app.select_previous_entry(1);
+ }
+ }
+ KeyCode::Char('d') => {
+ if key_event.modifiers == KeyModifiers::CONTROL {
+ app.select_next_entry(5);
+ }
+ }
+ KeyCode::Char('u') => {
+ if key_event.modifiers == KeyModifiers::CONTROL {
+ app.select_previous_entry(5);
+ } else {
+ app.open_doi_url()?;
}
}
KeyCode::Char('g') | KeyCode::Home => {
@@ -121,8 +143,14 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R
KeyCode::Char('G') | KeyCode::End => {
app.select_last_entry();
}
+ KeyCode::Char('h') => {
+ app.select_prev_column();
+ }
+ KeyCode::Char('l') => {
+ app.select_next_column();
+ }
KeyCode::Char('s') => {
- app.entry_table.sort_entry_table("author", true);
+ app.entry_table.sort_entry_table(true);
}
KeyCode::Char('y') => {
App::yank_text(&app.get_selected_citekey());
@@ -133,9 +161,6 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App, tui: &mut Tui) -> R
KeyCode::Char('o') => {
app.open_connected_file()?;
}
- KeyCode::Char('u') => {
- app.open_doi_url()?;
- }
KeyCode::Char('/') => {
app.enter_search_area();
}
diff --git a/src/frontend/keywords.rs b/src/frontend/keywords.rs
index ba74b02..8f13230 100644
--- a/src/frontend/keywords.rs
+++ b/src/frontend/keywords.rs
@@ -60,16 +60,16 @@ impl App {
// Tag List commands
// Movement
- pub fn select_next_tag(&mut self) {
- self.tag_list.tag_list_state.select_next();
+ pub fn select_next_tag(&mut self, keywords: u16) {
+ self.tag_list.tag_list_state.scroll_down_by(keywords);
self.tag_list.tag_scroll_state = self
.tag_list
.tag_scroll_state
.position(self.tag_list.tag_list_state.selected().unwrap());
}
- pub fn select_previous_tag(&mut self) {
- self.tag_list.tag_list_state.select_previous();
+ pub fn select_previous_tag(&mut self, keywords: u16) {
+ self.tag_list.tag_list_state.scroll_up_by(keywords);
self.tag_list.tag_scroll_state = self
.tag_list
.tag_scroll_state
diff --git a/src/frontend/ui.rs b/src/frontend/ui.rs
index 4ea275d..d4fbba1 100644
--- a/src/frontend/ui.rs
+++ b/src/frontend/ui.rs
@@ -30,7 +30,10 @@ use ratatui::{
use crate::frontend::{app::App, keywords::TagListItem};
-use super::app::{CurrentArea, FormerArea};
+use super::{
+ app::{CurrentArea, FormerArea},
+ entries::EntryTableColumn,
+};
const MAIN_BLUE_COLOR: Color = Color::Indexed(39);
// const MAIN_PURPLE_COLOR: Color = Color::Indexed(129);
@@ -49,6 +52,7 @@ const TEXT_FG_COLOR: Color = Color::Indexed(252);
const TEXT_UNSELECTED_FG_COLOR: Color = Color::Indexed(245);
const SORTED_ENTRIES: &str = "▼";
const SORTED_ENTRIES_REVERSED: &str = "▲";
+const HEADER_FOOTER_BG: Color = Color::Indexed(235);
const SCROLLBAR_UPPER_CORNER: Option<&str> = Some("┓");
const SCROLLBAR_LOWER_CORNER: Option<&str> = Some("┛");
@@ -183,8 +187,6 @@ impl App {
BOX_UNSELECTED_BORDER_STYLE
});
- let background_style = Color::Indexed(235);
-
let [file_area, keyword_area, count_area] = Layout::horizontal([
Constraint::Fill(4),
Constraint::Fill(2),
@@ -197,7 +199,7 @@ impl App {
Span::raw("File: ").bold(),
Span::raw(self.main_bibfile.file_name().unwrap().to_string_lossy()).bold(),
])
- .bg(background_style)
+ .bg(HEADER_FOOTER_BG)
.render(file_area, buf);
Line::from(if !self.tag_list.selected_keyword.is_empty() {
@@ -210,7 +212,7 @@ impl App {
} else {
vec![Span::raw(" ")]
})
- .bg(background_style)
+ .bg(HEADER_FOOTER_BG)
.render(keyword_area, buf);
Line::from(if self.entry_table.entry_table_state.selected().is_some() {
@@ -224,7 +226,7 @@ impl App {
vec![Span::raw("No entries")]
})
.right_aligned()
- .bg(background_style)
+ .bg(HEADER_FOOTER_BG)
.render(count_area, buf);
// Paragraph::new(Line::from(vec![Span::raw(
// self.main_bibfile.display().to_string(),
@@ -259,23 +261,72 @@ impl App {
BOX_UNSELECTED_BORDER_STYLE
});
- let header_style = Style::default().bold().fg(TEXT_FG_COLOR);
+ let header_style = Style::default()
+ .bold()
+ .fg(TEXT_FG_COLOR)
+ .bg(HEADER_FOOTER_BG);
let header = Row::new(vec![
- Cell::from(Line::from(vec![
- Span::raw("Author").underlined(),
- Span::raw(format!(
- " {}",
- if self.entry_table.entry_table_reversed_sort {
- SORTED_ENTRIES_REVERSED
- } else {
- SORTED_ENTRIES
- }
- )),
- ])),
- Cell::from("Title".to_string().underlined()),
- Cell::from("Year".to_string().underlined()),
- Cell::from("Type".to_string().underlined()),
+ if let EntryTableColumn::Authors = self.entry_table.entry_table_selected_column {
+ Cell::from(Line::from(vec![
+ Span::raw("Author").underlined(),
+ Span::raw(format!(
+ " {}",
+ if self.entry_table.entry_table_reversed_sort {
+ SORTED_ENTRIES_REVERSED
+ } else {
+ SORTED_ENTRIES
+ }
+ )),
+ ]))
+ } else {
+ Cell::from("Author".to_string())
+ },
+ if let EntryTableColumn::Title = self.entry_table.entry_table_selected_column {
+ Cell::from(Line::from(vec![
+ Span::raw("Title").underlined(),
+ Span::raw(format!(
+ " {}",
+ if self.entry_table.entry_table_reversed_sort {
+ SORTED_ENTRIES_REVERSED
+ } else {
+ SORTED_ENTRIES
+ }
+ )),
+ ]))
+ } else {
+ Cell::from("Title".to_string())
+ },
+ if let EntryTableColumn::Year = self.entry_table.entry_table_selected_column {
+ Cell::from(Line::from(vec![
+ Span::raw("Year").underlined(),
+ Span::raw(format!(
+ " {}",
+ if self.entry_table.entry_table_reversed_sort {
+ SORTED_ENTRIES_REVERSED
+ } else {
+ SORTED_ENTRIES
+ }
+ )),
+ ]))
+ } else {
+ Cell::from("Year".to_string())
+ },
+ if let EntryTableColumn::Pubtype = self.entry_table.entry_table_selected_column {
+ Cell::from(Line::from(vec![
+ Span::raw("Pubtype").underlined(),
+ Span::raw(format!(
+ " {}",
+ if self.entry_table.entry_table_reversed_sort {
+ SORTED_ENTRIES_REVERSED
+ } else {
+ SORTED_ENTRIES
+ }
+ )),
+ ]))
+ } else {
+ Cell::from("Pubtype".to_string())
+ },
])
.style(header_style)
.height(1);
@@ -299,7 +350,13 @@ impl App {
[
Constraint::Percentage(20),
Constraint::Fill(1),
- Constraint::Length(4),
+ Constraint::Length(
+ if let EntryTableColumn::Year = self.entry_table.entry_table_selected_column {
+ 6
+ } else {
+ 4
+ },
+ ),
Constraint::Percentage(10),
],
)