platform.rs

   1// todo(windows): remove
   2#![cfg_attr(windows, allow(dead_code))]
   3
   4mod app_menu;
   5mod keystroke;
   6
   7#[cfg(target_os = "linux")]
   8mod linux;
   9
  10#[cfg(target_os = "macos")]
  11mod mac;
  12
  13#[cfg(any(
  14    all(target_os = "linux", any(feature = "x11", feature = "wayland")),
  15    target_os = "windows",
  16    feature = "macos-blade"
  17))]
  18mod blade;
  19
  20#[cfg(any(test, feature = "test-support"))]
  21mod test;
  22
  23#[cfg(target_os = "windows")]
  24mod windows;
  25
  26use crate::{
  27    point, Action, AnyWindowHandle, AppContext, AsyncWindowContext, BackgroundExecutor, Bounds,
  28    DevicePixels, DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor,
  29    GPUSpecs, GlyphId, ImageSource, Keymap, LineLayout, Pixels, PlatformInput, Point,
  30    RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
  31    SharedString, Size, SvgSize, Task, TaskLabel, WindowContext, DEFAULT_WINDOW_SIZE,
  32};
  33use anyhow::{anyhow, Result};
  34use async_task::Runnable;
  35use futures::channel::oneshot;
  36use image::codecs::gif::GifDecoder;
  37use image::{AnimationDecoder as _, Frame};
  38use parking::Unparker;
  39use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
  40use seahash::SeaHasher;
  41use serde::{Deserialize, Serialize};
  42use smallvec::SmallVec;
  43use std::borrow::Cow;
  44use std::hash::{Hash, Hasher};
  45use std::io::Cursor;
  46use std::time::{Duration, Instant};
  47use std::{
  48    fmt::{self, Debug},
  49    ops::Range,
  50    path::{Path, PathBuf},
  51    rc::Rc,
  52    sync::Arc,
  53};
  54use strum::EnumIter;
  55use uuid::Uuid;
  56
  57pub use app_menu::*;
  58pub use keystroke::*;
  59
  60#[cfg(target_os = "linux")]
  61pub(crate) use linux::*;
  62#[cfg(target_os = "macos")]
  63pub(crate) use mac::*;
  64pub use semantic_version::SemanticVersion;
  65#[cfg(any(test, feature = "test-support"))]
  66pub(crate) use test::*;
  67#[cfg(target_os = "windows")]
  68pub(crate) use windows::*;
  69
  70#[cfg(target_os = "macos")]
  71pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
  72    Rc::new(MacPlatform::new(headless))
  73}
  74
  75#[cfg(target_os = "linux")]
  76pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
  77    if headless {
  78        return Rc::new(HeadlessClient::new());
  79    }
  80
  81    match guess_compositor() {
  82        #[cfg(feature = "wayland")]
  83        "Wayland" => Rc::new(WaylandClient::new()),
  84
  85        #[cfg(feature = "x11")]
  86        "X11" => Rc::new(X11Client::new()),
  87
  88        "Headless" => Rc::new(HeadlessClient::new()),
  89        _ => unreachable!(),
  90    }
  91}
  92
  93/// Return which compositor we're guessing we'll use.
  94/// Does not attempt to connect to the given compositor
  95#[cfg(target_os = "linux")]
  96#[inline]
  97pub fn guess_compositor() -> &'static str {
  98    if std::env::var_os("ZED_HEADLESS").is_some() {
  99        return "Headless";
 100    }
 101
 102    #[cfg(feature = "wayland")]
 103    let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
 104    #[cfg(not(feature = "wayland"))]
 105    let wayland_display: Option<std::ffi::OsString> = None;
 106
 107    #[cfg(feature = "x11")]
 108    let x11_display = std::env::var_os("DISPLAY");
 109    #[cfg(not(feature = "x11"))]
 110    let x11_display: Option<std::ffi::OsString> = None;
 111
 112    let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
 113    let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
 114
 115    if use_wayland {
 116        "Wayland"
 117    } else if use_x11 {
 118        "X11"
 119    } else {
 120        "Headless"
 121    }
 122}
 123
 124#[cfg(target_os = "windows")]
 125pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
 126    Rc::new(WindowsPlatform::new())
 127}
 128
 129pub(crate) trait Platform: 'static {
 130    fn background_executor(&self) -> BackgroundExecutor;
 131    fn foreground_executor(&self) -> ForegroundExecutor;
 132    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 133
 134    fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
 135    fn quit(&self);
 136    fn restart(&self, binary_path: Option<PathBuf>);
 137    fn activate(&self, ignoring_other_apps: bool);
 138    fn hide(&self);
 139    fn hide_other_apps(&self);
 140    fn unhide_other_apps(&self);
 141
 142    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
 143    fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 144    fn active_window(&self) -> Option<AnyWindowHandle>;
 145    fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
 146        None
 147    }
 148
 149    fn open_window(
 150        &self,
 151        handle: AnyWindowHandle,
 152        options: WindowParams,
 153    ) -> anyhow::Result<Box<dyn PlatformWindow>>;
 154
 155    /// Returns the appearance of the application's windows.
 156    fn window_appearance(&self) -> WindowAppearance;
 157
 158    fn open_url(&self, url: &str);
 159    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
 160    fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
 161
 162    fn prompt_for_paths(
 163        &self,
 164        options: PathPromptOptions,
 165    ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
 166    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
 167    fn reveal_path(&self, path: &Path);
 168    fn open_with_system(&self, path: &Path);
 169
 170    fn on_quit(&self, callback: Box<dyn FnMut()>);
 171    fn on_reopen(&self, callback: Box<dyn FnMut()>);
 172    fn on_keyboard_layout_change(&self, callback: Box<dyn FnMut()>);
 173
 174    fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
 175    fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
 176        None
 177    }
 178
 179    fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
 180    fn add_recent_document(&self, _path: &Path) {}
 181    fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
 182    fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
 183    fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
 184    fn keyboard_layout(&self) -> String;
 185
 186    fn compositor_name(&self) -> &'static str {
 187        ""
 188    }
 189    fn app_path(&self) -> Result<PathBuf>;
 190    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
 191
 192    fn set_cursor_style(&self, style: CursorStyle);
 193    fn should_auto_hide_scrollbars(&self) -> bool;
 194
 195    #[cfg(target_os = "linux")]
 196    fn write_to_primary(&self, item: ClipboardItem);
 197    fn write_to_clipboard(&self, item: ClipboardItem);
 198    #[cfg(target_os = "linux")]
 199    fn read_from_primary(&self) -> Option<ClipboardItem>;
 200    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
 201
 202    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
 203    fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
 204    fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
 205}
 206
 207/// A handle to a platform's display, e.g. a monitor or laptop screen.
 208pub trait PlatformDisplay: Send + Sync + Debug {
 209    /// Get the ID for this display
 210    fn id(&self) -> DisplayId;
 211
 212    /// Returns a stable identifier for this display that can be persisted and used
 213    /// across system restarts.
 214    fn uuid(&self) -> Result<Uuid>;
 215
 216    /// Get the bounds for this display
 217    fn bounds(&self) -> Bounds<Pixels>;
 218
 219    /// Get the default bounds for this display to place a window
 220    fn default_bounds(&self) -> Bounds<Pixels> {
 221        let center = self.bounds().center();
 222        let offset = DEFAULT_WINDOW_SIZE / 2.0;
 223        let origin = point(center.x - offset.width, center.y - offset.height);
 224        Bounds::new(origin, DEFAULT_WINDOW_SIZE)
 225    }
 226}
 227
 228/// An opaque identifier for a hardware display
 229#[derive(PartialEq, Eq, Hash, Copy, Clone)]
 230pub struct DisplayId(pub(crate) u32);
 231
 232impl Debug for DisplayId {
 233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 234        write!(f, "DisplayId({})", self.0)
 235    }
 236}
 237
 238unsafe impl Send for DisplayId {}
 239
 240/// Which part of the window to resize
 241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 242pub enum ResizeEdge {
 243    /// The top edge
 244    Top,
 245    /// The top right corner
 246    TopRight,
 247    /// The right edge
 248    Right,
 249    /// The bottom right corner
 250    BottomRight,
 251    /// The bottom edge
 252    Bottom,
 253    /// The bottom left corner
 254    BottomLeft,
 255    /// The left edge
 256    Left,
 257    /// The top left corner
 258    TopLeft,
 259}
 260
 261/// A type to describe the appearance of a window
 262#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 263pub enum WindowDecorations {
 264    #[default]
 265    /// Server side decorations
 266    Server,
 267    /// Client side decorations
 268    Client,
 269}
 270
 271/// A type to describe how this window is currently configured
 272#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 273pub enum Decorations {
 274    /// The window is configured to use server side decorations
 275    #[default]
 276    Server,
 277    /// The window is configured to use client side decorations
 278    Client {
 279        /// The edge tiling state
 280        tiling: Tiling,
 281    },
 282}
 283
 284/// What window controls this platform supports
 285#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
 286pub struct WindowControls {
 287    /// Whether this platform supports fullscreen
 288    pub fullscreen: bool,
 289    /// Whether this platform supports maximize
 290    pub maximize: bool,
 291    /// Whether this platform supports minimize
 292    pub minimize: bool,
 293    /// Whether this platform supports a window menu
 294    pub window_menu: bool,
 295}
 296
 297impl Default for WindowControls {
 298    fn default() -> Self {
 299        // Assume that we can do anything, unless told otherwise
 300        Self {
 301            fullscreen: true,
 302            maximize: true,
 303            minimize: true,
 304            window_menu: true,
 305        }
 306    }
 307}
 308
 309/// A type to describe which sides of the window are currently tiled in some way
 310#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
 311pub struct Tiling {
 312    /// Whether the top edge is tiled
 313    pub top: bool,
 314    /// Whether the left edge is tiled
 315    pub left: bool,
 316    /// Whether the right edge is tiled
 317    pub right: bool,
 318    /// Whether the bottom edge is tiled
 319    pub bottom: bool,
 320}
 321
 322impl Tiling {
 323    /// Initializes a [`Tiling`] type with all sides tiled
 324    pub fn tiled() -> Self {
 325        Self {
 326            top: true,
 327            left: true,
 328            right: true,
 329            bottom: true,
 330        }
 331    }
 332
 333    /// Whether any edge is tiled
 334    pub fn is_tiled(&self) -> bool {
 335        self.top || self.left || self.right || self.bottom
 336    }
 337}
 338
 339pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
 340    fn bounds(&self) -> Bounds<Pixels>;
 341    fn is_maximized(&self) -> bool;
 342    fn window_bounds(&self) -> WindowBounds;
 343    fn content_size(&self) -> Size<Pixels>;
 344    fn scale_factor(&self) -> f32;
 345    fn appearance(&self) -> WindowAppearance;
 346    fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
 347    fn mouse_position(&self) -> Point<Pixels>;
 348    fn modifiers(&self) -> Modifiers;
 349    fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
 350    fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
 351    fn prompt(
 352        &self,
 353        level: PromptLevel,
 354        msg: &str,
 355        detail: Option<&str>,
 356        answers: &[&str],
 357    ) -> Option<oneshot::Receiver<usize>>;
 358    fn activate(&self);
 359    fn is_active(&self) -> bool;
 360    fn is_hovered(&self) -> bool;
 361    fn set_title(&mut self, title: &str);
 362    fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
 363    fn minimize(&self);
 364    fn zoom(&self);
 365    fn toggle_fullscreen(&self);
 366    fn is_fullscreen(&self) -> bool;
 367    fn on_request_frame(&self, callback: Box<dyn FnMut()>);
 368    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
 369    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
 370    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
 371    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
 372    fn on_moved(&self, callback: Box<dyn FnMut()>);
 373    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
 374    fn on_close(&self, callback: Box<dyn FnOnce()>);
 375    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
 376    fn draw(&self, scene: &Scene);
 377    fn completed_frame(&self) {}
 378    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
 379
 380    // macOS specific methods
 381    fn set_edited(&mut self, _edited: bool) {}
 382    fn show_character_palette(&self) {}
 383
 384    #[cfg(target_os = "windows")]
 385    fn get_raw_handle(&self) -> windows::HWND;
 386
 387    // Linux specific methods
 388    fn request_decorations(&self, _decorations: WindowDecorations) {}
 389    fn show_window_menu(&self, _position: Point<Pixels>) {}
 390    fn start_window_move(&self) {}
 391    fn start_window_resize(&self, _edge: ResizeEdge) {}
 392    fn window_decorations(&self) -> Decorations {
 393        Decorations::Server
 394    }
 395    fn set_app_id(&mut self, _app_id: &str) {}
 396    fn window_controls(&self) -> WindowControls {
 397        WindowControls::default()
 398    }
 399    fn set_client_inset(&self, _inset: Pixels) {}
 400    fn gpu_specs(&self) -> Option<GPUSpecs>;
 401
 402    fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>);
 403
 404    #[cfg(any(test, feature = "test-support"))]
 405    fn as_test(&mut self) -> Option<&mut TestWindow> {
 406        None
 407    }
 408}
 409
 410/// This type is public so that our test macro can generate and use it, but it should not
 411/// be considered part of our public API.
 412#[doc(hidden)]
 413pub trait PlatformDispatcher: Send + Sync {
 414    fn is_main_thread(&self) -> bool;
 415    fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
 416    fn dispatch_on_main_thread(&self, runnable: Runnable);
 417    fn dispatch_after(&self, duration: Duration, runnable: Runnable);
 418    fn park(&self, timeout: Option<Duration>) -> bool;
 419    fn unparker(&self) -> Unparker;
 420    fn now(&self) -> Instant {
 421        Instant::now()
 422    }
 423
 424    #[cfg(any(test, feature = "test-support"))]
 425    fn as_test(&self) -> Option<&TestDispatcher> {
 426        None
 427    }
 428}
 429
 430pub(crate) trait PlatformTextSystem: Send + Sync {
 431    fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
 432    fn all_font_names(&self) -> Vec<String>;
 433    fn font_id(&self, descriptor: &Font) -> Result<FontId>;
 434    fn font_metrics(&self, font_id: FontId) -> FontMetrics;
 435    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
 436    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
 437    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
 438    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
 439    fn rasterize_glyph(
 440        &self,
 441        params: &RenderGlyphParams,
 442        raster_bounds: Bounds<DevicePixels>,
 443    ) -> Result<(Size<DevicePixels>, Vec<u8>)>;
 444    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
 445}
 446
 447pub(crate) struct NoopTextSystem;
 448
 449impl NoopTextSystem {
 450    #[allow(dead_code)]
 451    pub fn new() -> Self {
 452        Self
 453    }
 454}
 455
 456impl PlatformTextSystem for NoopTextSystem {
 457    fn add_fonts(&self, _fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
 458        Ok(())
 459    }
 460
 461    fn all_font_names(&self) -> Vec<String> {
 462        Vec::new()
 463    }
 464
 465    fn font_id(&self, descriptor: &Font) -> Result<FontId> {
 466        Err(anyhow!("No font found for {:?}", descriptor))
 467    }
 468
 469    fn font_metrics(&self, _font_id: FontId) -> FontMetrics {
 470        unimplemented!()
 471    }
 472
 473    fn typographic_bounds(&self, font_id: FontId, _glyph_id: GlyphId) -> Result<Bounds<f32>> {
 474        Err(anyhow!("No font found for {:?}", font_id))
 475    }
 476
 477    fn advance(&self, font_id: FontId, _glyph_id: GlyphId) -> Result<Size<f32>> {
 478        Err(anyhow!("No font found for {:?}", font_id))
 479    }
 480
 481    fn glyph_for_char(&self, _font_id: FontId, _ch: char) -> Option<GlyphId> {
 482        None
 483    }
 484
 485    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
 486        Err(anyhow!("No font found for {:?}", params))
 487    }
 488
 489    fn rasterize_glyph(
 490        &self,
 491        params: &RenderGlyphParams,
 492        _raster_bounds: Bounds<DevicePixels>,
 493    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
 494        Err(anyhow!("No font found for {:?}", params))
 495    }
 496
 497    fn layout_line(&self, _text: &str, _font_size: Pixels, _runs: &[FontRun]) -> LineLayout {
 498        unimplemented!()
 499    }
 500}
 501
 502#[derive(PartialEq, Eq, Hash, Clone)]
 503pub(crate) enum AtlasKey {
 504    Glyph(RenderGlyphParams),
 505    Svg(RenderSvgParams),
 506    Image(RenderImageParams),
 507}
 508
 509impl AtlasKey {
 510    #[cfg_attr(
 511        all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 512        allow(dead_code)
 513    )]
 514    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
 515        match self {
 516            AtlasKey::Glyph(params) => {
 517                if params.is_emoji {
 518                    AtlasTextureKind::Polychrome
 519                } else {
 520                    AtlasTextureKind::Monochrome
 521                }
 522            }
 523            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
 524            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
 525        }
 526    }
 527}
 528
 529impl From<RenderGlyphParams> for AtlasKey {
 530    fn from(params: RenderGlyphParams) -> Self {
 531        Self::Glyph(params)
 532    }
 533}
 534
 535impl From<RenderSvgParams> for AtlasKey {
 536    fn from(params: RenderSvgParams) -> Self {
 537        Self::Svg(params)
 538    }
 539}
 540
 541impl From<RenderImageParams> for AtlasKey {
 542    fn from(params: RenderImageParams) -> Self {
 543        Self::Image(params)
 544    }
 545}
 546
 547pub(crate) trait PlatformAtlas: Send + Sync {
 548    fn get_or_insert_with<'a>(
 549        &self,
 550        key: &AtlasKey,
 551        build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
 552    ) -> Result<Option<AtlasTile>>;
 553}
 554
 555#[derive(Clone, Debug, PartialEq, Eq)]
 556#[repr(C)]
 557pub(crate) struct AtlasTile {
 558    pub(crate) texture_id: AtlasTextureId,
 559    pub(crate) tile_id: TileId,
 560    pub(crate) padding: u32,
 561    pub(crate) bounds: Bounds<DevicePixels>,
 562}
 563
 564#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 565#[repr(C)]
 566pub(crate) struct AtlasTextureId {
 567    // We use u32 instead of usize for Metal Shader Language compatibility
 568    pub(crate) index: u32,
 569    pub(crate) kind: AtlasTextureKind,
 570}
 571
 572#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 573#[repr(C)]
 574#[cfg_attr(
 575    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 576    allow(dead_code)
 577)]
 578pub(crate) enum AtlasTextureKind {
 579    Monochrome = 0,
 580    Polychrome = 1,
 581    Path = 2,
 582}
 583
 584#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 585#[repr(C)]
 586pub(crate) struct TileId(pub(crate) u32);
 587
 588impl From<etagere::AllocId> for TileId {
 589    fn from(id: etagere::AllocId) -> Self {
 590        Self(id.serialize())
 591    }
 592}
 593
 594impl From<TileId> for etagere::AllocId {
 595    fn from(id: TileId) -> Self {
 596        Self::deserialize(id.0)
 597    }
 598}
 599
 600pub(crate) struct PlatformInputHandler {
 601    cx: AsyncWindowContext,
 602    handler: Box<dyn InputHandler>,
 603}
 604
 605#[cfg_attr(
 606    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 607    allow(dead_code)
 608)]
 609impl PlatformInputHandler {
 610    pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
 611        Self { cx, handler }
 612    }
 613
 614    fn selected_text_range(&mut self, ignore_disabled_input: bool) -> Option<UTF16Selection> {
 615        self.cx
 616            .update(|cx| self.handler.selected_text_range(ignore_disabled_input, cx))
 617            .ok()
 618            .flatten()
 619    }
 620
 621    fn marked_text_range(&mut self) -> Option<Range<usize>> {
 622        self.cx
 623            .update(|cx| self.handler.marked_text_range(cx))
 624            .ok()
 625            .flatten()
 626    }
 627
 628    #[cfg_attr(target_os = "linux", allow(dead_code))]
 629    fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
 630        self.cx
 631            .update(|cx| self.handler.text_for_range(range_utf16, cx))
 632            .ok()
 633            .flatten()
 634    }
 635
 636    fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
 637        self.cx
 638            .update(|cx| {
 639                self.handler
 640                    .replace_text_in_range(replacement_range, text, cx);
 641            })
 642            .ok();
 643    }
 644
 645    fn replace_and_mark_text_in_range(
 646        &mut self,
 647        range_utf16: Option<Range<usize>>,
 648        new_text: &str,
 649        new_selected_range: Option<Range<usize>>,
 650    ) {
 651        self.cx
 652            .update(|cx| {
 653                self.handler.replace_and_mark_text_in_range(
 654                    range_utf16,
 655                    new_text,
 656                    new_selected_range,
 657                    cx,
 658                )
 659            })
 660            .ok();
 661    }
 662
 663    fn unmark_text(&mut self) {
 664        self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
 665    }
 666
 667    fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
 668        self.cx
 669            .update(|cx| self.handler.bounds_for_range(range_utf16, cx))
 670            .ok()
 671            .flatten()
 672    }
 673
 674    pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
 675        self.handler.replace_text_in_range(None, input, cx);
 676    }
 677
 678    pub fn selected_bounds(&mut self, cx: &mut WindowContext) -> Option<Bounds<Pixels>> {
 679        let selection = self.handler.selected_text_range(true, cx)?;
 680        self.handler.bounds_for_range(
 681            if selection.reversed {
 682                selection.range.start..selection.range.start
 683            } else {
 684                selection.range.end..selection.range.end
 685            },
 686            cx,
 687        )
 688    }
 689}
 690
 691/// A struct representing a selection in a text buffer, in UTF16 characters.
 692/// This is different from a range because the head may be before the tail.
 693pub struct UTF16Selection {
 694    /// The range of text in the document this selection corresponds to
 695    /// in UTF16 characters.
 696    pub range: Range<usize>,
 697    /// Whether the head of this selection is at the start (true), or end (false)
 698    /// of the range
 699    pub reversed: bool,
 700}
 701
 702/// Zed's interface for handling text input from the platform's IME system
 703/// This is currently a 1:1 exposure of the NSTextInputClient API:
 704///
 705/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
 706pub trait InputHandler: 'static {
 707    /// Get the range of the user's currently selected text, if any
 708    /// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
 709    ///
 710    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
 711    fn selected_text_range(
 712        &mut self,
 713        ignore_disabled_input: bool,
 714        cx: &mut WindowContext,
 715    ) -> Option<UTF16Selection>;
 716
 717    /// Get the range of the currently marked text, if any
 718    /// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
 719    ///
 720    /// Return value is in terms of UTF-16 characters, from 0 to the length of the document
 721    fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
 722
 723    /// Get the text for the given document range in UTF-16 characters
 724    /// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
 725    ///
 726    /// range_utf16 is in terms of UTF-16 characters
 727    fn text_for_range(
 728        &mut self,
 729        range_utf16: Range<usize>,
 730        cx: &mut WindowContext,
 731    ) -> Option<String>;
 732
 733    /// Replace the text in the given document range with the given text
 734    /// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
 735    ///
 736    /// replacement_range is in terms of UTF-16 characters
 737    fn replace_text_in_range(
 738        &mut self,
 739        replacement_range: Option<Range<usize>>,
 740        text: &str,
 741        cx: &mut WindowContext,
 742    );
 743
 744    /// Replace the text in the given document range with the given text,
 745    /// and mark the given text as part of an IME 'composing' state
 746    /// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
 747    ///
 748    /// range_utf16 is in terms of UTF-16 characters
 749    /// new_selected_range is in terms of UTF-16 characters
 750    fn replace_and_mark_text_in_range(
 751        &mut self,
 752        range_utf16: Option<Range<usize>>,
 753        new_text: &str,
 754        new_selected_range: Option<Range<usize>>,
 755        cx: &mut WindowContext,
 756    );
 757
 758    /// Remove the IME 'composing' state from the document
 759    /// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
 760    fn unmark_text(&mut self, cx: &mut WindowContext);
 761
 762    /// Get the bounds of the given document range in screen coordinates
 763    /// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
 764    ///
 765    /// This is used for positioning the IME candidate window
 766    fn bounds_for_range(
 767        &mut self,
 768        range_utf16: Range<usize>,
 769        cx: &mut WindowContext,
 770    ) -> Option<Bounds<Pixels>>;
 771}
 772
 773/// The variables that can be configured when creating a new window
 774#[derive(Debug)]
 775pub struct WindowOptions {
 776    /// Specifies the state and bounds of the window in screen coordinates.
 777    /// - `None`: Inherit the bounds.
 778    /// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
 779    pub window_bounds: Option<WindowBounds>,
 780
 781    /// The titlebar configuration of the window
 782    pub titlebar: Option<TitlebarOptions>,
 783
 784    /// Whether the window should be focused when created
 785    pub focus: bool,
 786
 787    /// Whether the window should be shown when created
 788    pub show: bool,
 789
 790    /// The kind of window to create
 791    pub kind: WindowKind,
 792
 793    /// Whether the window should be movable by the user
 794    pub is_movable: bool,
 795
 796    /// The display to create the window on, if this is None,
 797    /// the window will be created on the main display
 798    pub display_id: Option<DisplayId>,
 799
 800    /// The appearance of the window background.
 801    pub window_background: WindowBackgroundAppearance,
 802
 803    /// Application identifier of the window. Can by used by desktop environments to group applications together.
 804    pub app_id: Option<String>,
 805
 806    /// Window minimum size
 807    pub window_min_size: Option<Size<Pixels>>,
 808
 809    /// Whether to use client or server side decorations. Wayland only
 810    /// Note that this may be ignored.
 811    pub window_decorations: Option<WindowDecorations>,
 812}
 813
 814/// The variables that can be configured when creating a new window
 815#[derive(Debug)]
 816#[cfg_attr(
 817    all(target_os = "linux", not(any(feature = "x11", feature = "wayland"))),
 818    allow(dead_code)
 819)]
 820pub(crate) struct WindowParams {
 821    pub bounds: Bounds<Pixels>,
 822
 823    /// The titlebar configuration of the window
 824    #[cfg_attr(feature = "wayland", allow(dead_code))]
 825    pub titlebar: Option<TitlebarOptions>,
 826
 827    /// The kind of window to create
 828    #[cfg_attr(target_os = "linux", allow(dead_code))]
 829    pub kind: WindowKind,
 830
 831    /// Whether the window should be movable by the user
 832    #[cfg_attr(target_os = "linux", allow(dead_code))]
 833    pub is_movable: bool,
 834
 835    #[cfg_attr(target_os = "linux", allow(dead_code))]
 836    pub focus: bool,
 837
 838    #[cfg_attr(target_os = "linux", allow(dead_code))]
 839    pub show: bool,
 840
 841    #[cfg_attr(feature = "wayland", allow(dead_code))]
 842    pub display_id: Option<DisplayId>,
 843
 844    pub window_min_size: Option<Size<Pixels>>,
 845}
 846
 847/// Represents the status of how a window should be opened.
 848#[derive(Debug, Copy, Clone, PartialEq)]
 849pub enum WindowBounds {
 850    /// Indicates that the window should open in a windowed state with the given bounds.
 851    Windowed(Bounds<Pixels>),
 852    /// Indicates that the window should open in a maximized state.
 853    /// The bounds provided here represent the restore size of the window.
 854    Maximized(Bounds<Pixels>),
 855    /// Indicates that the window should open in fullscreen mode.
 856    /// The bounds provided here represent the restore size of the window.
 857    Fullscreen(Bounds<Pixels>),
 858}
 859
 860impl Default for WindowBounds {
 861    fn default() -> Self {
 862        WindowBounds::Windowed(Bounds::default())
 863    }
 864}
 865
 866impl WindowBounds {
 867    /// Retrieve the inner bounds
 868    pub fn get_bounds(&self) -> Bounds<Pixels> {
 869        match self {
 870            WindowBounds::Windowed(bounds) => *bounds,
 871            WindowBounds::Maximized(bounds) => *bounds,
 872            WindowBounds::Fullscreen(bounds) => *bounds,
 873        }
 874    }
 875}
 876
 877impl Default for WindowOptions {
 878    fn default() -> Self {
 879        Self {
 880            window_bounds: None,
 881            titlebar: Some(TitlebarOptions {
 882                title: Default::default(),
 883                appears_transparent: Default::default(),
 884                traffic_light_position: Default::default(),
 885            }),
 886            focus: true,
 887            show: true,
 888            kind: WindowKind::Normal,
 889            is_movable: true,
 890            display_id: None,
 891            window_background: WindowBackgroundAppearance::default(),
 892            app_id: None,
 893            window_min_size: None,
 894            window_decorations: None,
 895        }
 896    }
 897}
 898
 899/// The options that can be configured for a window's titlebar
 900#[derive(Debug, Default)]
 901pub struct TitlebarOptions {
 902    /// The initial title of the window
 903    pub title: Option<SharedString>,
 904
 905    /// Should the default system titlebar be hidden to allow for a custom-drawn titlebar? (macOS and Windows only)
 906    /// Refer to [`WindowOptions::window_decorations`] on Linux
 907    pub appears_transparent: bool,
 908
 909    /// The position of the macOS traffic light buttons
 910    pub traffic_light_position: Option<Point<Pixels>>,
 911}
 912
 913/// The kind of window to create
 914#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 915pub enum WindowKind {
 916    /// A normal application window
 917    Normal,
 918
 919    /// A window that appears above all other windows, usually used for alerts or popups
 920    /// use sparingly!
 921    PopUp,
 922}
 923
 924/// The appearance of the window, as defined by the operating system.
 925///
 926/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
 927/// values.
 928#[derive(Copy, Clone, Debug)]
 929pub enum WindowAppearance {
 930    /// A light appearance.
 931    ///
 932    /// On macOS, this corresponds to the `aqua` appearance.
 933    Light,
 934
 935    /// A light appearance with vibrant colors.
 936    ///
 937    /// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
 938    VibrantLight,
 939
 940    /// A dark appearance.
 941    ///
 942    /// On macOS, this corresponds to the `darkAqua` appearance.
 943    Dark,
 944
 945    /// A dark appearance with vibrant colors.
 946    ///
 947    /// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
 948    VibrantDark,
 949}
 950
 951impl Default for WindowAppearance {
 952    fn default() -> Self {
 953        Self::Light
 954    }
 955}
 956
 957/// The appearance of the background of the window itself, when there is
 958/// no content or the content is transparent.
 959#[derive(Copy, Clone, Debug, Default, PartialEq)]
 960pub enum WindowBackgroundAppearance {
 961    /// Opaque.
 962    ///
 963    /// This lets the window manager know that content behind this
 964    /// window does not need to be drawn.
 965    ///
 966    /// Actual color depends on the system and themes should define a fully
 967    /// opaque background color instead.
 968    #[default]
 969    Opaque,
 970    /// Plain alpha transparency.
 971    Transparent,
 972    /// Transparency, but the contents behind the window are blurred.
 973    ///
 974    /// Not always supported.
 975    Blurred,
 976}
 977
 978/// The options that can be configured for a file dialog prompt
 979#[derive(Copy, Clone, Debug)]
 980pub struct PathPromptOptions {
 981    /// Should the prompt allow files to be selected?
 982    pub files: bool,
 983    /// Should the prompt allow directories to be selected?
 984    pub directories: bool,
 985    /// Should the prompt allow multiple files to be selected?
 986    pub multiple: bool,
 987}
 988
 989/// What kind of prompt styling to show
 990#[derive(Copy, Clone, Debug, PartialEq)]
 991pub enum PromptLevel {
 992    /// A prompt that is shown when the user should be notified of something
 993    Info,
 994
 995    /// A prompt that is shown when the user needs to be warned of a potential problem
 996    Warning,
 997
 998    /// A prompt that is shown when a critical problem has occurred
 999    Critical,
1000}
1001
1002/// The style of the cursor (pointer)
1003#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
1004pub enum CursorStyle {
1005    /// The default cursor
1006    Arrow,
1007
1008    /// A text input cursor
1009    /// corresponds to the CSS cursor value `text`
1010    IBeam,
1011
1012    /// A crosshair cursor
1013    /// corresponds to the CSS cursor value `crosshair`
1014    Crosshair,
1015
1016    /// A closed hand cursor
1017    /// corresponds to the CSS cursor value `grabbing`
1018    ClosedHand,
1019
1020    /// An open hand cursor
1021    /// corresponds to the CSS cursor value `grab`
1022    OpenHand,
1023
1024    /// A pointing hand cursor
1025    /// corresponds to the CSS cursor value `pointer`
1026    PointingHand,
1027
1028    /// A resize left cursor
1029    /// corresponds to the CSS cursor value `w-resize`
1030    ResizeLeft,
1031
1032    /// A resize right cursor
1033    /// corresponds to the CSS cursor value `e-resize`
1034    ResizeRight,
1035
1036    /// A resize cursor to the left and right
1037    /// corresponds to the CSS cursor value `ew-resize`
1038    ResizeLeftRight,
1039
1040    /// A resize up cursor
1041    /// corresponds to the CSS cursor value `n-resize`
1042    ResizeUp,
1043
1044    /// A resize down cursor
1045    /// corresponds to the CSS cursor value `s-resize`
1046    ResizeDown,
1047
1048    /// A resize cursor directing up and down
1049    /// corresponds to the CSS cursor value `ns-resize`
1050    ResizeUpDown,
1051
1052    /// A resize cursor directing up-left and down-right
1053    /// corresponds to the CSS cursor value `nesw-resize`
1054    ResizeUpLeftDownRight,
1055
1056    /// A resize cursor directing up-right and down-left
1057    /// corresponds to the CSS cursor value `nwse-resize`
1058    ResizeUpRightDownLeft,
1059
1060    /// A cursor indicating that the item/column can be resized horizontally.
1061    /// corresponds to the CSS cursor value `col-resize`
1062    ResizeColumn,
1063
1064    /// A cursor indicating that the item/row can be resized vertically.
1065    /// corresponds to the CSS cursor value `row-resize`
1066    ResizeRow,
1067
1068    /// A text input cursor for vertical layout
1069    /// corresponds to the CSS cursor value `vertical-text`
1070    IBeamCursorForVerticalLayout,
1071
1072    /// A cursor indicating that the operation is not allowed
1073    /// corresponds to the CSS cursor value `not-allowed`
1074    OperationNotAllowed,
1075
1076    /// A cursor indicating that the operation will result in a link
1077    /// corresponds to the CSS cursor value `alias`
1078    DragLink,
1079
1080    /// A cursor indicating that the operation will result in a copy
1081    /// corresponds to the CSS cursor value `copy`
1082    DragCopy,
1083
1084    /// A cursor indicating that the operation will result in a context menu
1085    /// corresponds to the CSS cursor value `context-menu`
1086    ContextualMenu,
1087}
1088
1089impl Default for CursorStyle {
1090    fn default() -> Self {
1091        Self::Arrow
1092    }
1093}
1094
1095/// A clipboard item that should be copied to the clipboard
1096#[derive(Clone, Debug, Eq, PartialEq)]
1097pub struct ClipboardItem {
1098    entries: Vec<ClipboardEntry>,
1099}
1100
1101/// Either a ClipboardString or a ClipboardImage
1102#[derive(Clone, Debug, Eq, PartialEq)]
1103pub enum ClipboardEntry {
1104    /// A string entry
1105    String(ClipboardString),
1106    /// An image entry
1107    Image(Image),
1108}
1109
1110impl ClipboardItem {
1111    /// Create a new ClipboardItem::String with no associated metadata
1112    pub fn new_string(text: String) -> Self {
1113        Self {
1114            entries: vec![ClipboardEntry::String(ClipboardString::new(text))],
1115        }
1116    }
1117
1118    /// Create a new ClipboardItem::String with the given text and associated metadata
1119    pub fn new_string_with_metadata(text: String, metadata: String) -> Self {
1120        Self {
1121            entries: vec![ClipboardEntry::String(ClipboardString {
1122                text,
1123                metadata: Some(metadata),
1124            })],
1125        }
1126    }
1127
1128    /// Create a new ClipboardItem::String with the given text and associated metadata
1129    pub fn new_string_with_json_metadata<T: Serialize>(text: String, metadata: T) -> Self {
1130        Self {
1131            entries: vec![ClipboardEntry::String(
1132                ClipboardString::new(text).with_json_metadata(metadata),
1133            )],
1134        }
1135    }
1136
1137    /// Create a new ClipboardItem::Image with the given image with no associated metadata
1138    pub fn new_image(image: &Image) -> Self {
1139        Self {
1140            entries: vec![ClipboardEntry::Image(image.clone())],
1141        }
1142    }
1143
1144    /// Concatenates together all the ClipboardString entries in the item.
1145    /// Returns None if there were no ClipboardString entries.
1146    pub fn text(&self) -> Option<String> {
1147        let mut answer = String::new();
1148        let mut any_entries = false;
1149
1150        for entry in self.entries.iter() {
1151            if let ClipboardEntry::String(ClipboardString { text, metadata: _ }) = entry {
1152                answer.push_str(text);
1153                any_entries = true;
1154            }
1155        }
1156
1157        if any_entries {
1158            Some(answer)
1159        } else {
1160            None
1161        }
1162    }
1163
1164    /// If this item is one ClipboardEntry::String, returns its metadata.
1165    #[cfg_attr(not(target_os = "windows"), allow(dead_code))]
1166    pub fn metadata(&self) -> Option<&String> {
1167        match self.entries().first() {
1168            Some(ClipboardEntry::String(clipboard_string)) if self.entries.len() == 1 => {
1169                clipboard_string.metadata.as_ref()
1170            }
1171            _ => None,
1172        }
1173    }
1174
1175    /// Get the item's entries
1176    pub fn entries(&self) -> &[ClipboardEntry] {
1177        &self.entries
1178    }
1179
1180    /// Get owned versions of the item's entries
1181    pub fn into_entries(self) -> impl Iterator<Item = ClipboardEntry> {
1182        self.entries.into_iter()
1183    }
1184}
1185
1186/// One of the editor's supported image formats (e.g. PNG, JPEG) - used when dealing with images in the clipboard
1187#[derive(Clone, Copy, Debug, Eq, PartialEq, EnumIter, Hash)]
1188pub enum ImageFormat {
1189    // Sorted from most to least likely to be pasted into an editor,
1190    // which matters when we iterate through them trying to see if
1191    // clipboard content matches them.
1192    /// .png
1193    Png,
1194    /// .jpeg or .jpg
1195    Jpeg,
1196    /// .webp
1197    Webp,
1198    /// .gif
1199    Gif,
1200    /// .svg
1201    Svg,
1202    /// .bmp
1203    Bmp,
1204    /// .tif or .tiff
1205    Tiff,
1206}
1207
1208/// An image, with a format and certain bytes
1209#[derive(Clone, Debug, PartialEq, Eq)]
1210pub struct Image {
1211    /// The image format the bytes represent (e.g. PNG)
1212    pub format: ImageFormat,
1213    /// The raw image bytes
1214    pub bytes: Vec<u8>,
1215    /// The unique ID for the image
1216    pub id: u64,
1217}
1218
1219impl Hash for Image {
1220    fn hash<H: Hasher>(&self, state: &mut H) {
1221        state.write_u64(self.id);
1222    }
1223}
1224
1225impl Image {
1226    /// Get this image's ID
1227    pub fn id(&self) -> u64 {
1228        self.id
1229    }
1230
1231    /// Use the GPUI `use_asset` API to make this image renderable
1232    pub fn use_render_image(self: Arc<Self>, cx: &mut WindowContext) -> Option<Arc<RenderImage>> {
1233        ImageSource::Image(self).use_data(cx)
1234    }
1235
1236    /// Convert the clipboard image to an `ImageData` object.
1237    pub fn to_image_data(&self, cx: &AppContext) -> Result<Arc<RenderImage>> {
1238        fn frames_for_image(
1239            bytes: &[u8],
1240            format: image::ImageFormat,
1241        ) -> Result<SmallVec<[Frame; 1]>> {
1242            let mut data = image::load_from_memory_with_format(bytes, format)?.into_rgba8();
1243
1244            // Convert from RGBA to BGRA.
1245            for pixel in data.chunks_exact_mut(4) {
1246                pixel.swap(0, 2);
1247            }
1248
1249            Ok(SmallVec::from_elem(Frame::new(data), 1))
1250        }
1251
1252        let frames = match self.format {
1253            ImageFormat::Gif => {
1254                let decoder = GifDecoder::new(Cursor::new(&self.bytes))?;
1255                let mut frames = SmallVec::new();
1256
1257                for frame in decoder.into_frames() {
1258                    let mut frame = frame?;
1259                    // Convert from RGBA to BGRA.
1260                    for pixel in frame.buffer_mut().chunks_exact_mut(4) {
1261                        pixel.swap(0, 2);
1262                    }
1263                    frames.push(frame);
1264                }
1265
1266                frames
1267            }
1268            ImageFormat::Png => frames_for_image(&self.bytes, image::ImageFormat::Png)?,
1269            ImageFormat::Jpeg => frames_for_image(&self.bytes, image::ImageFormat::Jpeg)?,
1270            ImageFormat::Webp => frames_for_image(&self.bytes, image::ImageFormat::WebP)?,
1271            ImageFormat::Bmp => frames_for_image(&self.bytes, image::ImageFormat::Bmp)?,
1272            ImageFormat::Tiff => frames_for_image(&self.bytes, image::ImageFormat::Tiff)?,
1273            ImageFormat::Svg => {
1274                // TODO: Fix this
1275                let pixmap = cx
1276                    .svg_renderer()
1277                    .render_pixmap(&self.bytes, SvgSize::ScaleFactor(1.0))?;
1278
1279                let buffer =
1280                    image::ImageBuffer::from_raw(pixmap.width(), pixmap.height(), pixmap.take())
1281                        .unwrap();
1282
1283                SmallVec::from_elem(Frame::new(buffer), 1)
1284            }
1285        };
1286
1287        Ok(Arc::new(RenderImage::new(frames)))
1288    }
1289
1290    /// Get the format of the clipboard image
1291    pub fn format(&self) -> ImageFormat {
1292        self.format
1293    }
1294
1295    /// Get the raw bytes of the clipboard image
1296    pub fn bytes(&self) -> &[u8] {
1297        self.bytes.as_slice()
1298    }
1299}
1300
1301/// A clipboard item that should be copied to the clipboard
1302#[derive(Clone, Debug, Eq, PartialEq)]
1303pub struct ClipboardString {
1304    pub(crate) text: String,
1305    pub(crate) metadata: Option<String>,
1306}
1307
1308impl ClipboardString {
1309    /// Create a new clipboard string with the given text
1310    pub fn new(text: String) -> Self {
1311        Self {
1312            text,
1313            metadata: None,
1314        }
1315    }
1316
1317    /// Return a new clipboard item with the metadata replaced by the given metadata,
1318    /// after serializing it as JSON.
1319    pub fn with_json_metadata<T: Serialize>(mut self, metadata: T) -> Self {
1320        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
1321        self
1322    }
1323
1324    /// Get the text of the clipboard string
1325    pub fn text(&self) -> &String {
1326        &self.text
1327    }
1328
1329    /// Get the owned text of the clipboard string
1330    pub fn into_text(self) -> String {
1331        self.text
1332    }
1333
1334    /// Get the metadata of the clipboard string, formatted as JSON
1335    pub fn metadata_json<T>(&self) -> Option<T>
1336    where
1337        T: for<'a> Deserialize<'a>,
1338    {
1339        self.metadata
1340            .as_ref()
1341            .and_then(|m| serde_json::from_str(m).ok())
1342    }
1343
1344    #[cfg_attr(target_os = "linux", allow(dead_code))]
1345    pub(crate) fn text_hash(text: &str) -> u64 {
1346        let mut hasher = SeaHasher::new();
1347        text.hash(&mut hasher);
1348        hasher.finish()
1349    }
1350}