Copy/paste images into editors (Mac only) (#15782)
For future reference: WIP branch of copy/pasting a mixture of images and text: https://github.com/zed-industries/zed/tree/copy-paste-images - we'll come back to that one after landing this one. Release Notes: - You can now paste images into the Assistant Panel to include them as context. Currently works only on Mac, and with Anthropic models. Future support is planned for more models, operating systems, and image clipboard operations. --------- Co-authored-by: Antonio <antonio@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Jason <jason@zed.dev> Co-authored-by: Kyle <kylek@zed.dev>
This commit is contained in:
parent
e3b0de5dda
commit
b1a581e81b
58 changed files with 2983 additions and 1708 deletions
|
@ -2,8 +2,8 @@ use futures::Future;
|
|||
use git::blame::BlameEntry;
|
||||
use git::Oid;
|
||||
use gpui::{
|
||||
Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle, StatefulInteractiveElement,
|
||||
WeakView, WindowContext,
|
||||
AppContext, Asset, ClipboardItem, Element, ParentElement, Render, ScrollHandle,
|
||||
StatefulInteractiveElement, WeakView,
|
||||
};
|
||||
use settings::Settings;
|
||||
use std::hash::Hash;
|
||||
|
@ -35,7 +35,7 @@ impl<'a> CommitAvatar<'a> {
|
|||
|
||||
let avatar_url = CommitAvatarAsset::new(remote.clone(), self.sha);
|
||||
|
||||
let element = match cx.use_cached_asset::<CommitAvatarAsset>(&avatar_url) {
|
||||
let element = match cx.use_asset::<CommitAvatarAsset>(&avatar_url) {
|
||||
// Loading or no avatar found
|
||||
None | Some(None) => Icon::new(IconName::Person)
|
||||
.color(Color::Muted)
|
||||
|
@ -73,7 +73,7 @@ impl Asset for CommitAvatarAsset {
|
|||
|
||||
fn load(
|
||||
source: Self::Source,
|
||||
cx: &mut WindowContext,
|
||||
cx: &mut AppContext,
|
||||
) -> impl Future<Output = Self::Output> + Send + 'static {
|
||||
let client = cx.http_client();
|
||||
|
||||
|
@ -242,9 +242,9 @@ impl Render for BlameEntryTooltip {
|
|||
.icon_color(Color::Muted)
|
||||
.on_click(move |_, cx| {
|
||||
cx.stop_propagation();
|
||||
cx.write_to_clipboard(ClipboardItem::new(
|
||||
full_sha.clone(),
|
||||
))
|
||||
cx.write_to_clipboard(
|
||||
ClipboardItem::new_string(full_sha.clone()),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -69,13 +69,13 @@ use git::blame::GitBlame;
|
|||
use git::diff_hunk_to_display;
|
||||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, px, relative, size, uniform_list, Action, AnyElement,
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem,
|
||||
Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render, SharedString,
|
||||
Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle, UnderlineStyle,
|
||||
UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle,
|
||||
WeakView, WindowContext,
|
||||
AppContext, AsyncWindowContext, AvailableSpace, BackgroundExecutor, Bounds, ClipboardEntry,
|
||||
ClipboardItem, Context, DispatchPhase, ElementId, EntityId, EventEmitter, FocusHandle,
|
||||
FocusOutEvent, FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText,
|
||||
KeyContext, ListSizingBehavior, Model, MouseButton, PaintQuad, ParentElement, Pixels, Render,
|
||||
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
UnderlineStyle, UniformListScrollHandle, View, ViewContext, ViewInputHandler, VisualContext,
|
||||
WeakFocusHandle, WeakView, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
|
@ -2304,7 +2304,7 @@ impl Editor {
|
|||
}
|
||||
|
||||
if !text.is_empty() {
|
||||
cx.write_to_primary(ClipboardItem::new(text));
|
||||
cx.write_to_primary(ClipboardItem::new_string(text));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6585,7 +6585,10 @@ impl Editor {
|
|||
s.select(selections);
|
||||
});
|
||||
this.insert("", cx);
|
||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
text,
|
||||
clipboard_selections,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6624,7 +6627,10 @@ impl Editor {
|
|||
}
|
||||
}
|
||||
|
||||
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
|
||||
text,
|
||||
clipboard_selections,
|
||||
));
|
||||
}
|
||||
|
||||
pub fn do_paste(
|
||||
|
@ -6708,13 +6714,21 @@ impl Editor {
|
|||
|
||||
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
self.do_paste(
|
||||
item.text(),
|
||||
item.metadata::<Vec<ClipboardSelection>>(),
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
};
|
||||
let entries = item.entries();
|
||||
|
||||
match entries.first() {
|
||||
// For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
|
||||
// of all the pasted entries.
|
||||
Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
|
||||
.do_paste(
|
||||
clipboard_string.text(),
|
||||
clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
|
||||
true,
|
||||
cx,
|
||||
),
|
||||
_ => self.do_paste(&item.text().unwrap_or_default(), None, true, cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||
|
@ -10535,7 +10549,7 @@ impl Editor {
|
|||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
|
||||
if let Some(path) = file.abs_path(cx).to_str() {
|
||||
cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10545,7 +10559,7 @@ impl Editor {
|
|||
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
|
||||
if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
|
||||
if let Some(path) = file.path().to_str() {
|
||||
cx.write_to_clipboard(ClipboardItem::new(path.to_string()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10735,7 +10749,7 @@ impl Editor {
|
|||
|
||||
match permalink {
|
||||
Ok(permalink) => {
|
||||
cx.write_to_clipboard(ClipboardItem::new(permalink.to_string()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
|
||||
}
|
||||
Err(err) => {
|
||||
let message = format!("Failed to copy permalink: {err}");
|
||||
|
@ -11671,7 +11685,7 @@ impl Editor {
|
|||
let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
|
||||
return;
|
||||
};
|
||||
cx.write_to_clipboard(ClipboardItem::new(lines));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(lines));
|
||||
}
|
||||
|
||||
pub fn inlay_hint_cache(&self) -> &InlayHintCache {
|
||||
|
@ -12938,7 +12952,9 @@ pub fn diagnostic_block_renderer(
|
|||
.visible_on_hover(group_id.clone())
|
||||
.on_click({
|
||||
let message = diagnostic.message.clone();
|
||||
move |_click, cx| cx.write_to_clipboard(ClipboardItem::new(message.clone()))
|
||||
move |_click, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
|
||||
}
|
||||
})
|
||||
.tooltip(|cx| Tooltip::text("Copy diagnostic message", cx)),
|
||||
)
|
||||
|
|
|
@ -3956,8 +3956,9 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
|
|||
the lazy dog"});
|
||||
cx.update_editor(|e, cx| e.copy(&Copy, cx));
|
||||
assert_eq!(
|
||||
cx.read_from_clipboard().map(|item| item.text().to_owned()),
|
||||
Some("fox jumps over\n".to_owned())
|
||||
cx.read_from_clipboard()
|
||||
.and_then(|item| item.text().as_deref().map(str::to_string)),
|
||||
Some("fox jumps over\n".to_string())
|
||||
);
|
||||
|
||||
// Paste with three selections, noticing how the copied full-line selection is inserted
|
||||
|
|
|
@ -642,7 +642,7 @@ impl EditorElement {
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(item) = cx.read_from_primary() {
|
||||
if let Some(text) = cx.read_from_primary().and_then(|item| item.text()) {
|
||||
let point_for_position =
|
||||
position_map.point_for_position(text_hitbox.bounds, event.position);
|
||||
let position = point_for_position.previous_valid;
|
||||
|
@ -655,7 +655,7 @@ impl EditorElement {
|
|||
},
|
||||
cx,
|
||||
);
|
||||
editor.insert(item.text(), cx);
|
||||
editor.insert(&text, cx);
|
||||
}
|
||||
cx.stop_propagation()
|
||||
}
|
||||
|
@ -4290,7 +4290,7 @@ fn deploy_blame_entry_context_menu(
|
|||
let sha = format!("{}", blame_entry.sha);
|
||||
menu.on_blur_subscription(Subscription::new(|| {}))
|
||||
.entry("Copy commit SHA", None, move |cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new(sha.clone()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(sha.clone()));
|
||||
})
|
||||
.when_some(
|
||||
details.and_then(|details| details.permalink.clone()),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue