MarkdownDocument: a Markdown file in one CodeEditor, shown visual, source, split or preview
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:
1 parent
9036c92442
commit
2b6f5513aa
19 files changed
+1267
-86
No files matched your search
+1
-2
@@ -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
|
||||
|
||||
@@ -19,6 +19,7 @@ mod line;
|
||||
mod moves;
|
||||
mod preview;
|
||||
mod problems;
|
||||
mod prose;
|
||||
mod refactor;
|
||||
mod row;
|
||||
mod search;
|
||||
|
||||
@@ -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
@@ -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))
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user