mirror of
https://github.com/zaphar/sheetsui.git
synced 2025-07-23 05:19:48 -04:00
wip: don't style at all but collect link targets
But do collect the link targets and have hotkeys for them.
This commit is contained in:
parent
3b2c65a9fa
commit
27d30302f5
@ -1,12 +1,11 @@
|
||||
use ratatui::text::Text;
|
||||
use tui_markdown;
|
||||
use crate::ui::render::markdown::Markdown;
|
||||
|
||||
pub fn render_topic(topic: &str) -> Text<'static> {
|
||||
pub fn to_widget(topic: &str) -> Markdown {
|
||||
match topic {
|
||||
"navigate" => tui_markdown::from_str(include_str!("../../../docs/navigation.md")),
|
||||
"edit" => tui_markdown::from_str(include_str!("../../../docs/edit.md")),
|
||||
"command" => tui_markdown::from_str(include_str!("../../../docs/command.md")),
|
||||
"visual" => tui_markdown::from_str(include_str!("../../../docs/visual.md")),
|
||||
_ => tui_markdown::from_str(include_str!("../../../docs/intro.md")),
|
||||
"navigate" => Markdown::from_str(include_str!("../../../docs/navigation.md")),
|
||||
"edit" => Markdown::from_str(include_str!("../../../docs/edit.md")),
|
||||
"command" => Markdown::from_str(include_str!("../../../docs/command.md")),
|
||||
"visual" => Markdown::from_str(include_str!("../../../docs/visual.md")),
|
||||
_ => Markdown::from_str(include_str!("../../../docs/intro.md")),
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use anyhow::{anyhow, Result};
|
||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||
use ironcalc::base::{expressions::types::Area, Model};
|
||||
use ratatui::{
|
||||
buffer::Buffer, layout::{Constraint, Flex, Layout}, style::{Modifier, Style}, text::{Line, Text}, widgets::Block
|
||||
buffer::Buffer, layout::{Constraint, Flex, Layout}, style::{Modifier, Style}, widgets::Block
|
||||
};
|
||||
use tui_prompts::{State, Status, TextPrompt, TextState};
|
||||
use tui_textarea::{CursorMove, TextArea};
|
||||
@ -19,7 +19,7 @@ pub mod render;
|
||||
mod test;
|
||||
|
||||
use cmd::Cmd;
|
||||
use render::viewport::ViewportState;
|
||||
use render::{markdown::Markdown, viewport::ViewportState};
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Clone)]
|
||||
pub enum Modality {
|
||||
@ -80,7 +80,7 @@ pub struct AppState<'ws> {
|
||||
pub range_select: RangeSelection,
|
||||
pub dialog_scroll: u16,
|
||||
dirty: bool,
|
||||
popup: Text<'ws>,
|
||||
popup: Option<Markdown>,
|
||||
clipboard: Option<ClipboardContents>,
|
||||
}
|
||||
|
||||
@ -299,16 +299,16 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn render_help_text(&self) -> Text<'static> {
|
||||
fn render_help_text(&self) -> Markdown {
|
||||
// TODO(zaphar): We should be sourcing these from our actual help documentation.
|
||||
// Ideally we would also render the markdown content properly.
|
||||
// https://github.com/zaphar/sheetsui/issues/22
|
||||
match self.state.modality() {
|
||||
Modality::Navigate => help::render_topic("navigate"),
|
||||
Modality::CellEdit => help::render_topic("edit"),
|
||||
Modality::Command => help::render_topic("command"),
|
||||
Modality::RangeSelect => help::render_topic("visual"),
|
||||
_ => help::render_topic(""),
|
||||
Modality::Navigate => help::to_widget("navigate"),
|
||||
Modality::CellEdit => help::to_widget("edit"),
|
||||
Modality::Command => help::to_widget("command"),
|
||||
Modality::RangeSelect => help::to_widget("visual"),
|
||||
_ => help::to_widget(""),
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,8 +361,10 @@ impl<'ws> Workspace<'ws> {
|
||||
KeyCode::Char('k') | KeyCode::Up => {
|
||||
self.state.dialog_scroll = self.state.dialog_scroll.saturating_sub(1);
|
||||
}
|
||||
_ => {
|
||||
// NOOP
|
||||
code => {
|
||||
if let Some(widget) = &self.state.popup {
|
||||
widget.handle_input(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -414,7 +416,7 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(Some(Cmd::Help(maybe_topic))) => {
|
||||
self.enter_dialog_mode(help::render_topic(maybe_topic.unwrap_or("")));
|
||||
self.enter_dialog_mode(help::to_widget(maybe_topic.unwrap_or("")));
|
||||
Ok(None)
|
||||
}
|
||||
Ok(Some(Cmd::Write(maybe_path))) => {
|
||||
@ -508,11 +510,11 @@ impl<'ws> Workspace<'ws> {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(None) => {
|
||||
self.enter_dialog_mode(vec![Line::from(format!("Unrecognized commmand {}", cmd_text))]);
|
||||
self.enter_dialog_mode(Markdown::from_str(&format!("Unrecognized commmand {}", cmd_text)));
|
||||
Ok(None)
|
||||
}
|
||||
Err(msg) => {
|
||||
self.enter_dialog_mode(vec![Line::from(msg.to_owned())]);
|
||||
self.enter_dialog_mode(Markdown::from_str(msg));
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
@ -951,8 +953,8 @@ impl<'ws> Workspace<'ws> {
|
||||
self.state.command_state.focus();
|
||||
}
|
||||
|
||||
fn enter_dialog_mode<T: Into<Text<'ws>>>(&mut self, msg: T) {
|
||||
self.state.popup = msg.into();
|
||||
fn enter_dialog_mode(&mut self, msg: Markdown) {
|
||||
self.state.popup = Some(msg);
|
||||
self.state.modality_stack.push(Modality::Dialog);
|
||||
}
|
||||
|
||||
|
@ -1,124 +1,55 @@
|
||||
use core::panic;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use ratatui::{
|
||||
text::{Line, Span, Text},
|
||||
widgets::Widget,
|
||||
};
|
||||
use crossterm::event::KeyCode;
|
||||
use ratatui::{text::Text, widgets::Widget};
|
||||
|
||||
use pulldown_cmark::{Event, HeadingLevel, LinkType, Parser, Tag, TagEnd, TextMergeStream};
|
||||
use pulldown_cmark::{Event,LinkType, Parser, Tag, TextMergeStream};
|
||||
|
||||
enum State {
|
||||
Para,
|
||||
NumberList,
|
||||
BulletList,
|
||||
Heading,
|
||||
BlockQuote,
|
||||
}
|
||||
//enum State {
|
||||
// Para,
|
||||
// NumberList,
|
||||
// BulletList,
|
||||
// Heading,
|
||||
// BlockQuote,
|
||||
//}
|
||||
|
||||
struct WidgetWriter<'i> {
|
||||
input: &'i str,
|
||||
state_stack: Vec<State>,
|
||||
heading_stack: Vec<&'static str>,
|
||||
list_stack: Vec<u64>,
|
||||
accumulator: String,
|
||||
lines: Vec<String>,
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Markdown {
|
||||
input: String,
|
||||
links: BTreeSet<String>,
|
||||
}
|
||||
|
||||
impl<'i> WidgetWriter<'i>
|
||||
{
|
||||
pub fn from_str(input: &'i str) -> Self {
|
||||
Self {
|
||||
input,
|
||||
state_stack: Default::default(),
|
||||
heading_stack: Default::default(),
|
||||
list_stack: Default::default(),
|
||||
accumulator: Default::default(),
|
||||
lines: Default::default(),
|
||||
impl Markdown {
|
||||
pub fn from_str(input: &str) -> Self {
|
||||
let mut me = Self {
|
||||
input: input.to_owned(),
|
||||
links: Default::default(),
|
||||
}
|
||||
};
|
||||
me.parse();
|
||||
me
|
||||
}
|
||||
|
||||
pub fn parse(&mut self) {
|
||||
let iter = TextMergeStream::new(Parser::new(self.input));
|
||||
fn parse(&mut self) {
|
||||
let input = self.input.clone();
|
||||
let iter = TextMergeStream::new(Parser::new(&input));
|
||||
for event in iter {
|
||||
match event {
|
||||
Event::Start(tag) => {
|
||||
self.start_tag(&tag);
|
||||
},
|
||||
Event::End(tag) => {
|
||||
self.end_tag(tag);
|
||||
},
|
||||
Event::Text(txt)
|
||||
| Event::Code(txt)
|
||||
| Event::InlineHtml(txt)
|
||||
| Event::Html(txt) => {
|
||||
let prefix = if let Some(State::BlockQuote) = self.state_stack.first() {
|
||||
"| "
|
||||
} else {
|
||||
""
|
||||
};
|
||||
for ln in txt.lines() {
|
||||
self.accumulator.push_str(prefix);
|
||||
self.accumulator.push_str(ln);
|
||||
}
|
||||
},
|
||||
Event::Rule => { /* noop */ },
|
||||
Event::SoftBreak => { /* noop */ },
|
||||
Event::HardBreak => { /* noop */ },
|
||||
// We don't support these
|
||||
Event::InlineMath(_) => todo!(),
|
||||
Event::DisplayMath(_) => todo!(),
|
||||
Event::FootnoteReference(_) => todo!(),
|
||||
Event::TaskListMarker(_) => todo!(),
|
||||
}
|
||||
_ => { /* noop */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn start_tag(&mut self, tag: &Tag<'i>) {
|
||||
fn start_tag(&mut self, tag: &Tag<'_>) {
|
||||
match tag {
|
||||
Tag::Paragraph => {
|
||||
self.state_stack.push(State::Para);
|
||||
},
|
||||
Tag::Heading { level, id: _id, classes: _classes, attrs: _attrs } => {
|
||||
self.heading_stack.push(match level {
|
||||
HeadingLevel::H1 => "1",
|
||||
HeadingLevel::H2 => "2",
|
||||
HeadingLevel::H3 => "3",
|
||||
HeadingLevel::H4 => "4",
|
||||
HeadingLevel::H5 => "5",
|
||||
HeadingLevel::H6 => "6",
|
||||
});
|
||||
self.state_stack.push(State::Heading);
|
||||
let prefix = self.heading_stack.join(".");
|
||||
self.accumulator.push_str(&prefix);
|
||||
self.accumulator.push_str(" ");
|
||||
},
|
||||
Tag::List(Some(first)) => {
|
||||
self.list_stack.push(*first);
|
||||
self.state_stack.push(State::NumberList);
|
||||
},
|
||||
Tag::List(None) => {
|
||||
self.state_stack.push(State::BulletList);
|
||||
},
|
||||
Tag::Item => {
|
||||
if let Some(State::BulletList) = self.state_stack.first() {
|
||||
self.accumulator.push_str("- ");
|
||||
} else if let Some(State::NumberList) = self.state_stack.first() {
|
||||
let num = self.list_stack.pop().unwrap_or(1);
|
||||
self.accumulator.push_str(&format!("{}. ", num));
|
||||
self.list_stack.push(num + 1);
|
||||
}
|
||||
panic!("No list type in our state stack");
|
||||
},
|
||||
Tag::Emphasis => {
|
||||
self.accumulator.push_str("*");
|
||||
},
|
||||
Tag::Strong => {
|
||||
self.accumulator.push_str("**");
|
||||
},
|
||||
Tag::Link { link_type, dest_url, title, id } => {
|
||||
Tag::Link {
|
||||
link_type,
|
||||
dest_url,
|
||||
title,
|
||||
id,
|
||||
} => {
|
||||
let dest = match link_type {
|
||||
// [foo](bar)
|
||||
LinkType::Inline => format!("({})", dest_url),
|
||||
@ -135,77 +66,41 @@ impl<'i> WidgetWriter<'i>
|
||||
LinkType::Email => todo!(),
|
||||
LinkType::WikiLink { has_pothole: _ } => todo!(),
|
||||
};
|
||||
self.accumulator.push_str(&format!("[{}]{}", title, dest));
|
||||
self.links.insert(dest);
|
||||
},
|
||||
Tag::BlockQuote(_) => {
|
||||
self.state_stack.push(State::BlockQuote);
|
||||
},
|
||||
// these are all noops
|
||||
Tag::CodeBlock(_) => {},
|
||||
Tag::HtmlBlock => {},
|
||||
Tag::FootnoteDefinition(_) => {},
|
||||
Tag::DefinitionList => {},
|
||||
Tag::DefinitionListTitle => {},
|
||||
Tag::DefinitionListDefinition => {},
|
||||
Tag::Table(_) => {},
|
||||
Tag::TableHead => {},
|
||||
Tag::TableRow => {},
|
||||
Tag::TableCell => {},
|
||||
Tag::Strikethrough => {},
|
||||
Tag::Superscript => {},
|
||||
Tag::Subscript => {}
|
||||
Tag::Image { link_type: _link_type, dest_url: _dest_url, title: _title, id: _id } => {},
|
||||
Tag::MetadataBlock(_) => {},
|
||||
}
|
||||
_ => { /* noop */ }
|
||||
}
|
||||
}
|
||||
|
||||
fn end_tag(&mut self, tag: TagEnd) {
|
||||
match tag {
|
||||
TagEnd::Paragraph => {
|
||||
self.state_stack.pop();
|
||||
self.lines.push("\n".to_owned());
|
||||
},
|
||||
TagEnd::Heading(_level) => {
|
||||
self.heading_stack.pop();
|
||||
self.state_stack.pop();
|
||||
self.lines.extend(self.accumulator.lines().map(|s| s.to_owned()));
|
||||
self.accumulator.clear();
|
||||
},
|
||||
TagEnd::List(_ordered) => {
|
||||
self.state_stack.pop();
|
||||
},
|
||||
TagEnd::BlockQuote(_kind) => {
|
||||
self.state_stack.pop();
|
||||
},
|
||||
TagEnd::CodeBlock => {
|
||||
todo!()
|
||||
},
|
||||
TagEnd::HtmlBlock => {
|
||||
todo!()
|
||||
},
|
||||
TagEnd::Item => { /* noop */ },
|
||||
TagEnd::Link => { /* noop */ },
|
||||
// We don't support these
|
||||
TagEnd::FootnoteDefinition => todo!(),
|
||||
TagEnd::DefinitionList => todo!(),
|
||||
TagEnd::DefinitionListTitle => todo!(),
|
||||
TagEnd::DefinitionListDefinition => todo!(),
|
||||
TagEnd::Table => todo!(),
|
||||
TagEnd::TableHead => todo!(),
|
||||
TagEnd::TableRow => todo!(),
|
||||
TagEnd::TableCell => todo!(),
|
||||
TagEnd::Emphasis => {
|
||||
self.accumulator.push_str("*");
|
||||
},
|
||||
TagEnd::Strong => {
|
||||
self.accumulator.push_str("**");
|
||||
},
|
||||
TagEnd::Strikethrough => todo!(),
|
||||
TagEnd::Superscript => todo!(),
|
||||
TagEnd::Subscript => todo!(),
|
||||
TagEnd::Image => todo!(),
|
||||
TagEnd::MetadataBlock(_) => todo!(),
|
||||
}
|
||||
pub fn handle_input(&self, code: KeyCode) -> Option<String> {
|
||||
let num = match code {
|
||||
KeyCode::Char('0') => 0,
|
||||
KeyCode::Char('1') => 1,
|
||||
KeyCode::Char('2') => 2,
|
||||
KeyCode::Char('3') => 3,
|
||||
KeyCode::Char('4') => 4,
|
||||
KeyCode::Char('5') => 5,
|
||||
KeyCode::Char('6') => 6,
|
||||
KeyCode::Char('7') => 7,
|
||||
KeyCode::Char('8') => 8,
|
||||
KeyCode::Char('9') => 9,
|
||||
_ => return None,
|
||||
};
|
||||
self.links.iter().nth(num).cloned()
|
||||
}
|
||||
|
||||
pub fn get_text<'w>(&'w self) -> Text<'_> {
|
||||
Text::raw(&self.input)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jwall): We need this to be lines instead of just a render.
|
||||
impl Widget for Markdown {
|
||||
fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let text = Text::raw(self.input);
|
||||
text.render(area, buf);
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ impl<'widget, 'ws: 'widget> Widget for &'widget mut Workspace<'ws> {
|
||||
Self: Sized,
|
||||
{
|
||||
if self.state.modality() == &Modality::Dialog {
|
||||
let lines = Text::from_iter(self.state.popup.iter().cloned());
|
||||
let lines = self.state.popup.as_ref().map(|md| md.get_text()).unwrap_or_else(|| Text::raw("Popup message here"));
|
||||
let popup = dialog::Dialog::new(lines, "Help").scroll(self.state.dialog_scroll);
|
||||
popup.render(area, buf);
|
||||
} else if self.state.modality() == &Modality::Quit {
|
||||
|
@ -395,7 +395,7 @@ macro_rules! assert_help_dialog {
|
||||
.run(&mut ws)
|
||||
.expect("Failed to handle 'alt-h' key event");
|
||||
assert_eq!(Some(&Modality::Dialog), ws.state.modality_stack.last());
|
||||
assert_eq!(edit_help, ws.state.popup);
|
||||
assert_eq!(Some(edit_help), ws.state.popup);
|
||||
$exit.run(&mut ws).expect("Failed to handle key event");
|
||||
assert_eq!(Some(&Modality::CellEdit), ws.state.modality_stack.last());
|
||||
}};
|
||||
@ -431,7 +431,7 @@ fn test_navigation_mode_help_keycode() {
|
||||
.run(&mut ws)
|
||||
.expect("Failed to handle 'alt-h' key event");
|
||||
assert_eq!(Some(&Modality::Dialog), ws.state.modality_stack.last());
|
||||
assert_eq!(help_text, ws.state.popup);
|
||||
assert_eq!(Some(help_text), ws.state.popup);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -449,7 +449,7 @@ fn test_command_mode_help_keycode() {
|
||||
.run(&mut ws)
|
||||
.expect("Failed to handle 'alt-h' key event");
|
||||
assert_eq!(Some(&Modality::Dialog), ws.state.modality_stack.last());
|
||||
assert_eq!(edit_help, ws.state.popup);
|
||||
assert_eq!(Some(edit_help), ws.state.popup);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
Loading…
x
Reference in New Issue
Block a user