#![allow(clippy::redundant_static_lifetimes)]
use crate::{
color::{BG_CURSOR_LINE, FG_CURSOR_LINE, NEW},
ui::{render_line, LineState, Scroll, Surround},
};
use ratatui::{
prelude::{Buffer, Rect},
widgets::{Block, BorderType, Borders},
};
use term_rustdoc::tree::{DataItemKind as Kind, IDMap, ID};
struct NaviOutlineInner {
selected: Option<Selected>,
lines: &'static [NaviAction],
}
impl Default for NaviOutlineInner {
fn default() -> Self {
NaviOutlineInner {
selected: None,
lines: &[NaviAction::Item, NaviAction::BackToHome],
}
}
}
impl std::ops::Deref for NaviOutlineInner {
type Target = [NaviAction];
fn deref(&self) -> &Self::Target {
self.lines
}
}
impl LineState for NaviAction {
type State = NaviAction;
fn state(&self) -> Self::State {
*self
}
fn is_identical(&self, state: &Self::State) -> bool {
*self == *state
}
}
pub struct NaviOutline {
display: Scroll<NaviOutlineInner>,
border: Surround,
}
impl Default for NaviOutline {
fn default() -> Self {
Self {
display: Default::default(),
border: Surround::new(block(), Default::default()),
}
}
}
struct Selected {
id: ID,
kind: Kind,
}
fn lines(kind: Kind) -> &'static [NaviAction] {
match kind {
Kind::Struct | Kind::Union => STRUCT,
Kind::Enum => ENUM,
Kind::Trait => TRAIT,
Kind::Module => MODULE,
}
}
const MODULE: &'static [NaviAction] = &[NaviAction::Item, NaviAction::BackToHome];
const STRUCT: &'static [NaviAction] = &[
NaviAction::Item,
NaviAction::StructInner,
NaviAction::ITABImpls,
NaviAction::BackToHome,
];
const ENUM: &'static [NaviAction] = &[
NaviAction::Item,
NaviAction::EnumInner,
NaviAction::ITABImpls,
NaviAction::BackToHome,
];
const TRAIT: &'static [NaviAction] = &[
NaviAction::Item,
NaviAction::TraitAssociated,
NaviAction::TraitImplementors,
NaviAction::BackToHome,
];
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum NaviAction {
StructInner,
EnumInner,
TraitAssociated,
TraitImplementors,
ITABImpls,
Item,
#[default]
BackToHome,
}
impl NaviAction {
fn text(self) -> &'static str {
match self {
NaviAction::StructInner => "Fields",
NaviAction::EnumInner => "Varaints",
NaviAction::TraitAssociated => "Associated",
NaviAction::TraitImplementors => "Implementors",
NaviAction::ITABImpls => "Impls",
NaviAction::Item => "Current Item",
NaviAction::BackToHome => "Back To Home",
}
}
fn len(self) -> u16 {
self.text().len() as u16
}
}
pub fn height() -> u16 {
[STRUCT.len(), ENUM.len(), TRAIT.len()]
.into_iter()
.max()
.unwrap_or(0) as u16
+ 1u16
}
pub fn width() -> u16 {
[STRUCT, ENUM, TRAIT]
.map(|val| val.iter().map(|s| s.len()))
.into_iter()
.flatten()
.max()
.unwrap_or(0u16)
+ 5u16
}
impl NaviOutline {
pub fn set_item_inner(&mut self, id: Option<&str>, map: &IDMap) -> Option<ID> {
let id = id?;
let selected = Selected {
kind: Kind::new(id, map)?,
id: id.into(),
};
let inner = &mut self.display.lines;
inner.lines = lines(selected.kind);
let ret = Some(selected.id.clone());
inner.selected = Some(selected);
ret
}
pub fn reset(&mut self) {
*self.inner() = Default::default();
self.set_cursor_back_to_home();
}
pub fn set_cursor_back_to_home(&mut self) {
self.display.move_bottom_cursor();
}
pub fn update_area(&mut self, area: Rect) {
if let Some(inner) = self.border.update_area(area) {
self.display.area = inner;
}
}
fn inner(&mut self) -> &mut NaviOutlineInner {
&mut self.display.lines
}
fn inner_ref(&self) -> &NaviOutlineInner {
&self.display.lines
}
fn kind(&self) -> Option<Kind> {
self.inner_ref().selected.as_ref().map(|v| v.kind)
}
pub fn render(&self, buf: &mut Buffer) {
self.border.render(buf);
if let Some(lines) = self.display.visible_lines() {
let width = self.display.area.width as usize;
let Rect { x, mut y, .. } = self.display.area;
for &line in lines {
let line = ["👉 ", line.text()].map(|s| (s, NEW));
render_line(line, buf, x, y, width);
y += 1;
}
self.display.highlight_current_line(buf, |cell| {
cell.bg = BG_CURSOR_LINE;
cell.fg = FG_CURSOR_LINE;
});
}
}
pub fn update_outline(&mut self, y: u16) -> Option<NaviAction> {
self.display.force_line_on_screen(y).copied()
}
pub fn next_action(&mut self) -> Option<NaviAction> {
let current = self.display.get_line_of_current_cursor()?;
if matches!(current, NaviAction::BackToHome) {
self.display.move_top_cursor();
} else {
self.display.move_forward_cursor();
}
self.display.get_line_of_current_cursor().copied()
}
pub fn previous_action(&mut self) -> Option<NaviAction> {
if self.display.cursor.y == 0 && self.display.start == 0 {
self.display.move_bottom_cursor();
} else {
self.display.move_backward_cursor();
}
self.display.get_line_of_current_cursor().copied()
}
}
fn block() -> Block<'static> {
Block::new()
.borders(Borders::TOP)
.border_type(BorderType::Thick)
}