This changes the workspace/session serialization to also persist the order of windows across restarts. Release Notes: - Improved restoring of windows across restarts: the order of the windows is now also restored. That means windows that were in the foreground when Zed was quit will be in the foreground after restart. (Right now only supported on Linux/X11, not on Linux/Wayland.) Demo: https://github.com/user-attachments/assets/0b8162f8-f06d-43df-88d3-c45d8460fb68
1013 lines
31 KiB
Rust
1013 lines
31 KiB
Rust
// todo(windows): remove
|
|
#![cfg_attr(windows, allow(dead_code))]
|
|
|
|
mod app_menu;
|
|
mod keystroke;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod linux;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod mac;
|
|
|
|
#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))]
|
|
mod blade;
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
mod test;
|
|
|
|
#[cfg(target_os = "windows")]
|
|
mod windows;
|
|
|
|
use crate::{
|
|
point, Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels,
|
|
DispatchEventResult, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GPUSpecs, GlyphId,
|
|
Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams,
|
|
RenderSvgParams, Scene, SharedString, Size, Task, TaskLabel, WindowContext,
|
|
DEFAULT_WINDOW_SIZE,
|
|
};
|
|
use anyhow::Result;
|
|
use async_task::Runnable;
|
|
use futures::channel::oneshot;
|
|
use parking::Unparker;
|
|
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
|
use seahash::SeaHasher;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::borrow::Cow;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::time::{Duration, Instant};
|
|
use std::{
|
|
fmt::{self, Debug},
|
|
ops::Range,
|
|
path::{Path, PathBuf},
|
|
rc::Rc,
|
|
sync::Arc,
|
|
};
|
|
use uuid::Uuid;
|
|
|
|
pub use app_menu::*;
|
|
pub use keystroke::*;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub(crate) use linux::*;
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) use mac::*;
|
|
pub use semantic_version::SemanticVersion;
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
pub(crate) use test::*;
|
|
#[cfg(target_os = "windows")]
|
|
pub(crate) use windows::*;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
|
|
Rc::new(MacPlatform::new(headless))
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub(crate) fn current_platform(headless: bool) -> Rc<dyn Platform> {
|
|
if headless {
|
|
return Rc::new(HeadlessClient::new());
|
|
}
|
|
|
|
match guess_compositor() {
|
|
"Wayland" => Rc::new(WaylandClient::new()),
|
|
"X11" => Rc::new(X11Client::new()),
|
|
"Headless" => Rc::new(HeadlessClient::new()),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
/// Return which compositor we're guessing we'll use.
|
|
/// Does not attempt to connect to the given compositor
|
|
#[cfg(target_os = "linux")]
|
|
#[inline]
|
|
pub fn guess_compositor() -> &'static str {
|
|
if std::env::var_os("ZED_HEADLESS").is_some() {
|
|
return "Headless";
|
|
}
|
|
let wayland_display = std::env::var_os("WAYLAND_DISPLAY");
|
|
let x11_display = std::env::var_os("DISPLAY");
|
|
|
|
let use_wayland = wayland_display.is_some_and(|display| !display.is_empty());
|
|
let use_x11 = x11_display.is_some_and(|display| !display.is_empty());
|
|
|
|
if use_wayland {
|
|
"Wayland"
|
|
} else if use_x11 {
|
|
"X11"
|
|
} else {
|
|
"Headless"
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub(crate) fn current_platform(_headless: bool) -> Rc<dyn Platform> {
|
|
Rc::new(WindowsPlatform::new())
|
|
}
|
|
|
|
pub(crate) trait Platform: 'static {
|
|
fn background_executor(&self) -> BackgroundExecutor;
|
|
fn foreground_executor(&self) -> ForegroundExecutor;
|
|
fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
|
|
|
|
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>);
|
|
fn quit(&self);
|
|
fn restart(&self, binary_path: Option<PathBuf>);
|
|
fn activate(&self, ignoring_other_apps: bool);
|
|
fn hide(&self);
|
|
fn hide_other_apps(&self);
|
|
fn unhide_other_apps(&self);
|
|
|
|
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
|
|
fn primary_display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
|
fn active_window(&self) -> Option<AnyWindowHandle>;
|
|
fn window_stack(&self) -> Option<Vec<AnyWindowHandle>> {
|
|
None
|
|
}
|
|
|
|
fn open_window(
|
|
&self,
|
|
handle: AnyWindowHandle,
|
|
options: WindowParams,
|
|
) -> anyhow::Result<Box<dyn PlatformWindow>>;
|
|
|
|
/// Returns the appearance of the application's windows.
|
|
fn window_appearance(&self) -> WindowAppearance;
|
|
|
|
fn open_url(&self, url: &str);
|
|
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
|
|
fn register_url_scheme(&self, url: &str) -> Task<Result<()>>;
|
|
|
|
fn prompt_for_paths(
|
|
&self,
|
|
options: PathPromptOptions,
|
|
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
|
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
|
fn reveal_path(&self, path: &Path);
|
|
|
|
fn on_quit(&self, callback: Box<dyn FnMut()>);
|
|
fn on_reopen(&self, callback: Box<dyn FnMut()>);
|
|
|
|
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap);
|
|
fn get_menus(&self) -> Option<Vec<OwnedMenu>> {
|
|
None
|
|
}
|
|
|
|
fn set_dock_menu(&self, menu: Vec<MenuItem>, keymap: &Keymap);
|
|
fn add_recent_document(&self, _path: &Path) {}
|
|
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>);
|
|
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>);
|
|
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>);
|
|
|
|
fn compositor_name(&self) -> &'static str {
|
|
""
|
|
}
|
|
fn app_path(&self) -> Result<PathBuf>;
|
|
fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
|
|
|
|
fn set_cursor_style(&self, style: CursorStyle);
|
|
fn should_auto_hide_scrollbars(&self) -> bool;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn write_to_primary(&self, item: ClipboardItem);
|
|
fn write_to_clipboard(&self, item: ClipboardItem);
|
|
#[cfg(target_os = "linux")]
|
|
fn read_from_primary(&self) -> Option<ClipboardItem>;
|
|
fn read_from_clipboard(&self) -> Option<ClipboardItem>;
|
|
|
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>>;
|
|
fn read_credentials(&self, url: &str) -> Task<Result<Option<(String, Vec<u8>)>>>;
|
|
fn delete_credentials(&self, url: &str) -> Task<Result<()>>;
|
|
}
|
|
|
|
/// A handle to a platform's display, e.g. a monitor or laptop screen.
|
|
pub trait PlatformDisplay: Send + Sync + Debug {
|
|
/// Get the ID for this display
|
|
fn id(&self) -> DisplayId;
|
|
|
|
/// Returns a stable identifier for this display that can be persisted and used
|
|
/// across system restarts.
|
|
fn uuid(&self) -> Result<Uuid>;
|
|
|
|
/// Get the bounds for this display
|
|
fn bounds(&self) -> Bounds<Pixels>;
|
|
|
|
/// Get the default bounds for this display to place a window
|
|
fn default_bounds(&self) -> Bounds<Pixels> {
|
|
let center = self.bounds().center();
|
|
let offset = DEFAULT_WINDOW_SIZE / 2.0;
|
|
let origin = point(center.x - offset.width, center.y - offset.height);
|
|
Bounds::new(origin, DEFAULT_WINDOW_SIZE)
|
|
}
|
|
}
|
|
|
|
/// An opaque identifier for a hardware display
|
|
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
|
|
pub struct DisplayId(pub(crate) u32);
|
|
|
|
impl Debug for DisplayId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "DisplayId({})", self.0)
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for DisplayId {}
|
|
|
|
/// Which part of the window to resize
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ResizeEdge {
|
|
/// The top edge
|
|
Top,
|
|
/// The top right corner
|
|
TopRight,
|
|
/// The right edge
|
|
Right,
|
|
/// The bottom right corner
|
|
BottomRight,
|
|
/// The bottom edge
|
|
Bottom,
|
|
/// The bottom left corner
|
|
BottomLeft,
|
|
/// The left edge
|
|
Left,
|
|
/// The top left corner
|
|
TopLeft,
|
|
}
|
|
|
|
/// A type to describe the appearance of a window
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
|
pub enum WindowDecorations {
|
|
#[default]
|
|
/// Server side decorations
|
|
Server,
|
|
/// Client side decorations
|
|
Client,
|
|
}
|
|
|
|
/// A type to describe how this window is currently configured
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
|
pub enum Decorations {
|
|
/// The window is configured to use server side decorations
|
|
#[default]
|
|
Server,
|
|
/// The window is configured to use client side decorations
|
|
Client {
|
|
/// The edge tiling state
|
|
tiling: Tiling,
|
|
},
|
|
}
|
|
|
|
/// What window controls this platform supports
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub struct WindowControls {
|
|
/// Whether this platform supports fullscreen
|
|
pub fullscreen: bool,
|
|
/// Whether this platform supports maximize
|
|
pub maximize: bool,
|
|
/// Whether this platform supports minimize
|
|
pub minimize: bool,
|
|
/// Whether this platform supports a window menu
|
|
pub window_menu: bool,
|
|
}
|
|
|
|
impl Default for WindowControls {
|
|
fn default() -> Self {
|
|
// Assume that we can do anything, unless told otherwise
|
|
Self {
|
|
fullscreen: true,
|
|
maximize: true,
|
|
minimize: true,
|
|
window_menu: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A type to describe which sides of the window are currently tiled in some way
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
|
pub struct Tiling {
|
|
/// Whether the top edge is tiled
|
|
pub top: bool,
|
|
/// Whether the left edge is tiled
|
|
pub left: bool,
|
|
/// Whether the right edge is tiled
|
|
pub right: bool,
|
|
/// Whether the bottom edge is tiled
|
|
pub bottom: bool,
|
|
}
|
|
|
|
impl Tiling {
|
|
/// Initializes a [`Tiling`] type with all sides tiled
|
|
pub fn tiled() -> Self {
|
|
Self {
|
|
top: true,
|
|
left: true,
|
|
right: true,
|
|
bottom: true,
|
|
}
|
|
}
|
|
|
|
/// Whether any edge is tiled
|
|
pub fn is_tiled(&self) -> bool {
|
|
self.top || self.left || self.right || self.bottom
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
|
fn bounds(&self) -> Bounds<Pixels>;
|
|
fn is_maximized(&self) -> bool;
|
|
fn window_bounds(&self) -> WindowBounds;
|
|
fn content_size(&self) -> Size<Pixels>;
|
|
fn scale_factor(&self) -> f32;
|
|
fn appearance(&self) -> WindowAppearance;
|
|
fn display(&self) -> Option<Rc<dyn PlatformDisplay>>;
|
|
fn mouse_position(&self) -> Point<Pixels>;
|
|
fn modifiers(&self) -> Modifiers;
|
|
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
|
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
|
fn prompt(
|
|
&self,
|
|
level: PromptLevel,
|
|
msg: &str,
|
|
detail: Option<&str>,
|
|
answers: &[&str],
|
|
) -> Option<oneshot::Receiver<usize>>;
|
|
fn activate(&self);
|
|
fn is_active(&self) -> bool;
|
|
fn is_hovered(&self) -> bool;
|
|
fn set_title(&mut self, title: &str);
|
|
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
|
|
fn minimize(&self);
|
|
fn zoom(&self);
|
|
fn toggle_fullscreen(&self);
|
|
fn is_fullscreen(&self) -> bool;
|
|
fn on_request_frame(&self, callback: Box<dyn FnMut()>);
|
|
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>);
|
|
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
|
fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>);
|
|
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
|
fn on_moved(&self, callback: Box<dyn FnMut()>);
|
|
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
|
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
|
fn draw(&self, scene: &Scene);
|
|
fn completed_frame(&self) {}
|
|
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
|
|
|
|
// macOS specific methods
|
|
fn set_edited(&mut self, _edited: bool) {}
|
|
fn show_character_palette(&self) {}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
fn get_raw_handle(&self) -> windows::HWND;
|
|
|
|
// Linux specific methods
|
|
fn request_decorations(&self, _decorations: WindowDecorations) {}
|
|
fn show_window_menu(&self, _position: Point<Pixels>) {}
|
|
fn start_window_move(&self) {}
|
|
fn start_window_resize(&self, _edge: ResizeEdge) {}
|
|
fn window_decorations(&self) -> Decorations {
|
|
Decorations::Server
|
|
}
|
|
fn set_app_id(&mut self, _app_id: &str) {}
|
|
fn window_controls(&self) -> WindowControls {
|
|
WindowControls::default()
|
|
}
|
|
fn set_client_inset(&self, _inset: Pixels) {}
|
|
fn gpu_specs(&self) -> Option<GPUSpecs>;
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fn as_test(&mut self) -> Option<&mut TestWindow> {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// This type is public so that our test macro can generate and use it, but it should not
|
|
/// be considered part of our public API.
|
|
#[doc(hidden)]
|
|
pub trait PlatformDispatcher: Send + Sync {
|
|
fn is_main_thread(&self) -> bool;
|
|
fn dispatch(&self, runnable: Runnable, label: Option<TaskLabel>);
|
|
fn dispatch_on_main_thread(&self, runnable: Runnable);
|
|
fn dispatch_after(&self, duration: Duration, runnable: Runnable);
|
|
fn park(&self, timeout: Option<Duration>) -> bool;
|
|
fn unparker(&self) -> Unparker;
|
|
fn now(&self) -> Instant {
|
|
Instant::now()
|
|
}
|
|
|
|
#[cfg(any(test, feature = "test-support"))]
|
|
fn as_test(&self) -> Option<&TestDispatcher> {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PlatformTextSystem: Send + Sync {
|
|
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
|
|
fn all_font_names(&self) -> Vec<String>;
|
|
fn all_font_families(&self) -> Vec<String>;
|
|
fn font_id(&self, descriptor: &Font) -> Result<FontId>;
|
|
fn font_metrics(&self, font_id: FontId) -> FontMetrics;
|
|
fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
|
|
fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
|
|
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
|
|
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
|
|
fn rasterize_glyph(
|
|
&self,
|
|
params: &RenderGlyphParams,
|
|
raster_bounds: Bounds<DevicePixels>,
|
|
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
|
pub(crate) enum AtlasKey {
|
|
Glyph(RenderGlyphParams),
|
|
Svg(RenderSvgParams),
|
|
Image(RenderImageParams),
|
|
}
|
|
|
|
impl AtlasKey {
|
|
pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
|
|
match self {
|
|
AtlasKey::Glyph(params) => {
|
|
if params.is_emoji {
|
|
AtlasTextureKind::Polychrome
|
|
} else {
|
|
AtlasTextureKind::Monochrome
|
|
}
|
|
}
|
|
AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
|
|
AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RenderGlyphParams> for AtlasKey {
|
|
fn from(params: RenderGlyphParams) -> Self {
|
|
Self::Glyph(params)
|
|
}
|
|
}
|
|
|
|
impl From<RenderSvgParams> for AtlasKey {
|
|
fn from(params: RenderSvgParams) -> Self {
|
|
Self::Svg(params)
|
|
}
|
|
}
|
|
|
|
impl From<RenderImageParams> for AtlasKey {
|
|
fn from(params: RenderImageParams) -> Self {
|
|
Self::Image(params)
|
|
}
|
|
}
|
|
|
|
pub(crate) trait PlatformAtlas: Send + Sync {
|
|
fn get_or_insert_with<'a>(
|
|
&self,
|
|
key: &AtlasKey,
|
|
build: &mut dyn FnMut() -> Result<Option<(Size<DevicePixels>, Cow<'a, [u8]>)>>,
|
|
) -> Result<Option<AtlasTile>>;
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
#[repr(C)]
|
|
pub(crate) struct AtlasTile {
|
|
pub(crate) texture_id: AtlasTextureId,
|
|
pub(crate) tile_id: TileId,
|
|
pub(crate) padding: u32,
|
|
pub(crate) bounds: Bounds<DevicePixels>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[repr(C)]
|
|
pub(crate) struct AtlasTextureId {
|
|
// We use u32 instead of usize for Metal Shader Language compatibility
|
|
pub(crate) index: u32,
|
|
pub(crate) kind: AtlasTextureKind,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
#[repr(C)]
|
|
pub(crate) enum AtlasTextureKind {
|
|
Monochrome = 0,
|
|
Polychrome = 1,
|
|
Path = 2,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
#[repr(C)]
|
|
pub(crate) struct TileId(pub(crate) u32);
|
|
|
|
impl From<etagere::AllocId> for TileId {
|
|
fn from(id: etagere::AllocId) -> Self {
|
|
Self(id.serialize())
|
|
}
|
|
}
|
|
|
|
impl From<TileId> for etagere::AllocId {
|
|
fn from(id: TileId) -> Self {
|
|
Self::deserialize(id.0)
|
|
}
|
|
}
|
|
|
|
pub(crate) struct PlatformInputHandler {
|
|
cx: AsyncWindowContext,
|
|
handler: Box<dyn InputHandler>,
|
|
}
|
|
|
|
impl PlatformInputHandler {
|
|
pub fn new(cx: AsyncWindowContext, handler: Box<dyn InputHandler>) -> Self {
|
|
Self { cx, handler }
|
|
}
|
|
|
|
fn selected_text_range(&mut self) -> Option<Range<usize>> {
|
|
self.cx
|
|
.update(|cx| self.handler.selected_text_range(cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
fn marked_text_range(&mut self) -> Option<Range<usize>> {
|
|
self.cx
|
|
.update(|cx| self.handler.marked_text_range(cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
fn text_for_range(&mut self, range_utf16: Range<usize>) -> Option<String> {
|
|
self.cx
|
|
.update(|cx| self.handler.text_for_range(range_utf16, cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str) {
|
|
self.cx
|
|
.update(|cx| {
|
|
self.handler
|
|
.replace_text_in_range(replacement_range, text, cx);
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range_utf16: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
) {
|
|
self.cx
|
|
.update(|cx| {
|
|
self.handler.replace_and_mark_text_in_range(
|
|
range_utf16,
|
|
new_text,
|
|
new_selected_range,
|
|
cx,
|
|
)
|
|
})
|
|
.ok();
|
|
}
|
|
|
|
fn unmark_text(&mut self) {
|
|
self.cx.update(|cx| self.handler.unmark_text(cx)).ok();
|
|
}
|
|
|
|
fn bounds_for_range(&mut self, range_utf16: Range<usize>) -> Option<Bounds<Pixels>> {
|
|
self.cx
|
|
.update(|cx| self.handler.bounds_for_range(range_utf16, cx))
|
|
.ok()
|
|
.flatten()
|
|
}
|
|
|
|
pub(crate) fn dispatch_input(&mut self, input: &str, cx: &mut WindowContext) {
|
|
self.handler.replace_text_in_range(None, input, cx);
|
|
}
|
|
}
|
|
|
|
/// Zed's interface for handling text input from the platform's IME system
|
|
/// This is currently a 1:1 exposure of the NSTextInputClient API:
|
|
///
|
|
/// <https://developer.apple.com/documentation/appkit/nstextinputclient>
|
|
pub trait InputHandler: 'static {
|
|
/// Get the range of the user's currently selected text, if any
|
|
/// Corresponds to [selectedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438242-selectedrange)
|
|
///
|
|
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
|
fn selected_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
|
|
|
/// Get the range of the currently marked text, if any
|
|
/// Corresponds to [markedRange()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438250-markedrange)
|
|
///
|
|
/// Return value is in terms of UTF-16 characters, from 0 to the length of the document
|
|
fn marked_text_range(&mut self, cx: &mut WindowContext) -> Option<Range<usize>>;
|
|
|
|
/// Get the text for the given document range in UTF-16 characters
|
|
/// Corresponds to [attributedSubstring(forProposedRange: actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438238-attributedsubstring)
|
|
///
|
|
/// range_utf16 is in terms of UTF-16 characters
|
|
fn text_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
cx: &mut WindowContext,
|
|
) -> Option<String>;
|
|
|
|
/// Replace the text in the given document range with the given text
|
|
/// Corresponds to [insertText(_:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438258-inserttext)
|
|
///
|
|
/// replacement_range is in terms of UTF-16 characters
|
|
fn replace_text_in_range(
|
|
&mut self,
|
|
replacement_range: Option<Range<usize>>,
|
|
text: &str,
|
|
cx: &mut WindowContext,
|
|
);
|
|
|
|
/// Replace the text in the given document range with the given text,
|
|
/// and mark the given text as part of of an IME 'composing' state
|
|
/// Corresponds to [setMarkedText(_:selectedRange:replacementRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438246-setmarkedtext)
|
|
///
|
|
/// range_utf16 is in terms of UTF-16 characters
|
|
/// new_selected_range is in terms of UTF-16 characters
|
|
fn replace_and_mark_text_in_range(
|
|
&mut self,
|
|
range_utf16: Option<Range<usize>>,
|
|
new_text: &str,
|
|
new_selected_range: Option<Range<usize>>,
|
|
cx: &mut WindowContext,
|
|
);
|
|
|
|
/// Remove the IME 'composing' state from the document
|
|
/// Corresponds to [unmarkText()](https://developer.apple.com/documentation/appkit/nstextinputclient/1438239-unmarktext)
|
|
fn unmark_text(&mut self, cx: &mut WindowContext);
|
|
|
|
/// Get the bounds of the given document range in screen coordinates
|
|
/// Corresponds to [firstRect(forCharacterRange:actualRange:)](https://developer.apple.com/documentation/appkit/nstextinputclient/1438240-firstrect)
|
|
///
|
|
/// This is used for positioning the IME candidate window
|
|
fn bounds_for_range(
|
|
&mut self,
|
|
range_utf16: Range<usize>,
|
|
cx: &mut WindowContext,
|
|
) -> Option<Bounds<Pixels>>;
|
|
}
|
|
|
|
/// The variables that can be configured when creating a new window
|
|
#[derive(Debug)]
|
|
pub struct WindowOptions {
|
|
/// Specifies the state and bounds of the window in screen coordinates.
|
|
/// - `None`: Inherit the bounds.
|
|
/// - `Some(WindowBounds)`: Open a window with corresponding state and its restore size.
|
|
pub window_bounds: Option<WindowBounds>,
|
|
|
|
/// The titlebar configuration of the window
|
|
pub titlebar: Option<TitlebarOptions>,
|
|
|
|
/// Whether the window should be focused when created
|
|
pub focus: bool,
|
|
|
|
/// Whether the window should be shown when created
|
|
pub show: bool,
|
|
|
|
/// The kind of window to create
|
|
pub kind: WindowKind,
|
|
|
|
/// Whether the window should be movable by the user
|
|
pub is_movable: bool,
|
|
|
|
/// The display to create the window on, if this is None,
|
|
/// the window will be created on the main display
|
|
pub display_id: Option<DisplayId>,
|
|
|
|
/// The appearance of the window background.
|
|
pub window_background: WindowBackgroundAppearance,
|
|
|
|
/// Application identifier of the window. Can by used by desktop environments to group applications together.
|
|
pub app_id: Option<String>,
|
|
|
|
/// Window minimum size
|
|
pub window_min_size: Option<Size<Pixels>>,
|
|
|
|
/// Whether to use client or server side decorations. Wayland only
|
|
/// Note that this may be ignored.
|
|
pub window_decorations: Option<WindowDecorations>,
|
|
}
|
|
|
|
/// The variables that can be configured when creating a new window
|
|
#[derive(Debug)]
|
|
pub(crate) struct WindowParams {
|
|
pub bounds: Bounds<Pixels>,
|
|
|
|
/// The titlebar configuration of the window
|
|
pub titlebar: Option<TitlebarOptions>,
|
|
|
|
/// The kind of window to create
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
pub kind: WindowKind,
|
|
|
|
/// Whether the window should be movable by the user
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
pub is_movable: bool,
|
|
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
pub focus: bool,
|
|
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
pub show: bool,
|
|
|
|
pub display_id: Option<DisplayId>,
|
|
|
|
pub window_min_size: Option<Size<Pixels>>,
|
|
}
|
|
|
|
/// Represents the status of how a window should be opened.
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
pub enum WindowBounds {
|
|
/// Indicates that the window should open in a windowed state with the given bounds.
|
|
Windowed(Bounds<Pixels>),
|
|
/// Indicates that the window should open in a maximized state.
|
|
/// The bounds provided here represent the restore size of the window.
|
|
Maximized(Bounds<Pixels>),
|
|
/// Indicates that the window should open in fullscreen mode.
|
|
/// The bounds provided here represent the restore size of the window.
|
|
Fullscreen(Bounds<Pixels>),
|
|
}
|
|
|
|
impl Default for WindowBounds {
|
|
fn default() -> Self {
|
|
WindowBounds::Windowed(Bounds::default())
|
|
}
|
|
}
|
|
|
|
impl WindowBounds {
|
|
/// Retrieve the inner bounds
|
|
pub fn get_bounds(&self) -> Bounds<Pixels> {
|
|
match self {
|
|
WindowBounds::Windowed(bounds) => *bounds,
|
|
WindowBounds::Maximized(bounds) => *bounds,
|
|
WindowBounds::Fullscreen(bounds) => *bounds,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for WindowOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
window_bounds: None,
|
|
titlebar: Some(TitlebarOptions {
|
|
title: Default::default(),
|
|
appears_transparent: Default::default(),
|
|
traffic_light_position: Default::default(),
|
|
}),
|
|
focus: true,
|
|
show: true,
|
|
kind: WindowKind::Normal,
|
|
is_movable: true,
|
|
display_id: None,
|
|
window_background: WindowBackgroundAppearance::default(),
|
|
app_id: None,
|
|
window_min_size: None,
|
|
window_decorations: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The options that can be configured for a window's titlebar
|
|
#[derive(Debug, Default)]
|
|
pub struct TitlebarOptions {
|
|
/// The initial title of the window
|
|
pub title: Option<SharedString>,
|
|
|
|
/// Whether the titlebar should appear transparent (macOS only)
|
|
pub appears_transparent: bool,
|
|
|
|
/// The position of the macOS traffic light buttons
|
|
pub traffic_light_position: Option<Point<Pixels>>,
|
|
}
|
|
|
|
/// The kind of window to create
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
pub enum WindowKind {
|
|
/// A normal application window
|
|
Normal,
|
|
|
|
/// A window that appears above all other windows, usually used for alerts or popups
|
|
/// use sparingly!
|
|
PopUp,
|
|
}
|
|
|
|
/// The appearance of the window, as defined by the operating system.
|
|
///
|
|
/// On macOS, this corresponds to named [`NSAppearance`](https://developer.apple.com/documentation/appkit/nsappearance)
|
|
/// values.
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum WindowAppearance {
|
|
/// A light appearance.
|
|
///
|
|
/// On macOS, this corresponds to the `aqua` appearance.
|
|
Light,
|
|
|
|
/// A light appearance with vibrant colors.
|
|
///
|
|
/// On macOS, this corresponds to the `NSAppearanceNameVibrantLight` appearance.
|
|
VibrantLight,
|
|
|
|
/// A dark appearance.
|
|
///
|
|
/// On macOS, this corresponds to the `darkAqua` appearance.
|
|
Dark,
|
|
|
|
/// A dark appearance with vibrant colors.
|
|
///
|
|
/// On macOS, this corresponds to the `NSAppearanceNameVibrantDark` appearance.
|
|
VibrantDark,
|
|
}
|
|
|
|
impl Default for WindowAppearance {
|
|
fn default() -> Self {
|
|
Self::Light
|
|
}
|
|
}
|
|
|
|
/// The appearance of the background of the window itself, when there is
|
|
/// no content or the content is transparent.
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
|
pub enum WindowBackgroundAppearance {
|
|
/// Opaque.
|
|
///
|
|
/// This lets the window manager know that content behind this
|
|
/// window does not need to be drawn.
|
|
///
|
|
/// Actual color depends on the system and themes should define a fully
|
|
/// opaque background color instead.
|
|
#[default]
|
|
Opaque,
|
|
/// Plain alpha transparency.
|
|
Transparent,
|
|
/// Transparency, but the contents behind the window are blurred.
|
|
///
|
|
/// Not always supported.
|
|
Blurred,
|
|
}
|
|
|
|
/// The options that can be configured for a file dialog prompt
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct PathPromptOptions {
|
|
/// Should the prompt allow files to be selected?
|
|
pub files: bool,
|
|
/// Should the prompt allow directories to be selected?
|
|
pub directories: bool,
|
|
/// Should the prompt allow multiple files to be selected?
|
|
pub multiple: bool,
|
|
}
|
|
|
|
/// What kind of prompt styling to show
|
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
|
pub enum PromptLevel {
|
|
/// A prompt that is shown when the user should be notified of something
|
|
Info,
|
|
|
|
/// A prompt that is shown when the user needs to be warned of a potential problem
|
|
Warning,
|
|
|
|
/// A prompt that is shown when a critical problem has occurred
|
|
Critical,
|
|
}
|
|
|
|
/// The style of the cursor (pointer)
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
pub enum CursorStyle {
|
|
/// The default cursor
|
|
Arrow,
|
|
|
|
/// A text input cursor
|
|
/// corresponds to the CSS cursor value `text`
|
|
IBeam,
|
|
|
|
/// A crosshair cursor
|
|
/// corresponds to the CSS cursor value `crosshair`
|
|
Crosshair,
|
|
|
|
/// A closed hand cursor
|
|
/// corresponds to the CSS cursor value `grabbing`
|
|
ClosedHand,
|
|
|
|
/// An open hand cursor
|
|
/// corresponds to the CSS cursor value `grab`
|
|
OpenHand,
|
|
|
|
/// A pointing hand cursor
|
|
/// corresponds to the CSS cursor value `pointer`
|
|
PointingHand,
|
|
|
|
/// A resize left cursor
|
|
/// corresponds to the CSS cursor value `w-resize`
|
|
ResizeLeft,
|
|
|
|
/// A resize right cursor
|
|
/// corresponds to the CSS cursor value `e-resize`
|
|
ResizeRight,
|
|
|
|
/// A resize cursor to the left and right
|
|
/// corresponds to the CSS cursor value `ew-resize`
|
|
ResizeLeftRight,
|
|
|
|
/// A resize up cursor
|
|
/// corresponds to the CSS cursor value `n-resize`
|
|
ResizeUp,
|
|
|
|
/// A resize down cursor
|
|
/// corresponds to the CSS cursor value `s-resize`
|
|
ResizeDown,
|
|
|
|
/// A resize cursor directing up and down
|
|
/// corresponds to the CSS cursor value `ns-resize`
|
|
ResizeUpDown,
|
|
|
|
/// A resize cursor directing up-left and down-right
|
|
/// corresponds to the CSS cursor value `nesw-resize`
|
|
ResizeUpLeftDownRight,
|
|
|
|
/// A resize cursor directing up-right and down-left
|
|
/// corresponds to the CSS cursor value `nwse-resize`
|
|
ResizeUpRightDownLeft,
|
|
|
|
/// A cursor indicating that the item/column can be resized horizontally.
|
|
/// corresponds to the CSS curosr value `col-resize`
|
|
ResizeColumn,
|
|
|
|
/// A cursor indicating that the item/row can be resized vertically.
|
|
/// corresponds to the CSS curosr value `row-resize`
|
|
ResizeRow,
|
|
|
|
/// A text input cursor for vertical layout
|
|
/// corresponds to the CSS cursor value `vertical-text`
|
|
IBeamCursorForVerticalLayout,
|
|
|
|
/// A cursor indicating that the operation is not allowed
|
|
/// corresponds to the CSS cursor value `not-allowed`
|
|
OperationNotAllowed,
|
|
|
|
/// A cursor indicating that the operation will result in a link
|
|
/// corresponds to the CSS cursor value `alias`
|
|
DragLink,
|
|
|
|
/// A cursor indicating that the operation will result in a copy
|
|
/// corresponds to the CSS cursor value `copy`
|
|
DragCopy,
|
|
|
|
/// A cursor indicating that the operation will result in a context menu
|
|
/// corresponds to the CSS cursor value `context-menu`
|
|
ContextualMenu,
|
|
}
|
|
|
|
impl Default for CursorStyle {
|
|
fn default() -> Self {
|
|
Self::Arrow
|
|
}
|
|
}
|
|
|
|
/// A clipboard item that should be copied to the clipboard
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct ClipboardItem {
|
|
pub(crate) text: String,
|
|
pub(crate) metadata: Option<String>,
|
|
}
|
|
|
|
impl ClipboardItem {
|
|
/// Create a new clipboard item with the given text
|
|
pub fn new(text: String) -> Self {
|
|
Self {
|
|
text,
|
|
metadata: None,
|
|
}
|
|
}
|
|
|
|
/// Create a new clipboard item with the given text and metadata
|
|
pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
|
|
self.metadata = Some(serde_json::to_string(&metadata).unwrap());
|
|
self
|
|
}
|
|
|
|
/// Get the text of the clipboard item
|
|
pub fn text(&self) -> &String {
|
|
&self.text
|
|
}
|
|
|
|
/// Get the metadata of the clipboard item
|
|
pub fn metadata<T>(&self) -> Option<T>
|
|
where
|
|
T: for<'a> Deserialize<'a>,
|
|
{
|
|
self.metadata
|
|
.as_ref()
|
|
.and_then(|m| serde_json::from_str(m).ok())
|
|
}
|
|
|
|
#[cfg_attr(target_os = "linux", allow(dead_code))]
|
|
pub(crate) fn text_hash(text: &str) -> u64 {
|
|
let mut hasher = SeaHasher::new();
|
|
text.hash(&mut hasher);
|
|
hasher.finish()
|
|
}
|
|
}
|