Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions crates/base/src/input/base/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2627,9 +2627,39 @@ impl<M: InputModeKind> InputBaseState<M> {
if !self.is_editable() {
return;
}
let Some(clipboard) = cx.read_from_clipboard() else {
if let Some(clipboard) = cx.read_from_clipboard() {
self.insert_clipboard(clipboard, window, cx);
return;
};
}
// The synchronous read is empty on platforms whose clipboard is
// asynchronous and permission-gated (the web), so fall back to the
// real read. It has to start here, still inside the user activation
// that dispatched `Paste`, or the browser refuses the read.
let read = cx.read_from_clipboard_async();
cx.spawn_in(window, async move |this, cx| match read.await {
Ok(Some(clipboard)) => {
this.update_in(cx, |this, window, cx| {
if this.is_editable() {
this.insert_clipboard(clipboard, window, cx);
}
})
.ok();
}
Ok(None) => {}
Err(error) => tracing::warn!("failed to read the clipboard for paste: {error}"),
})
.detach();
}

/// Inserts the clipboard text the way `Paste` does: one atomic edit,
/// newlines dropped on a single-line input, one line per selection when
/// the counts match on a multi-line one.
fn insert_clipboard(
&mut self,
clipboard: ClipboardItem,
window: &mut Window,
cx: &mut Context<Self>,
) {
let mut new_text = clipboard.text().unwrap_or_default();
// A paste is one atomic edit, never part of a typing run.
self.undo_manager.set_pending_intent(EditIntent::Atomic);
Expand Down
13 changes: 9 additions & 4 deletions crates/component/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,11 @@ impl Input {
/// belong in app-owned state beside the input (e.g. `Attachment`s), never
/// inside it.
///
/// Known limit: on web `read_from_clipboard()` is `None` (text arrives
/// through the platform input handler); image paste there needs
/// `read_from_clipboard_async` and permission, out of scope here.
/// Known limit: on web `read_from_clipboard()` is `None`, so the handler
/// is skipped there and the input inserts the plain text itself (a
/// keyboard paste arrives through the platform input handler, a menu
/// paste through the asynchronous clipboard read); image paste there
/// needs `read_from_clipboard_async` and permission, out of scope here.
pub fn on_paste(
mut self,
handler: impl Fn(&gpui::ClipboardItem, &mut Window, &mut App) -> bool + 'static,
Expand Down Expand Up @@ -619,9 +621,12 @@ impl RenderOnce for Input {
!capabilities.is_copyable(),
Box::new(gpui_base::input::Copy),
)
// Offered whenever the text can change, without peeking
// at the clipboard: the synchronous read is always empty
// on the web, and an empty clipboard pastes nothing.
.menu_with_disabled(
t!("Input.Paste"),
!(editable && cx.read_from_clipboard().is_some()),
!editable,
Box::new(gpui_base::input::Paste),
)
.separator()
Expand Down
Loading