MarkdownDocument: one caret for every mode, pressed blocks kept across write-backs, preview read only, keyboard reach, history capped on every path
CI / site (push) Successful in 25s
CI / platforms (windows-latest) (push) Successful in 4m37s
CI / crate (push) Failing after 26m50s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-12 01:14:06 -04:00
1 parent 2b6f5513aa
commit fc181b44c2
13 files changed
+590 -242

No files matched your search

+61 -152
View File
@@ -1,18 +1,14 @@
use gpui::{
AnyElement, App, AppContext as _, Context, Focusable as _, FontWeight, InteractiveElement,
IntoElement, KeyBinding, MouseButton, ParentElement, Render, Styled, Window, actions, div,
list,
AnyElement, App, ClickEvent, Context, FontWeight, InteractiveElement, IntoElement, KeyBinding,
MouseButton, MouseDownEvent, ParentElement, Render, StatefulInteractiveElement, Styled, Window,
actions, div, list,
};
use super::{DocumentEvent, MarkdownDocument, Open};
use super::MarkdownDocument;
use crate::{
documents::{
MarkdownMode,
markdown::markdown_highlights,
modes::{at_edge, joint},
render::draw_nodes,
},
forms::{Down, InputEvent, TextInput, Up},
documents::{MarkdownMode, render::draw_nodes},
forms::{Down, Up},
primitives::FocusRing as _,
theme::{ActiveTheme, Radius, TextSize},
};
@@ -34,104 +30,6 @@ pub(crate) fn bind_keys(cx: &mut App) {
}
impl MarkdownDocument {
/// Opens block `ix`, or a new one past the end.
pub(super) fn open(
&mut self,
ix: Option<usize>,
at_start: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.commit(cx);
if !self.read_now() {
return log::warn!("markdown document: pressed while the text is read again");
}
let text = self.editor.read(cx).text();
let (range, append) = match ix {
Some(ix) => (self.parsed.blocks[ix].range.clone(), false),
None => (text.len()..text.len(), true),
};
let source = text[range.clone()].to_string();
let input = cx.new(|cx| {
TextInput::new(window, cx)
.multi_line(1, usize::MAX)
.highlighter(markdown_highlights)
});
input.update(cx, |input, cx| {
input.set_text(source, cx);
if at_start {
input.select(0..0, cx);
}
});
window.focus(&input.focus_handle(cx), cx);
let subscriptions =
vec![
cx.subscribe(&input, |document, _, event: &InputEvent, cx| match event {
InputEvent::Blur => document.commit(cx),
InputEvent::Changed => cx.emit(DocumentEvent::Changed),
_ => {}
}),
];
log::info!("markdown document: block {range:?} opens");
self.open = Some(Open {
range,
input,
append,
_subscriptions: subscriptions,
});
cx.notify();
}
/// Writes the open block back as one undo step.
pub(super) fn commit(&mut self, cx: &mut Context<Self>) {
let Some(open) = self.open.take() else {
return;
};
cx.notify();
let typed = open.input.read(cx).text().to_string();
let text = self.editor.read(cx).text();
let written = match open.append {
true if typed.is_empty() => return,
true => format!("{}{typed}", joint(text)),
false => typed,
};
self.rest = open.range.start + written.len();
if text[open.range.clone()] == written {
return;
}
log::info!("markdown document: block {:?} written back", open.range);
self.editor
.update(cx, |editor, cx| editor.edit([(open.range, written)], cx));
}
/// Up past the open block's top, or down past it.
fn step(&mut self, down: bool, window: &mut Window, cx: &mut Context<Self>) -> bool {
let Some(open) = &self.open else {
return false;
};
if open.append || !at_edge(open.input.read(cx), down) {
return false;
}
let start = open.range.start;
let here = |blocks: &[super::parse::Block]| {
blocks.partition_point(|block| block.range.start <= start)
};
// The block next door, before writing back.
let next = |blocks: &[super::parse::Block]| match down {
true => (here(blocks) < blocks.len()).then(|| here(blocks)),
false => here(blocks).checked_sub(2),
};
if next(&self.parsed.blocks).is_none() {
return false;
}
self.commit(cx);
let Some(target) = next(&self.parsed.blocks) else {
return false;
};
self.open(Some(target), down, window, cx);
true
}
/// List item `ix`: a block, or the end.
fn item(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Self>) -> AnyElement {
let theme = cx.theme();
@@ -142,38 +40,27 @@ impl MarkdownDocument {
theme.mono_family.clone(),
);
let editing = self.mode == MarkdownMode::Visual;
let Some(block) = self.parsed.blocks.get(ix) else {
return match (&self.open, editing) {
(Some(open), _) if open.append => div()
.px_3()
.py_2()
.rounded(radius)
.bg(colors.sunken)
.child(open.input.clone())
.into_any_element(),
(_, true) => div()
.id("more")
.min_h_8()
.cursor_text()
.on_mouse_down(
MouseButton::Left,
cx.listener(|document, _, window, cx| {
window.prevent_default();
document.open(None, false, window, cx);
}),
)
.into_any_element(),
(_, false) => div().into_any_element(),
};
};
if let Some(open) = self.open.as_ref().filter(|open| open.range == block.range) {
return div()
let open = self.open.as_ref();
let field = |open: &super::Open| {
div()
.px_3()
.py_2()
.rounded(radius)
.bg(colors.sunken)
.child(open.input.clone())
.into_any_element();
.into_any_element()
};
let Some(block) = self.parsed.blocks.get(ix) else {
return match (open, editing) {
(Some(open), _) if open.append => field(open),
(_, true) => self
.pressable(div().id("more").min_h_8(), None, cx)
.into_any_element(),
(_, false) => div().into_any_element(),
};
};
if let Some(open) = open.filter(|open| open.range == block.range) {
return field(open);
}
let shown = match &block.nodes {
Some(nodes) => div()
@@ -202,31 +89,46 @@ impl MarkdownDocument {
.py_1()
.rounded(radius)
.child(shown);
if !editing {
return block.into_any_element();
match editing {
true => self.pressable(block, Some(ix), cx).into_any_element(),
false => block.into_any_element(),
}
block
}
/// `element` opens block `ix` on a press, Enter or Space.
fn pressable(
&self,
element: gpui::Stateful<gpui::Div>,
ix: Option<usize>,
cx: &mut Context<Self>,
) -> gpui::Stateful<gpui::Div> {
let hover = cx.theme().colors.hover.opacity(0.5);
element
.tab_index(0)
.border_1()
.border_color(gpui::transparent_black())
.focus_ring(cx)
.cursor_text()
.hover(|block| block.bg(colors.hover.opacity(0.5)))
.hover(move |element| element.bg(hover))
.on_mouse_down(
MouseButton::Left,
cx.listener(move |document, event: &gpui::MouseDownEvent, window, cx| {
cx.listener(move |document, event: &MouseDownEvent, window, cx| {
// Cmd or Ctrl leaves the press to a link.
if event.modifiers.secondary() {
return;
}
window.prevent_default();
cx.stop_propagation();
document.open(Some(ix), false, window, cx);
document.open(ix, false, window, cx);
}),
)
.on_click(
cx.listener(move |document, event: &ClickEvent, window, cx| {
if event.is_keyboard() {
document.open(ix, false, window, cx);
}
}),
)
.into_any_element()
}
fn blocks(&self, cx: &mut Context<Self>) -> AnyElement {
list(self.list.clone(), cx.processor(Self::item))
.size_full()
.into_any_element()
}
}
@@ -237,14 +139,21 @@ impl Render for MarkdownDocument {
.size_full()
.bg(colors.surface)
.font_weight(FontWeight::NORMAL);
let blocks = list(self.list.clone(), cx.processor(Self::item)).size_full();
match self.mode {
MarkdownMode::Source => root.child(self.editor.clone()).into_any_element(),
MarkdownMode::Split => root
.flex()
.child(div().flex_1().min_w_0().h_full().child(self.editor.clone()))
.child(div().flex_1().min_w_0().h_full().child(self.blocks(cx)))
.child(div().flex_1().min_w_0().h_full().child(blocks))
.into_any_element(),
MarkdownMode::Visual | MarkdownMode::Preview => root
MarkdownMode::Preview => root
.track_focus(&self.focus)
.px_2()
.py_3()
.child(blocks)
.into_any_element(),
MarkdownMode::Visual => root
.key_context(CONTEXT)
.track_focus(&self.focus)
.on_action(cx.listener(|document, _: &Undo, _, cx| {
@@ -276,7 +185,7 @@ impl Render for MarkdownDocument {
)
.px_2()
.py_3()
.child(self.blocks(cx))
.child(blocks)
.into_any_element(),
}
}