aboutsummaryrefslogtreecommitdiff
path: root/src/tui
diff options
context:
space:
mode:
authorlukeflo2025-07-07 15:17:19 +0200
committerlukeflo2025-07-07 15:17:19 +0200
commitd55cfd8617410545335aeaf895120044c46dde45 (patch)
treebb4a41d32a3715610b8429047a12ec9a6540ee08 /src/tui
parent2e4ae7a05a73cb1df63343e14fbb8a5fd3c7bf41 (diff)
parentc0970da999e222cadbcc2242fb67686ed5b00ca4 (diff)
downloadbibiman-d55cfd8617410545335aeaf895120044c46dde45.tar.gz
bibiman-d55cfd8617410545335aeaf895120044c46dde45.zip
Merge pull request 'bibnotes' (#49) from bibnotes into main
- connect bibentries with notes through citekey<>basename matching - use multiple file extensions for note files (plain-text heavily recommended) - create notes for bibentries inside notes dir - use custom symbols as marker for files/links/notes attached to a bibentry
Diffstat (limited to 'src/tui')
-rw-r--r--src/tui/commands.rs4
-rw-r--r--src/tui/popup.rs19
-rw-r--r--src/tui/ui.rs230
3 files changed, 182 insertions, 71 deletions
diff --git a/src/tui/commands.rs b/src/tui/commands.rs
index 08ee677..89fcf44 100644
--- a/src/tui/commands.rs
+++ b/src/tui/commands.rs
@@ -73,6 +73,8 @@ pub enum CmdAction {
ShowHelp,
// Add new entry
AddEntry,
+ // Create note
+ CreateNote,
// Do nothing.
Nothing,
}
@@ -149,6 +151,8 @@ impl From<KeyEvent> for CmdAction {
// Open linked ressource
KeyCode::Char('o') => Self::Open,
// KeyCode::Char('u') => Self::Open(OpenRessource::WebLink),
+ // Create note file
+ KeyCode::Char('n') => Self::CreateNote,
// Edit currently selected entry
KeyCode::Char('e') => Self::EditFile,
// Yank selected item/value
diff --git a/src/tui/popup.rs b/src/tui/popup.rs
index 93b01c3..46e4792 100644
--- a/src/tui/popup.rs
+++ b/src/tui/popup.rs
@@ -38,6 +38,19 @@ pub enum PopupKind {
AddEntry,
/// select an item of the current entry to yank to clipboard
YankItem,
+ /// Create a new note, select extension
+ CreateNote,
+}
+
+#[derive(Debug)]
+pub enum PopupItem {
+ Bibfile,
+ Entryfile,
+ Notefile,
+ Citekey,
+ Link,
+ Default,
+ None,
}
#[derive(Debug, Default)]
@@ -46,7 +59,7 @@ pub struct PopupArea {
pub popup_kind: Option<PopupKind>,
pub popup_message: String,
pub popup_scroll_pos: u16,
- pub popup_list: Vec<(String, String)>,
+ pub popup_list: Vec<(String, String, PopupItem)>,
pub popup_state: ListState,
pub popup_sel_item: String,
// pub add_entry_input: String,
@@ -116,8 +129,8 @@ impl PopupArea {
/// Opens a popup with a selectable list
///
- /// The list items are passed as argument of the kind `Vec<(String, String)>`.
- pub fn popup_selection(&mut self, items: Vec<(String, String)>) {
+ /// The list items are passed as argument of the kind `Vec<(String, String, PopupItem)>`.
+ pub fn popup_selection(&mut self, items: Vec<(String, String, PopupItem)>) {
self.popup_list = items;
// self.popup_kind = Some(PopupKind::SelectRes);
self.is_popup = true;
diff --git a/src/tui/ui.rs b/src/tui/ui.rs
index ebebe4c..69ca058 100644
--- a/src/tui/ui.rs
+++ b/src/tui/ui.rs
@@ -17,7 +17,7 @@
use std::path::PathBuf;
-use super::popup::PopupArea;
+use super::popup::{PopupArea, PopupItem};
use crate::bibiman::entries::EntryTableColumn;
use crate::bibiman::{CurrentArea, FormerArea};
use crate::cliargs::CLIArgs;
@@ -273,21 +273,49 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
frame.render_widget(Clear, popup_area);
frame.render_widget(&content, popup_area)
}
- Some(PopupKind::OpenRes) | Some(PopupKind::AppendToFile) | Some(PopupKind::YankItem) => {
- let list_items: Vec<ListItem> = app
- .bibiman
- .popup_area
- .popup_list
- .iter()
- .map(
- |(mes, obj)| {
- ListItem::from(Line::from(vec![
- Span::styled(mes, Style::new().bold()),
- Span::raw(obj),
- ]))
- }, // ListItem::from(mes.to_owned() + obj)
- )
- .collect();
+ Some(PopupKind::OpenRes)
+ | Some(PopupKind::AppendToFile)
+ | Some(PopupKind::YankItem)
+ | Some(PopupKind::CreateNote) => {
+ let list_items: Vec<ListItem> = if let Some(PopupKind::CreateNote) =
+ app.bibiman.popup_area.popup_kind
+ {
+ app.bibiman
+ .popup_area
+ .popup_list
+ .iter()
+ .map(|(m, o, _i)| {
+ ListItem::from(Line::from(vec![Span::raw(m), Span::raw("."), Span::raw(o)]))
+ .fg(cfg.colors.note_color)
+ })
+ .collect()
+ } else {
+ app.bibiman
+ .popup_area
+ .popup_list
+ .iter()
+ .map(
+ |(mes, obj, i)| {
+ let style: Color = match i {
+ PopupItem::Bibfile => cfg.colors.entry_color,
+ PopupItem::Citekey => cfg.colors.entry_color,
+ PopupItem::Entryfile => cfg.colors.file_color,
+ PopupItem::Notefile => cfg.colors.note_color,
+ PopupItem::Link => cfg.colors.link_color,
+ PopupItem::Default => cfg.colors.main_text_color,
+ PopupItem::None => cfg.colors.main_text_color,
+ };
+ ListItem::from(
+ Line::from(vec![
+ Span::styled(mes, Style::new().bold()),
+ Span::raw(obj),
+ ])
+ .fg(style),
+ )
+ }, // ListItem::from(mes.to_owned() + obj)
+ )
+ .collect()
+ };
let title = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind {
" Open "
@@ -295,21 +323,23 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
" Select file to append entry "
} else if let Some(PopupKind::YankItem) = app.bibiman.popup_area.popup_kind {
" Yank to clipboard "
+ } else if let Some(PopupKind::CreateNote) = app.bibiman.popup_area.popup_kind {
+ " Create Note with extension "
} else {
" Select "
};
let bottom_info = if let Some(PopupKind::OpenRes) = app.bibiman.popup_area.popup_kind {
- " (j,k|↓,↑) ━ (o,l) ━ (ENTER) ━ (ESC) ".bold()
+ " (j,k|↓,↑) ━ (o,l,n) ━ (ENTER) ━ (ESC) "
} else if let Some(PopupKind::YankItem) = app.bibiman.popup_area.popup_kind {
- " (j,k|↓,↑) ━ (y) ━ (ENTER) ━ (ESC) ".bold()
+ " (j,k|↓,↑) ━ (y) ━ (ENTER) ━ (ESC) "
} else {
- " (j,k|↓,↑) ━ (ENTER) ━ (ESC) ".bold()
+ " (j,k|↓,↑) ━ (ENTER) ━ (ESC) "
};
let block = Block::bordered()
.title_top(title.bold())
- .title_bottom(bottom_info)
+ .title_bottom(bottom_info.bold())
.title_alignment(Alignment::Center)
.style(
Style::new()
@@ -317,7 +347,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
.bg(cfg.colors.popup_bg_color),
)
.border_set(symbols::border::THICK)
- .border_style(Style::new().fg(cfg.colors.keyword_color));
+ .border_style(Style::new().fg(cfg.colors.popup_fg_color));
let list = List::new(list_items).block(block).highlight_style(
Style::new()
@@ -333,7 +363,7 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
.popup_area
.popup_list
.iter()
- .max_by(|(mes, obj), (m, o)| {
+ .max_by(|(mes, obj, _ik), (m, o, _i)| {
let x = mes.chars().count() + obj.chars().count();
let y = m.chars().count() + o.chars().count();
x.cmp(&y)
@@ -344,15 +374,29 @@ pub fn render_popup(app: &mut App, cfg: &BibiConfig, frame: &mut Frame) {
// Now take the max number for the width of the popup
// let max_item = list_widths.iter().max().unwrap().to_owned();
- let max_item =
- list_widths.0.chars().count() as u16 + list_widths.1.chars().count() as u16;
-
+ let max_item = list_widths.0.clone() + &list_widths.1;
+ // list_widths.0.chars().count() as u16 + list_widths.1.chars().count() as u16;
+
+ let fitting_width: u16 = {
+ let lines = vec![title, bottom_info, &max_item];
+ let lline = lines
+ .iter()
+ .max_by(|a, b| a.chars().count().cmp(&b.chars().count()))
+ .unwrap();
+ // lines.first().unwrap().chars().count() as u16
+ lline.chars().count() as u16
+ };
// Check if the popup would exceed the terminal frame width
- let popup_width = if max_item + 2 > frame.area().width - 2 {
+ let popup_width = if fitting_width + 2 > frame.area().width - 2 {
frame.area().width - 2
} else {
- max_item + 2
+ fitting_width + 2
};
+ // } else if title.chars().count() as u16 > max_item {
+ // (title.chars().count() + 2) as u16
+ // } else {
+ // max_item + 2
+ // };
let popup_heigth = list.len() + 2;
let popup_area = popup_area(frame.area(), popup_width, popup_heigth as u16);
@@ -580,6 +624,7 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec
.bg(cfg.colors.bar_bg_color);
let header = Row::new(vec![
+ Cell::from(Line::from("Res.")).bg(cfg.colors.bar_bg_color),
Cell::from(
Line::from(vec![{ Span::raw("Author") }, {
if let Some(EntryTableColumn::Authors) =
@@ -698,33 +743,74 @@ pub fn render_entrytable(app: &mut App, cfg: &BibiConfig, frame: &mut Frame, rec
.iter_mut()
.enumerate()
.map(|(_i, data)| {
- let item = data.ref_vec();
- item.into_iter()
- .map(|content| Cell::from(Text::from(content.to_string())))
- .collect::<Row>()
- .style(
- // Style::new().fg(color_list(
- // args,
- // i as i32,
- // app.bibiman
- // .entry_table
- // .entry_table_state
- // .selected()
- // .unwrap_or(0) as i32,
- // args.colors.highlight_text_color,
- // 20,
- // )),
- Style::new().fg(if let CurrentArea::EntryArea = app.bibiman.current_area {
- cfg.colors.highlight_text_color
- } else {
- cfg.colors.main_text_color
- }),
- )
- .height(1)
+ let item = data.ref_vec(cfg);
+
+ let mut symbol_vec = vec![];
+
+ // use default or custom symbols for resources
+ // if an entry has no, replace it with the correct number
+ // of whitespace to align the symbols correct
+ if let Some(f) = &item.symbols[0] {
+ symbol_vec.push(Span::styled(
+ f,
+ Style::new().fg(cfg.colors.file_color).bold(),
+ ));
+ } else {
+ symbol_vec.push(Span::raw(
+ " ".repeat(cfg.general.file_symbol.chars().count()),
+ ));
+ }
+ if let Some(l) = &item.symbols[1] {
+ symbol_vec.push(Span::styled(
+ l,
+ Style::new().fg(cfg.colors.link_color).bold(),
+ ));
+ } else {
+ symbol_vec.push(Span::raw(
+ " ".repeat(cfg.general.link_symbol.chars().count()),
+ ));
+ }
+ if let Some(n) = &item.symbols[2] {
+ symbol_vec.push(Span::styled(
+ n,
+ Style::new().fg(cfg.colors.note_color).bold(),
+ ))
+ } else {
+ symbol_vec.push(Span::raw(
+ " ".repeat(cfg.general.note_symbol.chars().count()),
+ ));
+ }
+
+ let row = Row::new(vec![
+ Cell::from(Line::from(symbol_vec)),
+ Cell::from(Line::from(item.authors)),
+ Cell::from(Line::from(item.title)),
+ Cell::from(Line::from(item.year)),
+ Cell::from(Line::from(item.pubtype)),
+ ]);
+
+ // let row = item
+ // .into_iter()
+ // .map(|content| Cell::from(Text::from(content.to_string())))
+ // .collect::<Row>();
+
+ row.style(
+ Style::new().fg(if let CurrentArea::EntryArea = app.bibiman.current_area {
+ cfg.colors.highlight_text_color
+ } else {
+ cfg.colors.main_text_color
+ }),
+ )
+ .height(1)
});
let entry_table = Table::new(
rows,
[
+ Constraint::Length(
+ (cfg.general.file_symbol.chars().count()
+ + cfg.general.link_symbol.chars().count()
+ + cfg.general.note_symbol.chars().count()) as u16,
+ ),
Constraint::Percentage(20),
Constraint::Fill(1),
Constraint::Length(
@@ -793,7 +879,10 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
lines.push(Line::from(vec![
Span::styled("Authors: ", style_value),
// Span::styled(cur_entry.authors.clone(), Style::new().green()),
- Span::styled(cur_entry.authors(), Style::new().fg(cfg.colors.info_color)),
+ Span::styled(
+ cur_entry.authors(),
+ Style::new().fg(cfg.colors.author_color),
+ ),
]));
if cur_entry.subtitle.is_some() {
lines.push(Line::from(vec![
@@ -801,19 +890,19 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled(
cur_entry.title(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
Span::styled(
": ",
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
Span::styled(
cur_entry.subtitle(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
]));
@@ -823,14 +912,14 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled(
cur_entry.title(),
Style::new()
- .fg(cfg.colors.entry_color)
+ .fg(cfg.colors.title_color)
.add_modifier(Modifier::ITALIC),
),
]));
}
lines.push(Line::from(vec![
Span::styled("Year: ", style_value),
- Span::styled(cur_entry.year(), Style::new().fg(cfg.colors.keyword_color)),
+ Span::styled(cur_entry.year(), Style::new().fg(cfg.colors.year_color)),
]));
// Render keywords in info box in Markdown code style
if !cur_entry.keywords.is_empty() {
@@ -873,7 +962,7 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled("DOI/URL: ", style_value),
Span::styled(
cur_entry.doi_url(),
- Style::new().fg(cfg.colors.main_text_color).underlined(),
+ Style::new().fg(cfg.colors.link_color).underlined(),
),
]));
}
@@ -882,19 +971,19 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
Span::styled("File: ", style_value),
Span::styled(
p.iter().map(|f| f.to_str().unwrap()).join("; "),
- Style::new().fg(cfg.colors.main_text_color),
+ Style::new().fg(cfg.colors.file_color),
+ ),
+ ]));
+ }
+ if let Some(n) = &cur_entry.notes {
+ lines.push(Line::from(vec![
+ Span::styled("Note: ", style_value),
+ Span::styled(
+ n.iter().map(|n| n.to_str().unwrap()).join("; "),
+ Style::new().fg(cfg.colors.note_color),
),
]));
}
- // if cur_entry.filepath.is_some() {
- // lines.push(Line::from(vec![
- // Span::styled("File: ", style_value),
- // Span::styled(
- // cur_entry.filepath().to_string_lossy(),
- // Style::new().fg(cfg.colors.main_text_color),
- // ),
- // ]));
- // }
lines.push(Line::from(""));
lines.push(Line::from(vec![Span::styled(
cur_entry.abstract_text.clone(),
@@ -913,7 +1002,12 @@ pub fn render_selected_item(app: &mut App, cfg: &BibiConfig, frame: &mut Frame,
// We show the list item's info under the list in this paragraph
let block = Block::bordered()
- .title(Line::raw(" Entry Information ").centered().bold())
+ .title(
+ Line::raw(" Entry Information ")
+ .centered()
+ .bold()
+ .fg(cfg.colors.info_color),
+ )
.border_set(symbols::border::PLAIN)
.border_style(Style::new().fg(cfg.colors.main_text_color))
.padding(Padding::horizontal(1));