MarkdownDocument: a Markdown file in one CodeEditor, shown visual, source, split or preview
CI / site (push) Successful in 34s
CI / platforms (windows-latest) (push) Successful in 5m7s
CI / crate (push) Failing after 26m45s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

Visual draws the top-level blocks in a gpui list; a pressed block opens as
its source and its typing is the text at once, written back as one undo
step. CodeEditor takes the owner's line colors, resets with a fresh
history and keeps at most 64 MiB of undo text. MarkdownMode gains Preview.

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 00:44:17 -04:00
1 parent 9036c92442
commit 2b6f5513aa
19 files changed
+1267 -86

No files matched your search

+1 -2
View File
@@ -11,7 +11,6 @@ use super::{
gutter::Cell,
layout::{Shown, stack},
state::{CodeEditor, CursorShape},
syntax,
};
use crate::{
charts::tint,
@@ -29,7 +28,7 @@ impl CodeEditor {
let touches = |span: &Range<usize>| {
span.start < range.end.max(range.start + 1) && range.start < span.end
};
let mut spans = syntax::colors(self.row_text(line), cx);
let mut spans = self.line_colors(self.row_text(line), cx);
let brackets = self
.frame
.brackets
+1
View File
@@ -19,6 +19,7 @@ mod line;
mod moves;
mod preview;
mod problems;
mod prose;
mod refactor;
mod row;
mod search;
+73
View File
@@ -0,0 +1,73 @@
use std::{fmt, ops::Range, rc::Rc};
use gpui::{App, Context, FontStyle, HighlightStyle, StrikethroughStyle};
use super::{buffer::Buffer, cursor::Selection, state::CodeEditor, syntax};
use crate::{
editor::{EditorEvent, anchors},
forms::{Highlight, History},
};
/// Undo keeps at most this many bytes of past text.
pub(crate) const HISTORY_BYTES: usize = 64 << 20;
/// Colors for one line of text.
type Paint = dyn Fn(&str, &App) -> Vec<(Range<usize>, Highlight)>;
/// The owner's colors for each line.
#[derive(Clone)]
pub(crate) struct LineHighlighter(pub Rc<Paint>);
impl fmt::Debug for LineHighlighter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("LineHighlighter")
}
}
impl CodeEditor {
/// Colors each line with the owner's highlighter, not code's.
pub fn highlighter(
mut self,
highlight: impl Fn(&str, &App) -> Vec<(Range<usize>, Highlight)> + 'static,
) -> Self {
self.options.highlighter = Some(LineHighlighter(Rc::new(highlight)));
self
}
/// New text with no history: undo starts here.
pub fn reset(&mut self, text: impl Into<String>, cx: &mut Context<Self>) {
self.buffer = Buffer::new(text);
self.selections = vec![Selection::caret(0)];
self.history = History::default();
self.folded.clear();
(self.marked, self.composing) = (None, None);
anchors::clear(&mut self.marks);
cx.emit(EditorEvent::Changed);
self.restart_blink(cx);
}
/// A line's colors, as the owner or as code.
pub(crate) fn line_colors(&self, text: &str, cx: &App) -> Vec<(Range<usize>, HighlightStyle)> {
let Some(LineHighlighter(highlight)) = &self.options.highlighter else {
return syntax::colors(text, cx);
};
let rule = crate::theme::ActiveTheme::theme(cx).underline_thickness();
highlight(text, cx)
.into_iter()
.map(|(range, highlight)| {
let style = HighlightStyle {
color: Some(highlight.color),
background_color: highlight.background,
font_weight: highlight.weight,
font_style: highlight.italic.then_some(FontStyle::Italic),
strikethrough: highlight.strike.then_some(StrikethroughStyle {
thickness: rule,
color: Some(highlight.color),
}),
..HighlightStyle::default()
};
(range, style)
})
.collect()
}
}
+1 -2
View File
@@ -8,7 +8,6 @@ use super::{
gutter::Cell,
layout::{CHAT_ROWS, Row},
state::{CodeEditor, EditorEvent},
syntax,
};
use crate::theme::{ActiveTheme, TextSize};
@@ -121,7 +120,7 @@ impl CodeEditor {
let line = self.buffer.line_of(ghost.offset);
let rest =
self.buffer.text()[ghost.offset..self.buffer.line_range(line).end].to_string();
let styles = syntax::colors(&rest, cx);
let styles = self.line_colors(&rest, cx);
StyledText::new(rest).with_highlights(styles)
});
base.child(self.gutter(Cell::Blank, ix, cx))
+8
View File
@@ -63,6 +63,7 @@ pub(crate) struct Options {
pub sticky: bool,
pub read_only: bool,
pub tab: usize,
pub highlighter: Option<super::prose::LineHighlighter>,
}
/// What the owner lays over the code.
@@ -186,6 +187,7 @@ impl CodeEditor {
sticky: false,
read_only: false,
tab: 4,
highlighter: None,
},
marks: Marks::default(),
marked: None,
@@ -430,6 +432,12 @@ impl CodeEditor {
if before.text != self.buffer.text() {
if self.composing.is_none() {
self.history.record(before, typing);
let dropped = self
.history
.trim(super::prose::HISTORY_BYTES, |step| step.text.len());
if dropped > 0 {
log::info!("code editor: {dropped} oldest undo steps dropped");
}
}
self.marks.ghost = None;
self.folded.retain(|line| *line < self.buffer.lines());
+3 -3
View File
@@ -53,7 +53,7 @@ impl CodeEditor {
}
/// The first line the list shows, after scrolling.
fn top_line(&self) -> Option<usize> {
pub(crate) fn top_line(&self) -> Option<usize> {
let scrolled = -self.scroll.0.borrow().base_handle.offset().y;
let first = (scrolled / self.frame.line).floor().max(0.0) as usize;
self.frame.rows[first.min(self.frame.rows.len().saturating_sub(1))..]
@@ -73,7 +73,7 @@ impl CodeEditor {
}
let rows = headers.into_iter().map(|line| {
let text = self.buffer.line(line).to_string();
let styles = syntax::colors(&text, cx);
let styles = self.line_colors(&text, cx);
let jump = cx.entity();
div()
.id(("sticky", line))
@@ -131,7 +131,7 @@ impl CodeEditor {
.map(|row| match row {
Row::Line(at) => {
let text = self.buffer.line(*at);
syntax::colors(text, cx)
self.line_colors(text, cx)
.into_iter()
.filter_map(|(range, style)| {
let start = text[..range.start].chars().count();