Add ClipboardItem::string

This commit is contained in:
Richard Feldman
2024-08-04 14:08:44 -04:00
parent 8c8d350b61
commit 4667e2ec22
24 changed files with 128 additions and 91 deletions

View File

@@ -2336,7 +2336,7 @@ impl ContextEditor {
}
if spanned_messages > 1 {
cx.write_to_clipboard(ClipboardItem::new(copied_text));
cx.write_to_clipboard(ClipboardItem::new_string(copied_text));
return;
}
}

View File

@@ -280,7 +280,7 @@ impl ChannelView {
};
let link = channel.notes_link(closest_heading.map(|heading| heading.text), cx);
cx.write_to_clipboard(ClipboardItem::new(link));
cx.write_to_clipboard(ClipboardItem::new_string(link));
self.workspace
.update(cx, |workspace, cx| {
struct CopyLinkForPositionToast;

View File

@@ -710,7 +710,7 @@ impl ChatPanel {
active_chat.read(cx).find_loaded_message(message_id)
}) {
let text = message.body.clone();
cx.write_to_clipboard(ClipboardItem::new(text))
cx.write_to_clipboard(ClipboardItem::new_string(text))
}
}),
)

View File

@@ -2042,7 +2042,7 @@ impl CollabPanel {
let Some(channel) = channel_store.channel_for_id(channel_id) else {
return;
};
let item = ClipboardItem::new(channel.link(cx));
let item = ClipboardItem::new_string(channel.link(cx));
cx.write_to_clipboard(item)
}
@@ -2261,7 +2261,7 @@ impl CollabPanel {
.size(ButtonSize::None)
.visible_on_hover("section-header")
.on_click(move |_, cx| {
let item = ClipboardItem::new(channel_link_copy.clone());
let item = ClipboardItem::new_string(channel_link_copy.clone());
cx.write_to_clipboard(item)
})
.tooltip(|cx| Tooltip::text("Copy channel link", cx))

View File

@@ -175,7 +175,8 @@ impl Render for ChannelModal {
.read(cx)
.channel_for_id(channel_id)
{
let item = ClipboardItem::new(channel.link(cx));
let item =
ClipboardItem::new_string(channel.link(cx));
cx.write_to_clipboard(item);
}
})),

View File

@@ -55,7 +55,7 @@ impl CopilotCodeVerification {
) -> impl IntoElement {
let copied = cx
.read_from_clipboard()
.map(|item| item.text() == &data.user_code)
.map(|item| item.text() == Some(&data.user_code))
.unwrap_or(false);
h_flex()
.w_full()
@@ -68,7 +68,7 @@ impl CopilotCodeVerification {
.on_mouse_down(gpui::MouseButton::Left, {
let user_code = data.user_code.clone();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(user_code.clone()));
cx.write_to_clipboard(ClipboardItem::new_string(user_code.clone()));
cx.refresh();
}
})

View File

@@ -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()),
)
}),
),
),

View File

@@ -70,12 +70,12 @@ 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,
ClipboardString, 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};
@@ -2280,7 +2280,7 @@ impl Editor {
}
if !text.is_empty() {
cx.write_to_primary(ClipboardItem::new(text));
cx.write_to_primary(ClipboardItem::new_string(text));
}
}
@@ -6545,7 +6545,9 @@ impl Editor {
s.select(selections);
});
this.insert("", cx);
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
cx.write_to_clipboard(ClipboardItem::String(
ClipboardString::new(text).with_metadata(clipboard_selections),
));
});
}
@@ -6584,7 +6586,9 @@ impl Editor {
}
}
cx.write_to_clipboard(ClipboardItem::new(text).with_metadata(clipboard_selections));
cx.write_to_clipboard(ClipboardItem::String(
ClipboardString::new(text).with_metadata(clipboard_selections),
));
}
pub fn do_paste(
@@ -6667,10 +6671,11 @@ impl Editor {
}
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(item) = cx.read_from_clipboard() {
let todo = (); // TODO support pasting clipboard images, not just strings.
if let Some(ClipboardItem::String(clipboard_string)) = cx.read_from_clipboard() {
self.do_paste(
item.text(),
item.metadata::<Vec<ClipboardSelection>>(),
clipboard_string.text(),
clipboard_string.metadata::<Vec<ClipboardSelection>>(),
true,
cx,
)
@@ -10474,7 +10479,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()));
}
}
}
@@ -10484,7 +10489,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()));
}
}
}
@@ -10674,7 +10679,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}");
@@ -11610,7 +11615,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 {
@@ -12877,7 +12882,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)),
)

View File

@@ -4273,7 +4273,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()),

View File

@@ -44,7 +44,7 @@ pub fn init(cx: &mut AppContext) {
cx.spawn(|_, mut cx| async move {
let specs = specs.await.to_string();
cx.update(|cx| cx.write_to_clipboard(ClipboardItem::new(specs.clone())))
cx.update(|cx| cx.write_to_clipboard(ClipboardItem::new_string(specs.clone())))
.log_err();
cx.prompt(

View File

@@ -980,6 +980,21 @@ pub enum ClipboardItem {
},
}
impl ClipboardItem {
/// Create a new ClipboardItem::String with no associated metadata
pub fn new_string(text: String) -> Self {
Self::String(ClipboardString::new(text))
}
/// If this is a ClipboardItem::String, return that string's text
pub fn text(&self) -> Option<&str> {
match self {
Self::String(ClipboardString { text, metadata: _ }) => Some(text),
_ => None,
}
}
}
/// One of our supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ImageFormat {
@@ -999,7 +1014,7 @@ pub struct ClipboardString {
}
impl ClipboardString {
/// Create a new clipboard item with the given text
/// Create a new clipboard string with the given text
pub fn new(text: String) -> Self {
Self {
text,
@@ -1013,12 +1028,17 @@ impl ClipboardString {
self
}
/// Get the text of the clipboard item
/// Get the text of the clipboard string
pub fn text(&self) -> &String {
&self.text
}
/// Get the metadata of the clipboard item
/// Get the owned text of the clipboard string
pub fn into_text(self) -> String {
self.text
}
/// Get the metadata of the clipboard string
pub fn metadata<T>(&self) -> Option<T>
where
T: for<'a> Deserialize<'a>,

View File

@@ -145,7 +145,7 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_read = Some(ClipboardItem::new(v));
self.cached_read = Some(ClipboardItem::new_string(v));
self.cached_read.clone()
}
Err(err) => {
@@ -177,7 +177,7 @@ impl Clipboard {
match unsafe { read_fd(fd) } {
Ok(v) => {
self.cached_primary_read = Some(ClipboardItem::new(v.clone()));
self.cached_primary_read = Some(ClipboardItem::new_string(v.clone()));
self.cached_primary_read.clone()
}
Err(err) => {

View File

@@ -1284,12 +1284,11 @@ mod tests {
let platform = build_platform();
assert_eq!(platform.read_from_clipboard(), None);
let item = ClipboardItem::String(ClipboardString::new("1".to_string()));
let item = ClipboardItem::new_string("1".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
let item =
ClipboardItem::String(ClipboardString::new("2".to_string()).with_metadata(vec![3, 4]));
let item = ClipboardItem::new_string("2".to_string()).with_metadata(vec![3, 4]);
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
@@ -1308,9 +1307,7 @@ mod tests {
}
assert_eq!(
platform.read_from_clipboard(),
Some(ClipboardItem::String(ClipboardString::new(
text_from_other_app.to_string()
)))
Some(ClipboardItem::new_string(text_from_other_app.to_string()))
);
}

View File

@@ -826,15 +826,15 @@ mod tests {
#[test]
fn test_clipboard() {
let platform = WindowsPlatform::new();
let item = ClipboardItem::new("你好".to_string());
let item = ClipboardItem::new_string("你好".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
let item = ClipboardItem::new("12345".to_string());
let item = ClipboardItem::new_string("12345".to_string());
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
let item = ClipboardItem::new("abcdef".to_string()).with_metadata(vec![3, 4]);
let item = ClipboardItem::new_string("abcdef".to_string()).with_metadata(vec![3, 4]);
platform.write_to_clipboard(item.clone());
assert_eq!(platform.read_from_clipboard(), Some(item));
}

View File

@@ -3,11 +3,11 @@ pub mod parser;
use crate::parser::CodeBlockKind;
use futures::FutureExt;
use gpui::{
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, ClipboardString,
CursorStyle, DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight,
GlobalElementId, Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent,
MouseUpEvent, Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout,
TextRun, TextStyle, TextStyleRefinement, View,
actions, point, quad, AnyElement, AppContext, Bounds, ClipboardItem, CursorStyle,
DispatchPhase, Edges, FocusHandle, FocusableView, FontStyle, FontWeight, GlobalElementId,
Hitbox, Hsla, KeyContext, Length, MouseDownEvent, MouseEvent, MouseMoveEvent, MouseUpEvent,
Point, Render, StrikethroughStyle, StyleRefinement, StyledText, Task, TextLayout, TextRun,
TextStyle, TextStyleRefinement, View,
};
use language::{Language, LanguageRegistry, Rope};
use parser::{parse_links_only, parse_markdown, MarkdownEvent, MarkdownTag, MarkdownTagEnd};
@@ -150,7 +150,7 @@ impl Markdown {
return;
}
let text = text.text_for_range(self.selection.start..self.selection.end);
cx.write_to_clipboard(ClipboardItem::String(ClipboardString::new(text)));
cx.write_to_clipboard(ClipboardItem::new_string(text));
}
fn parse(&mut self, cx: &mut ViewContext<Self>) {
@@ -480,7 +480,7 @@ impl MarkdownElement {
{
let text = rendered_text
.text_for_range(markdown.selection.start..markdown.selection.end);
cx.write_to_primary(ClipboardItem::new(text))
cx.write_to_primary(ClipboardItem::new_string(text))
}
cx.notify();
}

View File

@@ -1067,7 +1067,7 @@ impl OutlinePanel {
.and_then(|entry| self.abs_path(&entry, cx))
.map(|p| p.to_string_lossy().to_string())
{
cx.write_to_clipboard(ClipboardItem::new(clipboard_text));
cx.write_to_clipboard(ClipboardItem::new_string(clipboard_text));
}
}
@@ -1082,7 +1082,7 @@ impl OutlinePanel {
})
.map(|p| p.to_string_lossy().to_string())
{
cx.write_to_clipboard(ClipboardItem::new(clipboard_text));
cx.write_to_clipboard(ClipboardItem::new_string(clipboard_text));
}
}

View File

@@ -1357,7 +1357,7 @@ impl ProjectPanel {
fn copy_path(&mut self, _: &CopyPath, cx: &mut ViewContext<Self>) {
if let Some((worktree, entry)) = self.selected_entry(cx) {
cx.write_to_clipboard(ClipboardItem::new(
cx.write_to_clipboard(ClipboardItem::new_string(
worktree
.abs_path()
.join(&entry.path)
@@ -1369,7 +1369,9 @@ impl ProjectPanel {
fn copy_relative_path(&mut self, _: &CopyRelativePath, cx: &mut ViewContext<Self>) {
if let Some((_, entry)) = self.selected_entry(cx) {
cx.write_to_clipboard(ClipboardItem::new(entry.path.to_string_lossy().to_string()));
cx.write_to_clipboard(ClipboardItem::new_string(
entry.path.to_string_lossy().to_string(),
));
}
}

View File

@@ -55,9 +55,9 @@ use std::{
use thiserror::Error;
use gpui::{
actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, ClipboardString,
EventEmitter, Hsla, Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
actions, black, px, AnyWindowHandle, AppContext, Bounds, ClipboardItem, EventEmitter, Hsla,
Keystroke, ModelContext, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, Rgba, ScrollWheelEvent, Size, Task, TouchPhase,
};
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
@@ -652,9 +652,9 @@ impl Terminal {
self.breadcrumb_text = String::new();
cx.emit(Event::BreadcrumbsChanged);
}
AlacTermEvent::ClipboardStore(_, data) => cx.write_to_clipboard(ClipboardItem::String(
ClipboardString::new(data.to_string()),
)),
AlacTermEvent::ClipboardStore(_, data) => {
cx.write_to_clipboard(ClipboardItem::new_string(data.to_string()))
}
AlacTermEvent::ClipboardLoad(_, format) => {
self.write_to_pty(match &cx.read_from_clipboard() {
// The terminal only supports pasting strings
@@ -766,7 +766,7 @@ impl Terminal {
#[cfg(target_os = "linux")]
if let Some(selection_text) = term.selection_to_string() {
cx.write_to_primary(ClipboardItem::new(selection_text));
cx.write_to_primary(ClipboardItem::new_string(selection_text));
}
if let Some((_, head)) = selection {
@@ -787,7 +787,7 @@ impl Terminal {
#[cfg(target_os = "linux")]
if let Some(selection_text) = term.selection_to_string() {
cx.write_to_primary(ClipboardItem::new(selection_text));
cx.write_to_primary(ClipboardItem::new_string(selection_text));
}
self.selection_head = Some(point);
@@ -797,7 +797,7 @@ impl Terminal {
InternalEvent::Copy => {
if let Some(txt) = term.selection_to_string() {
cx.write_to_clipboard(ClipboardItem::String(ClipboardString::new(txt)))
cx.write_to_clipboard(ClipboardItem::new_string(txt))
}
}
InternalEvent::ScrollToAlacPoint(point) => {

View File

@@ -6,10 +6,10 @@ use collections::HashSet;
use editor::{actions::SelectAll, scroll::Autoscroll, Editor};
use futures::{stream::FuturesUnordered, StreamExt};
use gpui::{
anchored, deferred, div, impl_actions, AnyElement, AppContext, DismissEvent, EventEmitter,
FocusHandle, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model, MouseButton,
MouseDownEvent, Pixels, Render, ScrollWheelEvent, Styled, Subscription, Task, View,
VisualContext, WeakView,
anchored, deferred, div, impl_actions, AnyElement, AppContext, ClipboardItem, DismissEvent,
EventEmitter, FocusHandle, FocusableView, KeyContext, KeyDownEvent, Keystroke, Model,
MouseButton, MouseDownEvent, Pixels, Render, ScrollWheelEvent, Styled, Subscription, Task,
View, VisualContext, WeakView,
};
use language::Bias;
use persistence::TERMINAL_DB;
@@ -487,9 +487,9 @@ impl TerminalView {
///Attempt to paste the clipboard into the terminal
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(item) = cx.read_from_clipboard() {
if let Some(ClipboardItem::String(clipboard_string)) = cx.read_from_clipboard() {
self.terminal
.update(cx, |terminal, _cx| terminal.paste(item.text()));
.update(cx, |terminal, _cx| terminal.paste(clipboard_string.text()));
}
}

View File

@@ -376,7 +376,7 @@ mod test {
cx.read_from_clipboard().map(|item| item.text().clone()),
Some("jumps".into())
);
cx.write_to_clipboard(ClipboardItem::new("test-copy".to_string()));
cx.write_to_clipboard(ClipboardItem::new_string("test-copy".to_string()));
cx.simulate_keystrokes("shift-p");
cx.assert_state(
indoc! {"

View File

@@ -5,7 +5,7 @@ use crate::surrounds::SurroundsType;
use crate::{motion::Motion, object::Object};
use collections::HashMap;
use editor::{Anchor, ClipboardSelection};
use gpui::{Action, ClipboardItem, KeyContext};
use gpui::{Action, ClipboardItem, ClipboardString, KeyContext};
use language::{CursorShape, Selection, TransactionId};
use serde::{Deserialize, Serialize};
use ui::SharedString;
@@ -129,20 +129,27 @@ pub struct Register {
impl From<Register> for ClipboardItem {
fn from(register: Register) -> Self {
let item = ClipboardItem::new(register.text.into());
if let Some(clipboard_selections) = register.clipboard_selections {
item.with_metadata(clipboard_selections)
} else {
item
}
let clipboard_string = ClipboardString::new(register.text.into());
ClipboardItem::String(
if let Some(clipboard_selections) = register.clipboard_selections {
clipboard_string.with_metadata(clipboard_selections)
} else {
clipboard_string
},
)
}
}
impl From<ClipboardItem> for Register {
fn from(value: ClipboardItem) -> Self {
Register {
text: value.text().to_owned().into(),
clipboard_selections: value.metadata::<Vec<ClipboardSelection>>(),
fn from(item: ClipboardItem) -> Self {
match item {
ClipboardItem::String(value) => Register {
text: value.text().to_owned().into(),
clipboard_selections: value.metadata::<Vec<ClipboardSelection>>(),
},
// For now, registers can't store images. This could change in the future.
ClipboardItem::Image { .. } => Register::default(),
}
}
}

View File

@@ -26,8 +26,8 @@ use editor::{
Anchor, Bias, Editor, EditorEvent, EditorMode, ToPoint,
};
use gpui::{
actions, impl_actions, Action, AppContext, EntityId, FocusableView, Global, KeystrokeEvent,
Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
actions, impl_actions, Action, AppContext, ClipboardItem, EntityId, FocusableView, Global,
KeystrokeEvent, Subscription, UpdateGlobal, View, ViewContext, WeakView, WindowContext,
};
use language::{CursorShape, Point, SelectionGoal, TransactionId};
pub use mode_indicator::ModeIndicator;
@@ -584,9 +584,12 @@ impl Vim {
self.workspace_state.last_yank.replace(content.text.clone());
cx.write_to_clipboard(content.clone().into());
} else {
self.workspace_state.last_yank = cx
.read_from_clipboard()
.map(|item| item.text().to_owned().into());
self.workspace_state.last_yank = match cx.read_from_clipboard() {
Some(ClipboardItem::String(clipboard_string)) => {
Some(clipboard_string.into_text().into())
}
_ => None,
}
}
self.workspace_state.registers.insert('"', content.clone());
@@ -663,7 +666,7 @@ impl Vim {
fn system_clipboard_is_newer(&self, cx: &mut AppContext) -> bool {
cx.read_from_clipboard().is_some_and(|item| {
if let Some(last_state) = &self.workspace_state.last_yank {
last_state != item.text()
Some(last_state.as_ref()) != item.text()
} else {
true
}

View File

@@ -342,7 +342,7 @@ impl Render for LanguageServerPrompt {
.on_click({
let message = request.message.clone();
move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(
cx.write_to_clipboard(ClipboardItem::new_string(
message.clone(),
))
}

View File

@@ -1600,7 +1600,7 @@ impl Pane {
.and_then(|entry| entry.project_path(cx))
.map(|p| p.path.to_string_lossy().to_string())
{
cx.write_to_clipboard(ClipboardItem::new(clipboard_text));
cx.write_to_clipboard(ClipboardItem::new_string(clipboard_text));
}
}
@@ -1810,7 +1810,7 @@ impl Pane {
"Copy Path",
Some(Box::new(CopyPath)),
cx.handler_for(&pane, move |_, cx| {
cx.write_to_clipboard(ClipboardItem::new(
cx.write_to_clipboard(ClipboardItem::new_string(
abs_path.to_string_lossy().to_string(),
));
}),