T44d Web · Hidden demos keep still, wide ones scroll whole, pastes read the web's clipboard

This commit is contained in:
ZacharyZhang-NY committed 2026-09-29 22:13:27 -04:00
1 parent d2018f8510
commit 752e7cba0c
26 files changed
+211 -101

No files matched your search

+2
View File
@@ -161,6 +161,8 @@ impl CodeEditor {
div()
.w(advance * digits as f32)
.text_right()
// Snapped to device pixels, the box can run short.
.whitespace_nowrap()
.when(current, |number| number.text_color(colors.fg))
.child(number),
)
+1 -1
View File
@@ -97,7 +97,7 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-alt-[", Fold, context),
KeyBinding::new("secondary-alt-]", Unfold, context),
];
// The browser's paste event carries the clipboard to the input handler.
// The browser's paste event reaches the input handler.
if !cfg!(target_family = "wasm") {
bindings.push(KeyBinding::new("secondary-v", Paste, context));
}
+9 -2
View File
@@ -112,6 +112,8 @@ pub struct CodeEditor {
pub(crate) metrics: Metrics,
pub(crate) dragging: bool,
pub(crate) caret_on: bool,
/// Blink runs only while focused, or unseen cursors redraw forever.
focused: bool,
pub(crate) chat_field: gpui::Entity<TextInput>,
pub(crate) frame: Frame,
blink_epoch: u64,
@@ -133,8 +135,12 @@ impl CodeEditor {
let chat_field =
cx.new(|cx| TextInput::new(window, cx).placeholder("Ask to change this code"));
let subscriptions = vec![
cx.on_focus(&focus, window, |editor, _, cx| editor.restart_blink(cx)),
cx.on_focus(&focus, window, |editor, _, cx| {
editor.focused = true;
editor.restart_blink(cx);
}),
cx.on_blur(&focus, window, |editor, _, cx| {
editor.focused = false;
editor.dragging = false;
editor._blink = None;
cx.notify();
@@ -178,6 +184,7 @@ impl CodeEditor {
metrics: Metrics::default(),
dragging: false,
caret_on: true,
focused: false,
chat_field,
frame: Frame::default(),
blink_epoch: 0,
@@ -462,7 +469,7 @@ impl CodeEditor {
self.blink_epoch += 1;
let epoch = self.blink_epoch;
let still = cx.theme().reduced_motion;
self._blink = (!still).then(|| {
self._blink = (!still && self.focused).then(|| {
cx.spawn(async move |editor, cx| {
loop {
cx.background_executor().timer(BLINK).await;
+28 -1
View File
@@ -1,4 +1,4 @@
use std::time::Duration;
use std::{cell::Cell, rc::Rc, time::Duration};
use gpui::{
ClipboardItem, Entity, EntityInputHandler, KeyUpEvent, Keystroke, TestAppContext,
@@ -256,3 +256,30 @@ fn a_browser_paste_gives_each_cursor_its_line(cx: &mut TestAppContext) {
})
});
}
#[gpui::test]
fn only_a_focused_editor_blinks(cx: &mut TestAppContext) {
let (editor, cx) = editor("a\nb", cx);
let redraws = Rc::new(Cell::new(0));
let counted = redraws.clone();
let _count = cx.update(|_, cx| cx.observe(&editor, move |_, _| counted.set(counted.get() + 1)));
editor.update(cx, |editor, cx| {
let second = 2..2;
editor.select([second], cx);
});
cx.run_until_parked();
redraws.set(0);
cx.executor().advance_clock(Duration::from_secs(3));
cx.run_until_parked();
assert_eq!(redraws.get(), 0, "an unfocused editor keeps still");
cx.update(|window, cx| {
window.activate_window();
let focus = editor.read(cx).focus.clone();
focus.focus(window, cx);
});
cx.run_until_parked();
redraws.set(0);
cx.executor().advance_clock(Duration::from_secs(3));
cx.run_until_parked();
assert!(redraws.get() > 2, "a focused one blinks");
}
+1 -1
View File
@@ -89,7 +89,7 @@ pub(crate) fn bind_keys(cx: &mut App) {
KeyBinding::new("secondary-enter", Submit, Some(CONTEXT)),
KeyBinding::new("secondary-enter", Submit, Some(FORM_CONTEXT)),
];
// The browser's paste event carries the clipboard to the input handler.
// The browser's paste event reaches the input handler.
if !cfg!(target_family = "wasm") {
bindings.push(KeyBinding::new("secondary-v", Paste, Some(CONTEXT)));
}
+22 -10
View File
@@ -1,6 +1,9 @@
use std::rc::Rc;
use gpui::{App, ClipboardItem, Entity, KeyDownEvent, Pixels, Point, SharedString, Window, point};
use anyhow::anyhow;
use gpui::{
App, ClipboardItem, Entity, KeyDownEvent, Pixels, Point, SharedString, TaskExt, Window, point,
};
use super::{
formula::filled,
@@ -283,15 +286,24 @@ pub(crate) fn keys(
sheet.read(cx),
))),
("v", true) => {
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
log::info!("grid: the clipboard holds no text to paste");
return;
};
let edits = pasted(&text, (row, col), (rows, cols));
log::info!("grid: pasted {} cells", edits.len());
if let Some(on_change) = &face.on_change {
on_change(&edits, window, cx);
}
// The web reads its clipboard only through the async reader.
let read = cx.read_from_clipboard_async();
let on_change = face.on_change.clone();
window
.spawn(cx, async move |cx| {
let item = read.await.map_err(|error| anyhow!("{error:?}"))?;
let Some(text) = item.and_then(|item| item.text()) else {
log::info!("grid: the clipboard holds no text to paste");
return Ok(());
};
let edits = pasted(&text, (row, col), (rows, cols));
log::info!("grid: pasted {} cells", edits.len());
if let Some(on_change) = on_change {
cx.update(|window, cx| on_change(&edits, window, cx))?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
_ => {
let typed = stroke.key_char.clone().filter(|typed| {
+33
View File
@@ -0,0 +1,33 @@
use std::{cell::RefCell, rc::Rc};
use gpui::{ClipboardItem, Modifiers, TestAppContext, point, px};
use super::{Grid, settle};
use crate::theme::Theme;
#[gpui::test]
fn a_paste_fills_cells_from_the_cursor(cx: &mut TestAppContext) {
cx.update(|cx| {
Theme::init(cx);
crate::forms::bind_keys(cx);
});
let heard = Rc::new(RefCell::new(Vec::new()));
let seen = heard.clone();
let (_, cx) = cx.add_window_view(|_, _| Grid(seen));
settle(cx);
cx.simulate_click(point(px(48.0), px(42.0)), Modifiers::none());
settle(cx);
cx.update(|_, cx| cx.write_to_clipboard(ClipboardItem::new_string("x\ty\nz\tw".into())));
cx.simulate_keystrokes("cmd-v");
settle(cx);
let cells = |row, col, text: &str| (row, col, text.to_string());
assert_eq!(
*heard.borrow(),
[
cells(0, 0, "x"),
cells(0, 1, "y"),
cells(1, 0, "z"),
cells(1, 1, "w")
]
);
}
+1
View File
@@ -9,6 +9,7 @@ use gpui::{
Styled, TestAppContext, VisualTestContext, Window, div, point, px,
};
mod clipboard;
mod columns;
use super::{Column, DataTable, FilterBuilder, FilterRule, Row};
+1 -1
View File
@@ -90,7 +90,7 @@ pub(crate) fn printed(source: &str) -> (String, Vec<(Range<usize>, Pen)>) {
(printed.text, printed.spans)
}
/// Plain text sets no colors with OSC 4, 10 or 11.
/// Plain text sets no colors through OSC 4, 10, 11.
struct Unset;
impl Index<usize> for Unset {
+1 -1
View File
@@ -93,7 +93,7 @@ impl Ink {
}
}
/// Colors a program set with OSC 4, 10 or 11, by index.
/// Colors a program set through OSC 4, 10, 11.
pub(crate) trait Set: Index<usize, Output = Option<Rgb>> {}
impl<T: Index<usize, Output = Option<Rgb>>> Set for T {}
+1 -1
View File
@@ -136,7 +136,7 @@ impl Theme {
Self::fade_to_target(cx);
}
/// Sets `mode` without the fade, as before a window's first frame; a fade under way stops.
/// Sets `mode` at once, stopping any fade under way.
pub fn set_mode_now(mode: Mode, cx: &mut App) {
log::info!("theme: mode -> {mode:?}, at once");
let theme = cx.global_mut::<Theme>();
+1 -1
View File
@@ -21,7 +21,7 @@ fn an_owners_palette_serves_its_mode_alone(cx: &mut TestAppContext) {
assert_eq!(cx.read(|cx| cx.theme().palette()), Palette::light(false));
}
/// A mode set at once wears its palette at once, and a fade under way cannot pull it back.
/// A mode set at once keeps its palette through fades.
#[gpui::test]
fn a_mode_set_at_once_skips_the_fade(cx: &mut TestAppContext) {
cx.update(|cx| {