documents and editor: a writing view - measure, font, scale, focus fading, typewriter centering
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e89fb32f43
commit
508fee9d15
13 files changed
+299
-12
No files matched your search
@@ -97,7 +97,10 @@ impl MarkdownDocument {
|
|||||||
InputEvent::Blur => {
|
InputEvent::Blur => {
|
||||||
document.commit(cx);
|
document.commit(cx);
|
||||||
}
|
}
|
||||||
InputEvent::Changed => cx.emit(DocumentEvent::Changed),
|
InputEvent::Changed => {
|
||||||
|
document.typed += 1;
|
||||||
|
cx.emit(DocumentEvent::Changed)
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
//! A writing view: how text shows, not what it says.
|
||||||
|
|
||||||
|
use gpui::{Context, Rems, SharedString};
|
||||||
|
|
||||||
|
use super::MarkdownDocument;
|
||||||
|
use crate::documents::MarkdownMode;
|
||||||
|
|
||||||
|
/// How the document shows its text (MARK-09).
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct DocumentLook {
|
||||||
|
/// All but the current block or line fade.
|
||||||
|
pub focus: bool,
|
||||||
|
/// The current block or line stays mid-view.
|
||||||
|
pub typewriter: bool,
|
||||||
|
/// The widest the text column grows.
|
||||||
|
pub measure: Option<Rems>,
|
||||||
|
/// The prose font; none keeps the theme's.
|
||||||
|
pub font: Option<SharedString>,
|
||||||
|
/// Text size against the theme's.
|
||||||
|
pub scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DocumentLook {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
focus: false,
|
||||||
|
typewriter: false,
|
||||||
|
measure: None,
|
||||||
|
font: None,
|
||||||
|
scale: 1.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MarkdownDocument {
|
||||||
|
pub fn look(&self) -> &DocumentLook {
|
||||||
|
&self.look
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_look(&mut self, look: DocumentLook, cx: &mut Context<Self>) {
|
||||||
|
log::info!("markdown document: look {look:?}");
|
||||||
|
self.editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_scale(look.scale, cx);
|
||||||
|
editor.set_focus_line(look.focus, cx);
|
||||||
|
editor.set_typewriter(look.typewriter, cx);
|
||||||
|
});
|
||||||
|
self.look = look;
|
||||||
|
self.centered = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reader's block: the open one, or the caret's.
|
||||||
|
pub(super) fn current_block(&self, cx: &gpui::App) -> usize {
|
||||||
|
match &self.open {
|
||||||
|
Some(open) if open.append => self.parsed.blocks.len(),
|
||||||
|
Some(open) => self.block_at(open.range.start),
|
||||||
|
None => self.block_at(self.editor.read(cx).primary().head),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current block mid-view once it or its text moves.
|
||||||
|
pub(super) fn center_current(&mut self, cx: &gpui::App) {
|
||||||
|
if !self.look.typewriter || self.mode != MarkdownMode::Visual {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ix = self.current_block(cx);
|
||||||
|
let Some(block) = self.list.bounds_for_item(ix) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// A new layout moves it too: its height counts.
|
||||||
|
let height = f32::from(block.size.height).round() as u64;
|
||||||
|
let moved = (ix, self.edits, self.typed, height);
|
||||||
|
if self.centered == Some(moved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let view = self.list.viewport_bounds();
|
||||||
|
self.list.scroll_by(block.center().y - view.center().y);
|
||||||
|
self.centered = Some(moved);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ mod attach;
|
|||||||
mod edit;
|
mod edit;
|
||||||
mod finding;
|
mod finding;
|
||||||
mod lists;
|
mod lists;
|
||||||
|
mod look;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod place;
|
mod place;
|
||||||
mod tables;
|
mod tables;
|
||||||
@@ -19,6 +20,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub use attach::Attachment;
|
pub use attach::Attachment;
|
||||||
|
pub use look::DocumentLook;
|
||||||
pub use parse::DocumentHeading;
|
pub use parse::DocumentHeading;
|
||||||
use parse::{Parsed, alike, parsed};
|
use parse::{Parsed, alike, parsed};
|
||||||
pub use tables::{TableEdit, TableRefusal};
|
pub use tables::{TableEdit, TableRefusal};
|
||||||
@@ -83,6 +85,11 @@ pub struct MarkdownDocument {
|
|||||||
writes: usize,
|
writes: usize,
|
||||||
/// A match asked before its text was read; its version.
|
/// A match asked before its text was read; its version.
|
||||||
showing: Option<(Range<usize>, u64)>,
|
showing: Option<(Range<usize>, u64)>,
|
||||||
|
look: DocumentLook,
|
||||||
|
/// Typing in the open block, counted.
|
||||||
|
typed: u64,
|
||||||
|
/// The block, text, typing and height last centered.
|
||||||
|
centered: Option<(usize, u64, u64, u64)>,
|
||||||
focus: FocusHandle,
|
focus: FocusHandle,
|
||||||
on_link: Option<OnLink>,
|
on_link: Option<OnLink>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
@@ -136,6 +143,9 @@ impl MarkdownDocument {
|
|||||||
asks: 0,
|
asks: 0,
|
||||||
writes: 0,
|
writes: 0,
|
||||||
showing: None,
|
showing: None,
|
||||||
|
look: DocumentLook::default(),
|
||||||
|
typed: 0,
|
||||||
|
centered: None,
|
||||||
focus,
|
focus,
|
||||||
on_link: None,
|
on_link: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
use gpui::{TestAppContext, px, rems};
|
||||||
|
|
||||||
|
use super::{MarkdownMode, frames, open};
|
||||||
|
use crate::documents::DocumentLook;
|
||||||
|
|
||||||
|
/// Sixty short paragraphs, each its own block.
|
||||||
|
fn long() -> String {
|
||||||
|
(0..60).map(|n| format!("Paragraph {n}.\n\n")).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn the_measure_narrows_the_column(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open("A paragraph.\n", MarkdownMode::Visual, cx);
|
||||||
|
let look = DocumentLook {
|
||||||
|
measure: Some(rems(20.)),
|
||||||
|
..DocumentLook::default()
|
||||||
|
};
|
||||||
|
document.update(cx, |document, cx| document.set_look(look, cx));
|
||||||
|
frames(cx);
|
||||||
|
let block = cx.debug_bounds("markdown-block-0").expect("drawn");
|
||||||
|
assert!(block.size.width <= px(320.0), "{:?}", block.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn typewriter_keeps_the_open_block_mid_view(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(&long(), MarkdownMode::Visual, cx);
|
||||||
|
let look = DocumentLook {
|
||||||
|
typewriter: true,
|
||||||
|
..DocumentLook::default()
|
||||||
|
};
|
||||||
|
document.update_in(cx, |document, window, cx| {
|
||||||
|
document.set_look(look, cx);
|
||||||
|
document.open(Some(40), false, window, cx);
|
||||||
|
document.list.scroll_to_reveal_item(40);
|
||||||
|
});
|
||||||
|
frames(cx);
|
||||||
|
let (block, view) = document.read_with(cx, |document, _| {
|
||||||
|
(
|
||||||
|
document.list.bounds_for_item(40),
|
||||||
|
document.list.viewport_bounds(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let block = block.expect("block 40 is laid out");
|
||||||
|
let off = (block.center().y - view.center().y).abs();
|
||||||
|
assert!(off < px(4.0), "{off:?} from the middle");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn typewriter_keeps_the_source_caret_mid_view(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(&long(), MarkdownMode::Source, cx);
|
||||||
|
let look = DocumentLook {
|
||||||
|
typewriter: true,
|
||||||
|
..DocumentLook::default()
|
||||||
|
};
|
||||||
|
document.update(cx, |document, cx| {
|
||||||
|
document.set_look(look, cx);
|
||||||
|
document.set_caret(90, 0, cx);
|
||||||
|
});
|
||||||
|
frames(cx);
|
||||||
|
let before = document.read_with(cx, |document, cx| document.top_line(cx));
|
||||||
|
let editor = document.read_with(cx, |document, _| document.editor().clone());
|
||||||
|
let focus = editor.read_with(cx, gpui::Focusable::focus_handle);
|
||||||
|
cx.update(|window, cx| window.focus(&focus, cx));
|
||||||
|
cx.simulate_input(
|
||||||
|
"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
",
|
||||||
|
);
|
||||||
|
frames(cx);
|
||||||
|
let after = document.read_with(cx, |document, cx| document.top_line(cx));
|
||||||
|
assert_eq!(after, before + 10, "the view follows the caret");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn the_scale_sizes_source_lines(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open("a\n", MarkdownMode::Source, cx);
|
||||||
|
let editor = document.read_with(cx, |document, _| document.editor().clone());
|
||||||
|
frames(cx);
|
||||||
|
let plain = editor.read_with(cx, |editor, _| editor.metrics.line);
|
||||||
|
let look = DocumentLook {
|
||||||
|
scale: 2.0,
|
||||||
|
..DocumentLook::default()
|
||||||
|
};
|
||||||
|
document.update(cx, |document, cx| document.set_look(look, cx));
|
||||||
|
frames(cx);
|
||||||
|
let large = editor.read_with(cx, |editor, _| editor.metrics.line);
|
||||||
|
assert!(large > plain * 1.8, "{plain:?} to {large:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn focus_counts_the_open_block_as_the_readers(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(&long(), MarkdownMode::Visual, cx);
|
||||||
|
document.update_in(cx, |document, window, cx| {
|
||||||
|
document.open(Some(3), false, window, cx)
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
document.read_with(cx, |document, cx| document.current_block(cx)),
|
||||||
|
3
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -368,6 +368,7 @@ fn a_press_after_a_write_back_waits_for_the_long_read(cx: &mut TestAppContext) {
|
|||||||
mod attach;
|
mod attach;
|
||||||
mod history;
|
mod history;
|
||||||
mod links;
|
mod links;
|
||||||
|
mod look;
|
||||||
mod place;
|
mod place;
|
||||||
mod tools;
|
mod tools;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, ClickEvent, Context, ExternalPaths, FontWeight, InteractiveElement,
|
AnyElement, App, ClickEvent, Context, ExternalPaths, FontWeight, InteractiveElement,
|
||||||
IntoElement, KeyBinding, MouseButton, MouseDownEvent, ParentElement, Render,
|
IntoElement, KeyBinding, MouseButton, MouseDownEvent, ParentElement, Render,
|
||||||
StatefulInteractiveElement, Styled, Window, actions, div, list,
|
StatefulInteractiveElement, Styled, Window, actions, div, list, prelude::FluentBuilder as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::MarkdownDocument;
|
use super::MarkdownDocument;
|
||||||
@@ -16,6 +16,14 @@ actions!(markdown_document, [Undo, Redo, Indent, Outdent, Paste]);
|
|||||||
|
|
||||||
const CONTEXT: &str = "MarkdownDocument";
|
const CONTEXT: &str = "MarkdownDocument";
|
||||||
|
|
||||||
|
/// How far a faded block recedes.
|
||||||
|
const FADED: f32 = 0.35;
|
||||||
|
|
||||||
|
/// Modes with a reader's block to keep.
|
||||||
|
fn editing_mode(mode: MarkdownMode) -> bool {
|
||||||
|
mode == MarkdownMode::Visual
|
||||||
|
}
|
||||||
|
|
||||||
/// Undo and redo; Tab shifts an open block's lines.
|
/// Undo and redo; Tab shifts an open block's lines.
|
||||||
pub(crate) fn bind_keys(cx: &mut App) {
|
pub(crate) fn bind_keys(cx: &mut App) {
|
||||||
let context = Some(CONTEXT);
|
let context = Some(CONTEXT);
|
||||||
@@ -40,9 +48,11 @@ impl MarkdownDocument {
|
|||||||
let colors = theme.colors.clone();
|
let colors = theme.colors.clone();
|
||||||
let (radius, body, mono) = (
|
let (radius, body, mono) = (
|
||||||
theme.radius(Radius::Md),
|
theme.radius(Radius::Md),
|
||||||
theme.text_size(TextSize::Base),
|
theme.text_size(TextSize::Base) * self.look.scale,
|
||||||
theme.mono_family.clone(),
|
theme.mono_family.clone(),
|
||||||
);
|
);
|
||||||
|
// Focus fades all but the reader's block.
|
||||||
|
let faded = self.look.focus && editing_mode(self.mode) && ix != self.current_block(cx);
|
||||||
let editing = self.mode == MarkdownMode::Visual;
|
let editing = self.mode == MarkdownMode::Visual;
|
||||||
let open = self.open.as_ref();
|
let open = self.open.as_ref();
|
||||||
let field = |open: &super::Open| {
|
let field = |open: &super::Open| {
|
||||||
@@ -79,9 +89,11 @@ impl MarkdownDocument {
|
|||||||
nodes,
|
nodes,
|
||||||
&self.base,
|
&self.base,
|
||||||
&self.on_link,
|
&self.on_link,
|
||||||
|
self.look.scale,
|
||||||
cx,
|
cx,
|
||||||
)),
|
)),
|
||||||
None => div()
|
None => div()
|
||||||
|
.text_size(body)
|
||||||
.font_family(mono)
|
.font_family(mono)
|
||||||
.text_color(colors.fg_muted)
|
.text_color(colors.fg_muted)
|
||||||
.child(self.parsed.text[block.range.clone()].to_string()),
|
.child(self.parsed.text[block.range.clone()].to_string()),
|
||||||
@@ -92,6 +104,7 @@ impl MarkdownDocument {
|
|||||||
.px_3()
|
.px_3()
|
||||||
.py_1()
|
.py_1()
|
||||||
.rounded(radius)
|
.rounded(radius)
|
||||||
|
.when(faded, |block| block.opacity(FADED))
|
||||||
.child(shown);
|
.child(shown);
|
||||||
match editing {
|
match editing {
|
||||||
true => self.pressable(block, Some(ix), cx).into_any_element(),
|
true => self.pressable(block, Some(ix), cx).into_any_element(),
|
||||||
@@ -150,7 +163,21 @@ impl Render for MarkdownDocument {
|
|||||||
log::info!("markdown document: {} files dropped", dropped.paths().len());
|
log::info!("markdown document: {} files dropped", dropped.paths().len());
|
||||||
cx.emit(super::Attachment::Files(dropped.paths().to_vec()));
|
cx.emit(super::Attachment::Files(dropped.paths().to_vec()));
|
||||||
}));
|
}));
|
||||||
|
self.center_current(cx);
|
||||||
let blocks = list(self.list.clone(), cx.processor(Self::item)).size_full();
|
let blocks = list(self.list.clone(), cx.processor(Self::item)).size_full();
|
||||||
|
// The text column: its measure, font, centered.
|
||||||
|
let column = |content: gpui::AnyElement, look: &super::DocumentLook| {
|
||||||
|
div().size_full().flex().justify_center().child(
|
||||||
|
div()
|
||||||
|
.h_full()
|
||||||
|
.w_full()
|
||||||
|
.when_some(look.measure, |column, measure| column.max_w(measure))
|
||||||
|
.when_some(look.font.clone(), |column, font| column.font_family(font))
|
||||||
|
.child(content),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let blocks = column(blocks.into_any_element(), &self.look);
|
||||||
|
let source = column(self.editor.clone().into_any_element(), &self.look);
|
||||||
match self.mode {
|
match self.mode {
|
||||||
MarkdownMode::Source => root
|
MarkdownMode::Source => root
|
||||||
.capture_action(cx.listener(|document, _: &crate::editor::Paste, _, cx| {
|
.capture_action(cx.listener(|document, _: &crate::editor::Paste, _, cx| {
|
||||||
@@ -163,7 +190,7 @@ impl Render for MarkdownDocument {
|
|||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.child(self.editor.clone())
|
.child(source)
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
MarkdownMode::Split => root
|
MarkdownMode::Split => root
|
||||||
.flex()
|
.flex()
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ pub use contents::outline;
|
|||||||
pub use counts::{ReadingTime, WordCount};
|
pub use counts::{ReadingTime, WordCount};
|
||||||
pub use diagram::Diagram;
|
pub use diagram::Diagram;
|
||||||
pub use document::{
|
pub use document::{
|
||||||
Attachment, DocumentEvent, DocumentHeading, LARGEST_READ, MarkdownDocument, TableEdit,
|
Attachment, DocumentEvent, DocumentHeading, DocumentLook, LARGEST_READ, MarkdownDocument,
|
||||||
TableRefusal,
|
TableEdit, TableRefusal,
|
||||||
};
|
};
|
||||||
pub use epub::{Chapter, EpubReader};
|
pub use epub::{Chapter, EpubReader};
|
||||||
pub use format::{Format, format};
|
pub use format::{Format, format};
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ struct Draw<'a> {
|
|||||||
headings: std::cell::Cell<usize>,
|
headings: std::cell::Cell<usize>,
|
||||||
code: &'a Option<DrawCode>,
|
code: &'a Option<DrawCode>,
|
||||||
base: &'a Option<Arc<Path>>,
|
base: &'a Option<Arc<Path>>,
|
||||||
|
/// Text size against the theme's.
|
||||||
|
scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Draw<'_> {
|
impl Draw<'_> {
|
||||||
@@ -186,7 +188,7 @@ impl Draw<'_> {
|
|||||||
};
|
};
|
||||||
let heading = div()
|
let heading = div()
|
||||||
.pt_2()
|
.pt_2()
|
||||||
.text_size(theme.text_size(size))
|
.text_size(theme.text_size(size) * self.scale)
|
||||||
.font_weight(if *level <= 2 {
|
.font_weight(if *level <= 2 {
|
||||||
FontWeight::SEMIBOLD
|
FontWeight::SEMIBOLD
|
||||||
} else {
|
} else {
|
||||||
@@ -238,11 +240,11 @@ impl Draw<'_> {
|
|||||||
.rounded(theme.radius(Radius::Md))
|
.rounded(theme.radius(Radius::Md))
|
||||||
.bg(colors.sunken)
|
.bg(colors.sunken)
|
||||||
.font_family(theme.mono_family.clone())
|
.font_family(theme.mono_family.clone())
|
||||||
.text_size(theme.text_size(TextSize::Sm))
|
.text_size(theme.text_size(TextSize::Sm) * self.scale)
|
||||||
.text_color(colors.syntax.variable)
|
.text_color(colors.syntax.variable)
|
||||||
.children(language.clone().map(|name| {
|
.children(language.clone().map(|name| {
|
||||||
div()
|
div()
|
||||||
.text_size(theme.text_size(TextSize::Xs))
|
.text_size(theme.text_size(TextSize::Xs) * self.scale)
|
||||||
.text_color(colors.fg_subtle)
|
.text_color(colors.fg_subtle)
|
||||||
.child(name)
|
.child(name)
|
||||||
}))
|
}))
|
||||||
@@ -422,6 +424,7 @@ pub(crate) fn draw_nodes(
|
|||||||
nodes: &[Node],
|
nodes: &[Node],
|
||||||
base: &Option<Arc<Path>>,
|
base: &Option<Arc<Path>>,
|
||||||
on_link: &Option<OnLink>,
|
on_link: &Option<OnLink>,
|
||||||
|
scale: f32,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Vec<AnyElement> {
|
) -> Vec<AnyElement> {
|
||||||
let draw = Draw {
|
let draw = Draw {
|
||||||
@@ -432,6 +435,7 @@ pub(crate) fn draw_nodes(
|
|||||||
headings: std::cell::Cell::new(0),
|
headings: std::cell::Cell::new(0),
|
||||||
code: &None,
|
code: &None,
|
||||||
base,
|
base,
|
||||||
|
scale,
|
||||||
};
|
};
|
||||||
draw.nodes(nodes, cx)
|
draw.nodes(nodes, cx)
|
||||||
}
|
}
|
||||||
@@ -448,6 +452,7 @@ impl RenderOnce for MarkdownRenderer {
|
|||||||
headings: std::cell::Cell::new(0),
|
headings: std::cell::Cell::new(0),
|
||||||
code: &self.code,
|
code: &self.code,
|
||||||
base: &self.base,
|
base: &self.base,
|
||||||
|
scale: 1.0,
|
||||||
};
|
};
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ mod tasks;
|
|||||||
mod tests;
|
mod tests;
|
||||||
mod view;
|
mod view;
|
||||||
mod welcome;
|
mod welcome;
|
||||||
|
mod writing;
|
||||||
|
|
||||||
pub use buffer::Buffer;
|
pub use buffer::Buffer;
|
||||||
pub use chat::InlineChat;
|
pub use chat::InlineChat;
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::theme::{ActiveTheme, TextSize};
|
use crate::theme::{ActiveTheme, TextSize};
|
||||||
|
|
||||||
|
/// How far a faded line recedes.
|
||||||
|
const FADED: f32 = 0.35;
|
||||||
|
|
||||||
impl CodeEditor {
|
impl CodeEditor {
|
||||||
/// Row `ix` as the list draws it.
|
/// Row `ix` as the list draws it.
|
||||||
pub(crate) fn row(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
pub(crate) fn row(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||||
@@ -19,7 +22,11 @@ impl CodeEditor {
|
|||||||
.w_full()
|
.w_full()
|
||||||
.h(self.frame.line)
|
.h(self.frame.line)
|
||||||
.flex();
|
.flex();
|
||||||
|
let caret = self.buffer.line_of(self.primary().head);
|
||||||
match self.frame.rows[ix] {
|
match self.frame.rows[ix] {
|
||||||
|
Row::Line(line) if self.options.focus_line && line != caret => {
|
||||||
|
self.line_row(base.opacity(FADED), ix, line, window, cx)
|
||||||
|
}
|
||||||
Row::Line(line) => self.line_row(base, ix, line, window, cx),
|
Row::Line(line) => self.line_row(base, ix, line, window, cx),
|
||||||
Row::Lens(at) => self.lens_row(base, ix, at, cx),
|
Row::Lens(at) => self.lens_row(base, ix, at, cx),
|
||||||
Row::Removed(hunk, at) => self.removed_row(base, ix, hunk, at, cx),
|
Row::Removed(hunk, at) => self.removed_row(base, ix, hunk, at, cx),
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ pub(crate) struct Options {
|
|||||||
pub read_only: bool,
|
pub read_only: bool,
|
||||||
pub tab: usize,
|
pub tab: usize,
|
||||||
pub highlighter: Option<super::prose::LineHighlighter>,
|
pub highlighter: Option<super::prose::LineHighlighter>,
|
||||||
|
/// Text size against the theme's.
|
||||||
|
pub scale: f32,
|
||||||
|
/// Lines but the caret's fade.
|
||||||
|
pub focus_line: bool,
|
||||||
|
/// The caret's line stays mid-view.
|
||||||
|
pub typewriter: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What the owner lays over the code.
|
/// What the owner lays over the code.
|
||||||
@@ -114,6 +120,8 @@ pub struct CodeEditor {
|
|||||||
/// The text before an input method's composition began, while it runs.
|
/// The text before an input method's composition began, while it runs.
|
||||||
pub(crate) composing: Option<Snapshot>,
|
pub(crate) composing: Option<Snapshot>,
|
||||||
pub(crate) metrics: Metrics,
|
pub(crate) metrics: Metrics,
|
||||||
|
/// The caret row typewriter scrolling last centered.
|
||||||
|
pub(crate) centered: Option<usize>,
|
||||||
pub(crate) dragging: bool,
|
pub(crate) dragging: bool,
|
||||||
pub(crate) caret_on: bool,
|
pub(crate) caret_on: bool,
|
||||||
/// Blink runs only while focused, or unseen cursors redraw forever.
|
/// Blink runs only while focused, or unseen cursors redraw forever.
|
||||||
@@ -191,11 +199,15 @@ impl CodeEditor {
|
|||||||
read_only: false,
|
read_only: false,
|
||||||
tab: 4,
|
tab: 4,
|
||||||
highlighter: None,
|
highlighter: None,
|
||||||
|
scale: 1.0,
|
||||||
|
focus_line: false,
|
||||||
|
typewriter: false,
|
||||||
},
|
},
|
||||||
marks: Marks::default(),
|
marks: Marks::default(),
|
||||||
marked: None,
|
marked: None,
|
||||||
composing: None,
|
composing: None,
|
||||||
metrics: Metrics::default(),
|
metrics: Metrics::default(),
|
||||||
|
centered: None,
|
||||||
dragging: false,
|
dragging: false,
|
||||||
caret_on: true,
|
caret_on: true,
|
||||||
focused: false,
|
focused: false,
|
||||||
|
|||||||
+10
-3
@@ -2,8 +2,8 @@ use std::ops::Range;
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Bounds, Context, ElementInputHandler, InteractiveElement, IntoElement, MouseButton,
|
AnyElement, Bounds, Context, ElementInputHandler, InteractiveElement, IntoElement, MouseButton,
|
||||||
MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, Styled, StyledText, Window,
|
MouseDownEvent, MouseMoveEvent, ParentElement, Pixels, Render, ScrollStrategy, Styled,
|
||||||
canvas, div, fill, font, point, size, uniform_list,
|
StyledText, Window, canvas, div, fill, font, point, size, uniform_list,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -225,7 +225,7 @@ impl Render for CodeEditor {
|
|||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let colors = theme.colors.clone();
|
let colors = theme.colors.clone();
|
||||||
let mono_family = theme.mono_family.clone();
|
let mono_family = theme.mono_family.clone();
|
||||||
let size = theme.text_size(TextSize::Sm);
|
let size = theme.text_size(TextSize::Sm) * self.options.scale;
|
||||||
let pixels = size.to_pixels(window.rem_size());
|
let pixels = size.to_pixels(window.rem_size());
|
||||||
let mono = window
|
let mono = window
|
||||||
.text_system()
|
.text_system()
|
||||||
@@ -263,6 +263,13 @@ impl Render for CodeEditor {
|
|||||||
};
|
};
|
||||||
self.metrics.advance = advance;
|
self.metrics.advance = advance;
|
||||||
self.metrics.line = line;
|
self.metrics.line = line;
|
||||||
|
// Typewriter: the caret's row centers as it moves.
|
||||||
|
let row = self.primary_row();
|
||||||
|
if self.options.typewriter && self.centered != Some(row) {
|
||||||
|
self.scroll
|
||||||
|
.scroll_to_item_strict(row, ScrollStrategy::Center);
|
||||||
|
self.centered = Some(row);
|
||||||
|
}
|
||||||
let sticky = if self.options.sticky {
|
let sticky = if self.options.sticky {
|
||||||
self.sticky(cx)
|
self.sticky(cx)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//! Settings for a writing view over the code.
|
||||||
|
|
||||||
|
use gpui::Context;
|
||||||
|
|
||||||
|
use super::CodeEditor;
|
||||||
|
|
||||||
|
impl CodeEditor {
|
||||||
|
/// Text size against the theme's, for a writing view.
|
||||||
|
pub fn set_scale(&mut self, scale: f32, cx: &mut Context<Self>) {
|
||||||
|
self.options.scale = scale;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fades every line but the caret's.
|
||||||
|
pub fn set_focus_line(&mut self, on: bool, cx: &mut Context<Self>) {
|
||||||
|
self.options.focus_line = on;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keeps the caret's line mid-view.
|
||||||
|
pub fn set_typewriter(&mut self, on: bool, cx: &mut Context<Self>) {
|
||||||
|
self.options.typewriter = on;
|
||||||
|
self.centered = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user