use gpui::{ScrollHandle, SharedString};
use motion::Painter;
use ui::{popover::Filter, scroll::TransientState};
use markdown::{Align, BlockKind, Cursor, Text};
pub fn items() -> Vec<(SharedString, BlockKind)> {
let text = Text::default;
vec![
("Text".into(), BlockKind::Paragraph(text())),
(
"Heading 1".into(),
BlockKind::Heading {
level: 1,
text: text(),
},
),
(
"Heading 2".into(),
BlockKind::Heading {
level: 2,
text: text(),
},
),
(
"Heading 3".into(),
BlockKind::Heading {
level: 3,
text: text(),
},
),
("Bullet".into(), BlockKind::Bullet(text())),
(
"Numbered".into(),
BlockKind::Ordered {
number: 1,
text: text(),
},
),
(
"Task".into(),
BlockKind::Task {
checked: false,
text: text(),
},
),
(
"Quote".into(),
BlockKind::Quote {
kind: None,
text: text(),
},
),
(
"Code".into(),
BlockKind::Code {
language: None,
code: text(),
},
),
(
"Table".into(),
BlockKind::Table {
align: vec![Align::Left; 2],
header: vec![text(), text()],
rows: vec![vec![text(), text()]],
},
),
(
"Image".into(),
BlockKind::Image {
url: String::new(),
alt: text(),
width: None,
},
),
("Divider".into(), BlockKind::Rule),
]
}
pub fn label(kind: &BlockKind) -> Option<SharedString> {
items()
.into_iter()
.find(|(_, row)| same(row, kind))
.map(|(label, _)| label)
}
fn same(row: &BlockKind, kind: &BlockKind) -> bool {
match (row, kind) {
(BlockKind::Heading { level: a, .. }, BlockKind::Heading { level: b, .. }) => a == b,
(row, kind) => std::mem::discriminant(row) == std::mem::discriminant(kind),
}
}
pub struct Slash {
pub at: Cursor,
pub filter: Filter,
pub scroll: ScrollHandle,
pub bar: TransientState,
}
impl Slash {
pub fn open(at: Cursor, painter: Painter) -> Self {
Self {
at,
filter: Filter::new(items().into_iter().map(|(label, _)| label).collect()),
scroll: ScrollHandle::new(),
bar: TransientState::new(painter),
}
}
pub fn refilter(&mut self, query: &str) {
self.filter.refilter(query);
self.scroll.scroll_to_item(0);
}
pub fn step(&mut self, delta: isize) {
self.filter.step(delta);
if let Some(row) = self.filter.active() {
self.scroll.scroll_to_item(row);
}
}
pub fn choice(&self) -> Option<BlockKind> {
let ix = self.filter.active_item()?;
items().into_iter().nth(ix).map(|(_, kind)| kind)
}
pub fn query(&self, caret: Cursor, text: &str) -> Option<String> {
if caret.block != self.at.block || caret.part != self.at.part {
return None;
}
let start = self.at.offset + 1;
if caret.offset < start {
return None;
}
let query = text.get(start..caret.offset)?;
(!query.contains(char::is_whitespace)).then(|| query.to_string())
}
}