Checkpoint

Nathan Sobo created

Change summary

Cargo.lock                                   |   44 
crates/gpui/src/platform/mac/platform.rs     |    6 
crates/gpui3/Cargo.toml                      |    1 
crates/gpui3/src/app.rs                      |   21 
crates/gpui3/src/executor.rs                 |    2 
crates/gpui3/src/fonts.rs                    |    6 
crates/gpui3/src/geometry.rs                 |    6 
crates/gpui3/src/gpui3.rs                    |    1 
crates/gpui3/src/platform.rs                 |  187 +
crates/gpui3/src/platform/mac.rs             |   18 
crates/gpui3/src/platform/mac/open_type.rs   |  395 +++
crates/gpui3/src/platform/mac/platform.rs    | 2232 +++++++++++----------
crates/gpui3/src/platform/mac/text_system.rs |  757 +++++++
crates/gpui3/src/platform/test.rs            |  142 +
crates/gpui3/src/scene.rs                    |   32 
crates/gpui3/src/text.rs                     |   36 
crates/storybook/Cargo.toml                  |    6 
17 files changed, 2,700 insertions(+), 1,192 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -983,7 +983,7 @@ dependencies = [
  "collections",
  "editor",
  "gpui",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "outline",
  "project",
@@ -1960,7 +1960,7 @@ dependencies = [
  "cranelift-codegen",
  "cranelift-entity",
  "cranelift-frontend",
- "itertools 0.10.5",
+ "itertools",
  "log",
  "smallvec",
  "wasmparser",
@@ -2392,7 +2392,7 @@ dependencies = [
  "git",
  "gpui",
  "indoc",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "lazy_static",
  "log",
@@ -3273,7 +3273,7 @@ dependencies = [
  "futures 0.3.28",
  "gpui_macros",
  "image",
- "itertools 0.10.5",
+ "itertools",
  "lazy_static",
  "log",
  "media",
@@ -3365,7 +3365,7 @@ dependencies = [
  "futures 0.3.28",
  "gpui_macros",
  "image",
- "itertools 0.10.5",
+ "itertools",
  "lazy_static",
  "log",
  "media",
@@ -3374,6 +3374,7 @@ dependencies = [
  "ordered-float",
  "parking",
  "parking_lot 0.11.2",
+ "pathfinder_geometry",
  "plane-split",
  "png",
  "postage",
@@ -3971,15 +3972,6 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "itertools"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.9"
@@ -5772,7 +5764,7 @@ dependencies = [
  "globset",
  "gpui",
  "ignore",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "lazy_static",
  "log",
@@ -5896,7 +5888,7 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
 dependencies = [
  "bytes 1.4.0",
  "heck 0.3.3",
- "itertools 0.10.5",
+ "itertools",
  "lazy_static",
  "log",
  "multimap",
@@ -5915,7 +5907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba"
 dependencies = [
  "anyhow",
- "itertools 0.10.5",
+ "itertools",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -5928,7 +5920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
 dependencies = [
  "anyhow",
- "itertools 0.10.5",
+ "itertools",
  "proc-macro2",
  "quote",
  "syn 1.0.109",
@@ -7568,7 +7560,7 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
 dependencies = [
- "itertools 0.10.5",
+ "itertools",
  "nom",
  "unicode_categories",
 ]
@@ -7692,22 +7684,16 @@ name = "storybook"
 version = "0.1.0"
 dependencies = [
  "anyhow",
- "bytemuck",
  "derive_more",
  "gpui2",
- "itertools 0.11.0",
  "log",
- "plane-split",
- "raw-window-handle",
  "refineable",
  "rust-embed",
  "serde",
  "settings",
  "simplelog",
- "slotmap",
  "theme",
  "util",
- "wgpu",
 ]
 
 [[package]]
@@ -7977,7 +7963,7 @@ dependencies = [
  "dirs 4.0.0",
  "futures 0.3.28",
  "gpui",
- "itertools 0.10.5",
+ "itertools",
  "lazy_static",
  "libc",
  "mio-extras",
@@ -8008,7 +7994,7 @@ dependencies = [
  "editor",
  "futures 0.3.28",
  "gpui",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "lazy_static",
  "libc",
@@ -9190,7 +9176,7 @@ dependencies = [
  "futures 0.3.28",
  "gpui",
  "indoc",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "language_selector",
  "log",
@@ -10137,7 +10123,7 @@ dependencies = [
  "gpui",
  "indoc",
  "install_cli",
- "itertools 0.10.5",
+ "itertools",
  "language",
  "lazy_static",
  "log",

crates/gpui/src/platform/mac/platform.rs πŸ”—

@@ -1,6 +1,6 @@
 use super::{
     event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
-    FontSystem, MacWindow,
+    MacWindow, TextSystem,
 };
 use crate::{
     executor,
@@ -488,7 +488,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform {
 
 pub struct MacPlatform {
     dispatcher: Arc<Dispatcher>,
-    fonts: Arc<FontSystem>,
+    fonts: Arc<TextSystem>,
     pasteboard: id,
     text_hash_pasteboard_type: id,
     metadata_pasteboard_type: id,
@@ -498,7 +498,7 @@ impl MacPlatform {
     pub fn new() -> Self {
         Self {
             dispatcher: Arc::new(Dispatcher),
-            fonts: Arc::new(FontSystem::new()),
+            fonts: Arc::new(TextSystem::new()),
             pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
             text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
             metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },

crates/gpui3/Cargo.toml πŸ”—

@@ -35,6 +35,7 @@ num_cpus = "1.13"
 ordered-float.workspace = true
 parking = "2.0.0"
 parking_lot.workspace = true
+pathfinder_geometry = "0.5"
 postage.workspace = true
 rand.workspace = true
 refineable.workspace = true

crates/gpui3/src/app.rs πŸ”—

@@ -1,14 +1,19 @@
+use crate::{
+    Context, FontCache, LayoutId, Platform, Reference, View, Window, WindowContext, WindowHandle,
+    WindowId,
+};
 use anyhow::{anyhow, Result};
 use slotmap::SlotMap;
-use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};
+use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
 
-use crate::FontCache;
+#[derive(Clone)]
+pub struct App(Rc<RefCell<AppContext>>);
 
-use super::{
-    platform::Platform,
-    window::{Window, WindowHandle, WindowId},
-    Context, LayoutId, Reference, View, WindowContext,
-};
+impl App {
+    pub fn new(platform: Rc<dyn Platform>) -> Self {
+        Self(Rc::new(RefCell::new(AppContext::new(platform))))
+    }
+}
 
 pub struct AppContext {
     platform: Rc<dyn Platform>,
@@ -21,7 +26,7 @@ pub struct AppContext {
 
 impl AppContext {
     pub fn new(platform: Rc<dyn Platform>) -> Self {
-        let font_cache = Arc::new(FontCache::new(platform.font_system()));
+        let font_cache = Arc::new(FontCache::new(platform.text_system()));
         AppContext {
             platform,
             font_cache,

crates/gpui3/src/executor.rs πŸ”—

@@ -619,7 +619,7 @@ impl ExecutorEvent {
 }
 
 impl ForegroundExecutor {
-    pub fn platform(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
+    pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
         if dispatcher.is_main_thread() {
             Ok(Self::Platform {
                 dispatcher,

crates/gpui3/src/fonts.rs πŸ”—

@@ -245,7 +245,7 @@ impl FontCache {
                 .typographic_bounds(font_id, glyph_id)
                 .unwrap();
         }
-        bounds.size.width * self.em_size(font_id, font_size)
+        self.em_size(font_id, font_size) * bounds.size.width
     }
 
     pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
@@ -256,7 +256,7 @@ impl FontCache {
             glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
             advance = state.font_system.advance(font_id, glyph_id).unwrap();
         }
-        advance.x * self.em_size(font_id, font_size)
+        self.em_size(font_id, font_size) * advance.width
     }
 
     pub fn line_height(&self, font_size: Pixels) -> Pixels {
@@ -356,7 +356,7 @@ mod tests {
     #[test]
     fn test_select_font() {
         let platform = TestPlatform::new();
-        let fonts = FontCache::new(platform.font_system());
+        let fonts = FontCache::new(platform.text_system());
         let arial = fonts
             .load_family(
                 &["Arial"],

crates/gpui3/src/geometry.rs πŸ”—

@@ -246,6 +246,12 @@ impl std::hash::Hash for Pixels {
     }
 }
 
+impl From<f64> for Pixels {
+    fn from(val: f64) -> Self {
+        Pixels(val as f32)
+    }
+}
+
 unsafe impl bytemuck::Pod for Pixels {}
 unsafe impl bytemuck::Zeroable for Pixels {}
 

crates/gpui3/src/gpui3.rs πŸ”—

@@ -34,6 +34,7 @@ pub use styled::*;
 pub use taffy::LayoutId;
 use taffy::TaffyLayoutEngine;
 use text::*;
+pub use text::{Glyph, GlyphId};
 pub use util::arc_cow::ArcCow;
 pub use window::*;
 

crates/gpui3/src/platform.rs πŸ”—

@@ -6,13 +6,26 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
-    LineLayout, Pixels, Point, RunStyle, SharedString, Size,
+    AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight,
+    ForegroundExecutor, GlyphId, LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size,
 };
+use anyhow::anyhow;
 use async_task::Runnable;
 use futures::channel::oneshot;
 use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
-use std::{any::Any, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
+use seahash::SeaHasher;
+use serde::{Deserialize, Serialize};
+use std::hash::{Hash, Hasher};
+use std::{
+    any::Any,
+    fmt::{self, Debug, Display},
+    ops::Range,
+    path::{Path, PathBuf},
+    rc::Rc,
+    str::FromStr,
+    sync::Arc,
+};
+pub use time::UtcOffset;
 use uuid::Uuid;
 
 pub use events::*;
@@ -22,15 +35,64 @@ pub use mac::*;
 #[cfg(any(test, feature = "test"))]
 pub use test::*;
 
+// #[cfg(target_os = "macos")]
+// pub fn current() -> Rc<dyn Platform> {
+//     MacPlatform
+// }
+
 pub trait Platform {
-    fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
-    fn font_system(&self) -> Arc<dyn PlatformTextSystem>;
+    fn executor(&self) -> Rc<ForegroundExecutor>;
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
 
+    fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
+    fn quit(&self);
+    fn restart(&self);
+    fn activate(&self, ignoring_other_apps: bool);
+    fn hide(&self);
+    fn hide_other_apps(&self);
+    fn unhide_other_apps(&self);
+
+    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
+    fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>>;
+    fn main_window(&self) -> Option<AnyWindowHandle>;
     fn open_window(
         &self,
         handle: AnyWindowHandle,
         options: WindowOptions,
     ) -> Box<dyn PlatformWindow>;
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
+
+    fn open_url(&self, url: &str);
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
+    fn prompt_for_paths(
+        &self,
+        options: PathPromptOptions,
+    ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
+    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
+    fn reveal_path(&self, path: &Path);
+
+    fn on_become_active(&self, callback: Box<dyn FnMut()>);
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>);
+    fn on_quit(&self, callback: Box<dyn FnMut()>);
+    fn on_reopen(&self, callback: Box<dyn FnMut()>);
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
+
+    fn os_name(&self) -> &'static str;
+    fn os_version(&self) -> Result<SemanticVersion>;
+    fn app_version(&self) -> Result<SemanticVersion>;
+    fn app_path(&self) -> Result<PathBuf>;
+    fn local_timezone(&self) -> UtcOffset;
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
+
+    fn set_cursor_style(&self, style: CursorStyle);
+    fn should_auto_hide_scrollbars(&self) -> bool;
+
+    fn write_to_clipboard(&self, item: ClipboardItem);
+    fn read_from_clipboard(&self) -> Option<ClipboardItem>;
+
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
+    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
+    fn delete_credentials(&self, url: &str) -> Result<()>;
 }
 
 pub trait PlatformScreen: Debug {
@@ -90,12 +152,9 @@ pub trait PlatformTextSystem: Send + Sync {
         style: FontStyle,
     ) -> anyhow::Result<FontId>;
     fn font_metrics(&self, font_id: FontId) -> FontMetrics;
-    fn typographic_bounds(
-        &self,
-        font_id: FontId,
-        glyph_id: GlyphId,
-    ) -> anyhow::Result<Bounds<Pixels>>;
-    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Point<Pixels>>;
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId)
+        -> anyhow::Result<Bounds<f32>>;
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>>;
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
     fn rasterize_glyph(
         &self,
@@ -224,3 +283,109 @@ pub enum WindowPromptLevel {
     Warning,
     Critical,
 }
+
+#[derive(Copy, Clone, Debug)]
+pub struct PathPromptOptions {
+    pub files: bool,
+    pub directories: bool,
+    pub multiple: bool,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum PromptLevel {
+    Info,
+    Warning,
+    Critical,
+}
+
+#[derive(Copy, Clone, Debug)]
+pub enum CursorStyle {
+    Arrow,
+    ResizeLeftRight,
+    ResizeUpDown,
+    PointingHand,
+    IBeam,
+}
+
+impl Default for CursorStyle {
+    fn default() -> Self {
+        Self::Arrow
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct SemanticVersion {
+    major: usize,
+    minor: usize,
+    patch: usize,
+}
+
+impl FromStr for SemanticVersion {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self> {
+        let mut components = s.trim().split('.');
+        let major = components
+            .next()
+            .ok_or_else(|| anyhow!("missing major version number"))?
+            .parse()?;
+        let minor = components
+            .next()
+            .ok_or_else(|| anyhow!("missing minor version number"))?
+            .parse()?;
+        let patch = components
+            .next()
+            .ok_or_else(|| anyhow!("missing patch version number"))?
+            .parse()?;
+        Ok(Self {
+            major,
+            minor,
+            patch,
+        })
+    }
+}
+
+impl Display for SemanticVersion {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ClipboardItem {
+    pub(crate) text: String,
+    pub(crate) metadata: Option<String>,
+}
+
+impl ClipboardItem {
+    pub fn new(text: String) -> Self {
+        Self {
+            text,
+            metadata: None,
+        }
+    }
+
+    pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
+        self.metadata = Some(serde_json::to_string(&metadata).unwrap());
+        self
+    }
+
+    pub fn text(&self) -> &String {
+        &self.text
+    }
+
+    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())
+    }
+
+    pub(crate) fn text_hash(text: &str) -> u64 {
+        let mut hasher = SeaHasher::new();
+        text.hash(&mut hasher);
+        hasher.finish()
+    }
+}

crates/gpui3/src/platform/mac.rs πŸ”—

@@ -2,18 +2,13 @@
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
 mod events;
+mod open_type;
 mod platform;
 mod screen;
+mod text_system;
 mod window;
 mod window_appearence;
 
-use std::{
-    ffi::{c_char, CStr, OsStr},
-    ops::Range,
-    os::unix::prelude::OsStrExt,
-    path::PathBuf,
-};
-
 use crate::{px, size, Pixels, Size};
 use anyhow::anyhow;
 use cocoa::{
@@ -25,8 +20,17 @@ use objc::{
     runtime::{BOOL, NO, YES},
     sel, sel_impl,
 };
+use std::{
+    ffi::{c_char, CStr, OsStr},
+    ops::Range,
+    os::unix::prelude::OsStrExt,
+    path::PathBuf,
+};
+
+pub use dispatcher::*;
 pub use platform::*;
 pub use screen::*;
+pub use text_system::*;
 pub use window::*;
 
 trait BoolExt {

crates/gpui3/src/platform/mac/open_type.rs πŸ”—

@@ -0,0 +1,395 @@
+#![allow(unused, non_upper_case_globals)]
+
+use std::ptr;
+
+use crate::FontFeatures;
+use cocoa::appkit::CGFloat;
+use core_foundation::{base::TCFType, number::CFNumber};
+use core_graphics::geometry::CGAffineTransform;
+use core_text::{
+    font::{CTFont, CTFontRef},
+    font_descriptor::{
+        CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
+    },
+};
+use font_kit::font::Font;
+
+const kCaseSensitiveLayoutOffSelector: i32 = 1;
+const kCaseSensitiveLayoutOnSelector: i32 = 0;
+const kCaseSensitiveLayoutType: i32 = 33;
+const kCaseSensitiveSpacingOffSelector: i32 = 3;
+const kCaseSensitiveSpacingOnSelector: i32 = 2;
+const kCharacterAlternativesType: i32 = 17;
+const kCommonLigaturesOffSelector: i32 = 3;
+const kCommonLigaturesOnSelector: i32 = 2;
+const kContextualAlternatesOffSelector: i32 = 1;
+const kContextualAlternatesOnSelector: i32 = 0;
+const kContextualAlternatesType: i32 = 36;
+const kContextualLigaturesOffSelector: i32 = 19;
+const kContextualLigaturesOnSelector: i32 = 18;
+const kContextualSwashAlternatesOffSelector: i32 = 5;
+const kContextualSwashAlternatesOnSelector: i32 = 4;
+const kDefaultLowerCaseSelector: i32 = 0;
+const kDefaultUpperCaseSelector: i32 = 0;
+const kDiagonalFractionsSelector: i32 = 2;
+const kFractionsType: i32 = 11;
+const kHistoricalLigaturesOffSelector: i32 = 21;
+const kHistoricalLigaturesOnSelector: i32 = 20;
+const kHojoCharactersSelector: i32 = 12;
+const kInferiorsSelector: i32 = 2;
+const kJIS2004CharactersSelector: i32 = 11;
+const kLigaturesType: i32 = 1;
+const kLowerCasePetiteCapsSelector: i32 = 2;
+const kLowerCaseSmallCapsSelector: i32 = 1;
+const kLowerCaseType: i32 = 37;
+const kLowerCaseNumbersSelector: i32 = 0;
+const kMathematicalGreekOffSelector: i32 = 11;
+const kMathematicalGreekOnSelector: i32 = 10;
+const kMonospacedNumbersSelector: i32 = 0;
+const kNLCCharactersSelector: i32 = 13;
+const kNoFractionsSelector: i32 = 0;
+const kNormalPositionSelector: i32 = 0;
+const kNoStyleOptionsSelector: i32 = 0;
+const kNumberCaseType: i32 = 21;
+const kNumberSpacingType: i32 = 6;
+const kOrdinalsSelector: i32 = 3;
+const kProportionalNumbersSelector: i32 = 1;
+const kQuarterWidthTextSelector: i32 = 4;
+const kScientificInferiorsSelector: i32 = 4;
+const kSlashedZeroOffSelector: i32 = 5;
+const kSlashedZeroOnSelector: i32 = 4;
+const kStyleOptionsType: i32 = 19;
+const kStylisticAltEighteenOffSelector: i32 = 37;
+const kStylisticAltEighteenOnSelector: i32 = 36;
+const kStylisticAltEightOffSelector: i32 = 17;
+const kStylisticAltEightOnSelector: i32 = 16;
+const kStylisticAltElevenOffSelector: i32 = 23;
+const kStylisticAltElevenOnSelector: i32 = 22;
+const kStylisticAlternativesType: i32 = 35;
+const kStylisticAltFifteenOffSelector: i32 = 31;
+const kStylisticAltFifteenOnSelector: i32 = 30;
+const kStylisticAltFiveOffSelector: i32 = 11;
+const kStylisticAltFiveOnSelector: i32 = 10;
+const kStylisticAltFourOffSelector: i32 = 9;
+const kStylisticAltFourOnSelector: i32 = 8;
+const kStylisticAltFourteenOffSelector: i32 = 29;
+const kStylisticAltFourteenOnSelector: i32 = 28;
+const kStylisticAltNineOffSelector: i32 = 19;
+const kStylisticAltNineOnSelector: i32 = 18;
+const kStylisticAltNineteenOffSelector: i32 = 39;
+const kStylisticAltNineteenOnSelector: i32 = 38;
+const kStylisticAltOneOffSelector: i32 = 3;
+const kStylisticAltOneOnSelector: i32 = 2;
+const kStylisticAltSevenOffSelector: i32 = 15;
+const kStylisticAltSevenOnSelector: i32 = 14;
+const kStylisticAltSeventeenOffSelector: i32 = 35;
+const kStylisticAltSeventeenOnSelector: i32 = 34;
+const kStylisticAltSixOffSelector: i32 = 13;
+const kStylisticAltSixOnSelector: i32 = 12;
+const kStylisticAltSixteenOffSelector: i32 = 33;
+const kStylisticAltSixteenOnSelector: i32 = 32;
+const kStylisticAltTenOffSelector: i32 = 21;
+const kStylisticAltTenOnSelector: i32 = 20;
+const kStylisticAltThirteenOffSelector: i32 = 27;
+const kStylisticAltThirteenOnSelector: i32 = 26;
+const kStylisticAltThreeOffSelector: i32 = 7;
+const kStylisticAltThreeOnSelector: i32 = 6;
+const kStylisticAltTwelveOffSelector: i32 = 25;
+const kStylisticAltTwelveOnSelector: i32 = 24;
+const kStylisticAltTwentyOffSelector: i32 = 41;
+const kStylisticAltTwentyOnSelector: i32 = 40;
+const kStylisticAltTwoOffSelector: i32 = 5;
+const kStylisticAltTwoOnSelector: i32 = 4;
+const kSuperiorsSelector: i32 = 1;
+const kSwashAlternatesOffSelector: i32 = 3;
+const kSwashAlternatesOnSelector: i32 = 2;
+const kTitlingCapsSelector: i32 = 4;
+const kTypographicExtrasType: i32 = 14;
+const kVerticalFractionsSelector: i32 = 1;
+const kVerticalPositionType: i32 = 10;
+
+pub fn apply_features(font: &mut Font, features: &FontFeatures) {
+    // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
+    // for a reference implementation.
+    toggle_open_type_feature(
+        font,
+        features.calt,
+        kContextualAlternatesType,
+        kContextualAlternatesOnSelector,
+        kContextualAlternatesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.case,
+        kCaseSensitiveLayoutType,
+        kCaseSensitiveLayoutOnSelector,
+        kCaseSensitiveLayoutOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.cpsp,
+        kCaseSensitiveLayoutType,
+        kCaseSensitiveSpacingOnSelector,
+        kCaseSensitiveSpacingOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.frac,
+        kFractionsType,
+        kDiagonalFractionsSelector,
+        kNoFractionsSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.liga,
+        kLigaturesType,
+        kCommonLigaturesOnSelector,
+        kCommonLigaturesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.onum,
+        kNumberCaseType,
+        kLowerCaseNumbersSelector,
+        2,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ordn,
+        kVerticalPositionType,
+        kOrdinalsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.pnum,
+        kNumberSpacingType,
+        kProportionalNumbersSelector,
+        4,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss01,
+        kStylisticAlternativesType,
+        kStylisticAltOneOnSelector,
+        kStylisticAltOneOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss02,
+        kStylisticAlternativesType,
+        kStylisticAltTwoOnSelector,
+        kStylisticAltTwoOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss03,
+        kStylisticAlternativesType,
+        kStylisticAltThreeOnSelector,
+        kStylisticAltThreeOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss04,
+        kStylisticAlternativesType,
+        kStylisticAltFourOnSelector,
+        kStylisticAltFourOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss05,
+        kStylisticAlternativesType,
+        kStylisticAltFiveOnSelector,
+        kStylisticAltFiveOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss06,
+        kStylisticAlternativesType,
+        kStylisticAltSixOnSelector,
+        kStylisticAltSixOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss07,
+        kStylisticAlternativesType,
+        kStylisticAltSevenOnSelector,
+        kStylisticAltSevenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss08,
+        kStylisticAlternativesType,
+        kStylisticAltEightOnSelector,
+        kStylisticAltEightOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss09,
+        kStylisticAlternativesType,
+        kStylisticAltNineOnSelector,
+        kStylisticAltNineOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss10,
+        kStylisticAlternativesType,
+        kStylisticAltTenOnSelector,
+        kStylisticAltTenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss11,
+        kStylisticAlternativesType,
+        kStylisticAltElevenOnSelector,
+        kStylisticAltElevenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss12,
+        kStylisticAlternativesType,
+        kStylisticAltTwelveOnSelector,
+        kStylisticAltTwelveOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss13,
+        kStylisticAlternativesType,
+        kStylisticAltThirteenOnSelector,
+        kStylisticAltThirteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss14,
+        kStylisticAlternativesType,
+        kStylisticAltFourteenOnSelector,
+        kStylisticAltFourteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss15,
+        kStylisticAlternativesType,
+        kStylisticAltFifteenOnSelector,
+        kStylisticAltFifteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss16,
+        kStylisticAlternativesType,
+        kStylisticAltSixteenOnSelector,
+        kStylisticAltSixteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss17,
+        kStylisticAlternativesType,
+        kStylisticAltSeventeenOnSelector,
+        kStylisticAltSeventeenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss18,
+        kStylisticAlternativesType,
+        kStylisticAltEighteenOnSelector,
+        kStylisticAltEighteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss19,
+        kStylisticAlternativesType,
+        kStylisticAltNineteenOnSelector,
+        kStylisticAltNineteenOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.ss20,
+        kStylisticAlternativesType,
+        kStylisticAltTwentyOnSelector,
+        kStylisticAltTwentyOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.subs,
+        kVerticalPositionType,
+        kInferiorsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.sups,
+        kVerticalPositionType,
+        kSuperiorsSelector,
+        kNormalPositionSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.swsh,
+        kContextualAlternatesType,
+        kSwashAlternatesOnSelector,
+        kSwashAlternatesOffSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.titl,
+        kStyleOptionsType,
+        kTitlingCapsSelector,
+        kNoStyleOptionsSelector,
+    );
+    toggle_open_type_feature(
+        font,
+        features.tnum,
+        kNumberSpacingType,
+        kMonospacedNumbersSelector,
+        4,
+    );
+    toggle_open_type_feature(
+        font,
+        features.zero,
+        kTypographicExtrasType,
+        kSlashedZeroOnSelector,
+        kSlashedZeroOffSelector,
+    );
+}
+
+fn toggle_open_type_feature(
+    font: &mut Font,
+    enabled: Option<bool>,
+    type_identifier: i32,
+    on_selector_identifier: i32,
+    off_selector_identifier: i32,
+) {
+    if let Some(enabled) = enabled {
+        let native_font = font.native_font();
+        unsafe {
+            let selector_identifier = if enabled {
+                on_selector_identifier
+            } else {
+                off_selector_identifier
+            };
+            let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
+                native_font.copy_descriptor().as_concrete_TypeRef(),
+                CFNumber::from(type_identifier).as_concrete_TypeRef(),
+                CFNumber::from(selector_identifier).as_concrete_TypeRef(),
+            );
+            let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
+            let new_font = CTFontCreateCopyWithAttributes(
+                font.native_font().as_concrete_TypeRef(),
+                0.0,
+                ptr::null(),
+                new_descriptor.as_concrete_TypeRef(),
+            );
+            let new_font = CTFont::wrap_under_create_rule(new_font);
+            *font = Font::from_native_font(new_font);
+        }
+    }
+}
+
+#[link(name = "CoreText", kind = "framework")]
+extern "C" {
+    fn CTFontCreateCopyWithAttributes(
+        font: CTFontRef,
+        size: CGFloat,
+        matrix: *const CGAffineTransform,
+        attributes: CTFontDescriptorRef,
+    ) -> CTFontRef;
+}

crates/gpui3/src/platform/mac/platform.rs πŸ”—

@@ -1,1102 +1,1130 @@
-// use anyhow::{anyhow, Result};
-// use block::ConcreteBlock;
-// use cocoa::{
-//     appkit::{
-//         NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
-//         NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
-//         NSSavePanel, NSWindow,
-//     },
-//     base::{id, nil, selector, BOOL, YES},
-//     foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSUInteger, NSURL},
-// };
-// use core_foundation::{
-//     base::{CFTypeRef, OSStatus},
-//     dictionary::CFDictionaryRef,
-//     string::CFStringRef,
-// };
-// use ctor::ctor;
-// use objc::{
-//     class,
-//     declare::ClassDecl,
-//     msg_send,
-//     runtime::{Class, Object, Sel},
-//     sel, sel_impl,
-// };
-
-// use postage::oneshot;
-// use ptr::null_mut;
-// use std::{
-//     cell::{Cell, RefCell},
-//     convert::TryInto,
-//     ffi::{c_void, CStr, OsStr},
-//     os::{raw::c_char, unix::ffi::OsStrExt},
-//     path::{Path, PathBuf},
-//     process::Command,
-//     ptr,
-//     rc::Rc,
-//     slice, str,
-//     sync::Arc,
-// };
-
-// use crate::Event;
-
-// #[allow(non_upper_case_globals)]
-// const NSUTF8StringEncoding: NSUInteger = 4;
-
-// const MAC_PLATFORM_IVAR: &str = "platform";
-// static mut APP_CLASS: *const Class = ptr::null();
-// static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
-
-// #[ctor]
-// unsafe fn build_classes() {
-//     APP_CLASS = {
-//         let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
-//         decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
-//         decl.add_method(
-//             sel!(sendEvent:),
-//             send_event as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.register()
-//     };
-
-//     APP_DELEGATE_CLASS = {
-//         let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
-//         decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
-//         decl.add_method(
-//             sel!(applicationDidFinishLaunching:),
-//             did_finish_launching as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(applicationShouldHandleReopen:hasVisibleWindows:),
-//             should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
-//         );
-//         decl.add_method(
-//             sel!(applicationDidBecomeActive:),
-//             did_become_active as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(applicationDidResignActive:),
-//             did_resign_active as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(applicationWillTerminate:),
-//             will_terminate as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(handleGPUIMenuItem:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         // Add menu item handlers so that OS save panels have the correct key commands
-//         decl.add_method(
-//             sel!(cut:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(copy:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(paste:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(selectAll:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(undo:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(redo:),
-//             handle_menu_item as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(validateMenuItem:),
-//             validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
-//         );
-//         decl.add_method(
-//             sel!(menuWillOpen:),
-//             menu_will_open as extern "C" fn(&mut Object, Sel, id),
-//         );
-//         decl.add_method(
-//             sel!(application:openURLs:),
-//             open_urls as extern "C" fn(&mut Object, Sel, id, id),
-//         );
-//         decl.register()
-//     }
-// }
-
-// pub struct MacForegroundPlatformState {
-//     dispatcher: Arc<MacDispatcher>,
-//     fonts: Arc<MacFontSystem>,
-//     pasteboard: id,
-//     text_hash_pasteboard_type: id,
-//     metadata_pasteboard_type: id,
-//     become_active: Option<Box<dyn FnMut()>>,
-//     resign_active: Option<Box<dyn FnMut()>>,
-//     reopen: Option<Box<dyn FnMut()>>,
-//     quit: Option<Box<dyn FnMut()>>,
-//     event: Option<Box<dyn FnMut(Event) -> bool>>,
-//     // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
-//     validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
-//     will_open_menu: Option<Box<dyn FnMut()>>,
-//     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
-//     finish_launching: Option<Box<dyn FnOnce()>>,
-//     // menu_actions: Vec<Box<dyn Action>>,
-//     // foreground: Rc<executor::Foreground>,
-// }
-
-// impl MacForegroundPlatform {
-//     pub fn new(foreground: Rc<executor::Foreground>) -> Self {
-//         Self(RefCell::new(MacForegroundPlatformState {
-//             become_active: None,
-//             resign_active: None,
-//             reopen: None,
-//             quit: None,
-//             event: None,
-//             // menu_command: None,
-//             validate_menu_command: None,
-//             will_open_menu: None,
-//             open_urls: None,
-//             finish_launching: None,
-//             menu_actions: Default::default(),
-//             foreground,
-//         }))
-//     }
-
-//     unsafe fn create_menu_bar(
-//         &self,
-//         menus: Vec<Menu>,
-//         delegate: id,
-//         actions: &mut Vec<Box<dyn Action>>,
-//         keystroke_matcher: &KeymapMatcher,
-//     ) -> id {
-//         let application_menu = NSMenu::new(nil).autorelease();
-//         application_menu.setDelegate_(delegate);
-
-//         for menu_config in menus {
-//             let menu = NSMenu::new(nil).autorelease();
-//             menu.setTitle_(ns_string(menu_config.name));
-//             menu.setDelegate_(delegate);
-
-//             for item_config in menu_config.items {
-//                 menu.addItem_(self.create_menu_item(
-//                     item_config,
-//                     delegate,
-//                     actions,
-//                     keystroke_matcher,
-//                 ));
-//             }
-
-//             let menu_item = NSMenuItem::new(nil).autorelease();
-//             menu_item.setSubmenu_(menu);
-//             application_menu.addItem_(menu_item);
-
-//             if menu_config.name == "Window" {
-//                 let app: id = msg_send![APP_CLASS, sharedApplication];
-//                 app.setWindowsMenu_(menu);
-//             }
-//         }
-
-//         application_menu
-//     }
-
-//     // unsafe fn create_menu_item(
-//     //     &self,
-//     //     item: MenuItem,
-//     //     delegate: id,
-//     //     actions: &mut Vec<Box<dyn Action>>,
-//     //     keystroke_matcher: &KeymapMatcher,
-//     // ) -> id {
-//     //     match item {
-//     //         MenuItem::Separator => NSMenuItem::separatorItem(nil),
-//     //         MenuItem::Action {
-//     //             name,
-//     //             action,
-//     //             os_action,
-//     //         } => {
-//     //             // TODO
-//     //             let keystrokes = keystroke_matcher
-//     //                 .bindings_for_action(action.id())
-//     //                 .find(|binding| binding.action().eq(action.as_ref()))
-//     //                 .map(|binding| binding.keystrokes());
-//     //             let selector = match os_action {
-//     //                 Some(crate::OsAction::Cut) => selector("cut:"),
-//     //                 Some(crate::OsAction::Copy) => selector("copy:"),
-//     //                 Some(crate::OsAction::Paste) => selector("paste:"),
-//     //                 Some(crate::OsAction::SelectAll) => selector("selectAll:"),
-//     //                 Some(crate::OsAction::Undo) => selector("undo:"),
-//     //                 Some(crate::OsAction::Redo) => selector("redo:"),
-//     //                 None => selector("handleGPUIMenuItem:"),
-//     //             };
-
-//     //             let item;
-//     //             if let Some(keystrokes) = keystrokes {
-//     //                 if keystrokes.len() == 1 {
-//     //                     let keystroke = &keystrokes[0];
-//     //                     let mut mask = NSEventModifierFlags::empty();
-//     //                     for (modifier, flag) in &[
-//     //                         (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
-//     //                         (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
-//     //                         (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
-//     //                         (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
-//     //                     ] {
-//     //                         if *modifier {
-//     //                             mask |= *flag;
-//     //                         }
-//     //                     }
-
-//     //                     item = NSMenuItem::alloc(nil)
-//     //                         .initWithTitle_action_keyEquivalent_(
-//     //                             ns_string(name),
-//     //                             selector,
-//     //                             ns_string(key_to_native(&keystroke.key).as_ref()),
-//     //                         )
-//     //                         .autorelease();
-//     //                     item.setKeyEquivalentModifierMask_(mask);
-//     //                 }
-//     //                 // For multi-keystroke bindings, render the keystroke as part of the title.
-//     //                 else {
-//     //                     use std::fmt::Write;
-
-//     //                     let mut name = format!("{name} [");
-//     //                     for (i, keystroke) in keystrokes.iter().enumerate() {
-//     //                         if i > 0 {
-//     //                             name.push(' ');
-//     //                         }
-//     //                         write!(&mut name, "{}", keystroke).unwrap();
-//     //                     }
-//     //                     name.push(']');
-
-//     //                     item = NSMenuItem::alloc(nil)
-//     //                         .initWithTitle_action_keyEquivalent_(
-//     //                             ns_string(&name),
-//     //                             selector,
-//     //                             ns_string(""),
-//     //                         )
-//     //                         .autorelease();
-//     //                 }
-//     //             } else {
-//     //                 item = NSMenuItem::alloc(nil)
-//     //                     .initWithTitle_action_keyEquivalent_(
-//     //                         ns_string(name),
-//     //                         selector,
-//     //                         ns_string(""),
-//     //                     )
-//     //                     .autorelease();
-//     //             }
-
-//     //             let tag = actions.len() as NSInteger;
-//     //             let _: () = msg_send![item, setTag: tag];
-//     //             actions.push(action);
-//     //             item
-//     //         }
-//     //         MenuItem::Submenu(Menu { name, items }) => {
-//     //             let item = NSMenuItem::new(nil).autorelease();
-//     //             let submenu = NSMenu::new(nil).autorelease();
-//     //             submenu.setDelegate_(delegate);
-//     //             for item in items {
-//     //                 submenu.addItem_(self.create_menu_item(
-//     //                     item,
-//     //                     delegate,
-//     //                     actions,
-//     //                     keystroke_matcher,
-//     //                 ));
-//     //             }
-//     //             item.setSubmenu_(submenu);
-//     //             item.setTitle_(ns_string(name));
-//     //             item
-//     //         }
-//     //     }
-//     // }
-
-//     unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
-//         let data = self.pasteboard.dataForType(kind);
-//         if data == nil {
-//             None
-//         } else {
-//             Some(slice::from_raw_parts(
-//                 data.bytes() as *mut u8,
-//                 data.length() as usize,
-//             ))
-//         }
-//     }
-// }
-
-// // impl platform::ForegroundPlatform for MacForegroundPlatform {
-// //     fn on_become_active(&self, callback: Box<dyn FnMut()>) {
-// //         self.0.borrow_mut().become_active = Some(callback);
-// //     }
-
-// //     fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
-// //         self.0.borrow_mut().resign_active = Some(callback);
-// //     }
-
-// //     fn on_quit(&self, callback: Box<dyn FnMut()>) {
-// //         self.0.borrow_mut().quit = Some(callback);
-// //     }
-
-// //     fn on_reopen(&self, callback: Box<dyn FnMut()>) {
-// //         self.0.borrow_mut().reopen = Some(callback);
-// //     }
-
-// //     fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
-// //         self.0.borrow_mut().event = Some(callback);
-// //     }
-
-// //     fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
-// //         self.0.borrow_mut().open_urls = Some(callback);
-// //     }
-
-// //     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
-// //         self.0.borrow_mut().finish_launching = Some(on_finish_launching);
-
-// //         unsafe {
-// //             let app: id = msg_send![APP_CLASS, sharedApplication];
-// //             let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
-// //             app.setDelegate_(app_delegate);
-
-// //             let self_ptr = self as *const Self as *const c_void;
-// //             (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-// //             (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
-
-// //             let pool = NSAutoreleasePool::new(nil);
-// //             app.run();
-// //             pool.drain();
-
-// //             (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
-// //             (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
-// //         }
-// //     }
-
-// //     fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-// //         self.0.borrow_mut().menu_command = Some(callback);
-// //     }
-
-// //     fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
-// //         self.0.borrow_mut().will_open_menu = Some(callback);
-// //     }
-
-// //     fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-// //         self.0.borrow_mut().validate_menu_command = Some(callback);
-// //     }
-
-// //     fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
-// //         unsafe {
-// //             let app: id = msg_send![APP_CLASS, sharedApplication];
-// //             let mut state = self.0.borrow_mut();
-// //             let actions = &mut state.menu_actions;
-// //             app.setMainMenu_(self.create_menu_bar(
-// //                 menus,
-// //                 app.delegate(),
-// //                 actions,
-// //                 keystroke_matcher,
-// //             ));
-// //         }
-// //     }
-
-// //     fn prompt_for_paths(
-// //         &self,
-// //         options: platform::PathPromptOptions,
-// //     ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
-// //         unsafe {
-// //             let panel = NSOpenPanel::openPanel(nil);
-// //             panel.setCanChooseDirectories_(options.directories.to_objc());
-// //             panel.setCanChooseFiles_(options.files.to_objc());
-// //             panel.setAllowsMultipleSelection_(options.multiple.to_objc());
-// //             panel.setResolvesAliases_(false.to_objc());
-// //             let (done_tx, done_rx) = oneshot::channel();
-// //             let done_tx = Cell::new(Some(done_tx));
-// //             let block = ConcreteBlock::new(move |response: NSModalResponse| {
-// //                 let result = if response == NSModalResponse::NSModalResponseOk {
-// //                     let mut result = Vec::new();
-// //                     let urls = panel.URLs();
-// //                     for i in 0..urls.count() {
-// //                         let url = urls.objectAtIndex(i);
-// //                         if url.isFileURL() == YES {
-// //                             if let Ok(path) = ns_url_to_path(url) {
-// //                                 result.push(path)
-// //                             }
-// //                         }
-// //                     }
-// //                     Some(result)
-// //                 } else {
-// //                     None
-// //                 };
-
-// //                 if let Some(mut done_tx) = done_tx.take() {
-// //                     let _ = postage::sink::Sink::try_send(&mut done_tx, result);
-// //                 }
-// //             });
-// //             let block = block.copy();
-// //             let _: () = msg_send![panel, beginWithCompletionHandler: block];
-// //             done_rx
-// //         }
-// //     }
-
-// //     fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
-// //         unsafe {
-// //             let panel = NSSavePanel::savePanel(nil);
-// //             let path = ns_string(directory.to_string_lossy().as_ref());
-// //             let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
-// //             panel.setDirectoryURL(url);
-
-// //             let (done_tx, done_rx) = oneshot::channel();
-// //             let done_tx = Cell::new(Some(done_tx));
-// //             let block = ConcreteBlock::new(move |response: NSModalResponse| {
-// //                 let mut result = None;
-// //                 if response == NSModalResponse::NSModalResponseOk {
-// //                     let url = panel.URL();
-// //                     if url.isFileURL() == YES {
-// //                         result = ns_url_to_path(panel.URL()).ok()
-// //                     }
-// //                 }
-
-// //                 if let Some(mut done_tx) = done_tx.take() {
-// //                     let _ = postage::sink::Sink::try_send(&mut done_tx, result);
-// //                 }
-// //             });
-// //             let block = block.copy();
-// //             let _: () = msg_send![panel, beginWithCompletionHandler: block];
-// //             done_rx
-// //         }
-// //     }
-
-// //     fn reveal_path(&self, path: &Path) {
-// //         unsafe {
-// //             let path = path.to_path_buf();
-// //             self.0
-// //                 .borrow()
-// //                 .foreground
-// //                 .spawn(async move {
-// //                     let full_path = ns_string(path.to_str().unwrap_or(""));
-// //                     let root_full_path = ns_string("");
-// //                     let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
-// //                     let _: BOOL = msg_send![
-// //                         workspace,
-// //                         selectFile: full_path
-// //                         inFileViewerRootedAtPath: root_full_path
-// //                     ];
-// //                 })
-// //                 .detach();
-// //         }
-// //     }
-// // }
-
-// // impl Platform for MacPlatform {
-// //     fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
-// //         self.dispatcher.clone()
-// //     }
-
-// //     fn fonts(&self) -> Arc<dyn platform::FontSystem> {
-// //         self.fonts.clone()
-// //     }
-
-// //     fn activate(&self, ignoring_other_apps: bool) {
-// //         unsafe {
-// //             let app = NSApplication::sharedApplication(nil);
-// //             app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
-// //         }
-// //     }
-
-// //     fn hide(&self) {
-// //         unsafe {
-// //             let app = NSApplication::sharedApplication(nil);
-// //             let _: () = msg_send![app, hide: nil];
-// //         }
-// //     }
-
-// //     fn hide_other_apps(&self) {
-// //         unsafe {
-// //             let app = NSApplication::sharedApplication(nil);
-// //             let _: () = msg_send![app, hideOtherApplications: nil];
-// //         }
-// //     }
-
-// //     fn unhide_other_apps(&self) {
-// //         unsafe {
-// //             let app = NSApplication::sharedApplication(nil);
-// //             let _: () = msg_send![app, unhideAllApplications: nil];
-// //         }
-// //     }
-
-// //     fn quit(&self) {
-// //         // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
-// //         // synchronously before this method terminates. If we call `Platform::quit` while holding a
-// //         // borrow of the app state (which most of the time we will do), we will end up
-// //         // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
-// //         // this, we make quitting the application asynchronous so that we aren't holding borrows to
-// //         // the app state on the stack when we actually terminate the app.
-
-// //         use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
-
-// //         unsafe {
-// //             dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
-// //         }
-
-// //         unsafe extern "C" fn quit(_: *mut c_void) {
-// //             let app = NSApplication::sharedApplication(nil);
-// //             let _: () = msg_send![app, terminate: nil];
-// //         }
-// //     }
-
-// //     fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {
-// //         Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
-// //     }
-
-// //     fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
-// //         Screen::all()
-// //             .into_iter()
-// //             .map(|screen| Rc::new(screen) as Rc<_>)
-// //             .collect()
-// //     }
-
-// //     fn open_window(
-// //         &self,
-// //         handle: AnyWindowHandle,
-// //         options: platform::WindowOptions,
-// //         executor: Rc<executor::Foreground>,
-// //     ) -> Box<dyn platform::Window> {
-// //         Box::new(MacWindow::open(handle, options, executor, self.fonts()))
-// //     }
-
-// //     fn main_window(&self) -> Option<AnyWindowHandle> {
-// //         MacWindow::main_window()
-// //     }
-
-// //     fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
-// //         Box::new(StatusItem::add(self.fonts()))
-// //     }
-
-// //     fn write_to_clipboard(&self, item: ClipboardItem) {
-// //         unsafe {
-// //             self.pasteboard.clearContents();
-
-// //             let text_bytes = NSData::dataWithBytes_length_(
-// //                 nil,
-// //                 item.text.as_ptr() as *const c_void,
-// //                 item.text.len() as u64,
-// //             );
-// //             self.pasteboard
-// //                 .setData_forType(text_bytes, NSPasteboardTypeString);
-
-// //             if let Some(metadata) = item.metadata.as_ref() {
-// //                 let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
-// //                 let hash_bytes = NSData::dataWithBytes_length_(
-// //                     nil,
-// //                     hash_bytes.as_ptr() as *const c_void,
-// //                     hash_bytes.len() as u64,
-// //                 );
-// //                 self.pasteboard
-// //                     .setData_forType(hash_bytes, self.text_hash_pasteboard_type);
-
-// //                 let metadata_bytes = NSData::dataWithBytes_length_(
-// //                     nil,
-// //                     metadata.as_ptr() as *const c_void,
-// //                     metadata.len() as u64,
-// //                 );
-// //                 self.pasteboard
-// //                     .setData_forType(metadata_bytes, self.metadata_pasteboard_type);
-// //             }
-// //         }
-// //     }
-
-// //     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
-// //         unsafe {
-// //             if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
-// //                 let text = String::from_utf8_lossy(text_bytes).to_string();
-// //                 let hash_bytes = self
-// //                     .read_from_pasteboard(self.text_hash_pasteboard_type)
-// //                     .and_then(|bytes| bytes.try_into().ok())
-// //                     .map(u64::from_be_bytes);
-// //                 let metadata_bytes = self
-// //                     .read_from_pasteboard(self.metadata_pasteboard_type)
-// //                     .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
-
-// //                 if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
-// //                     if hash == ClipboardItem::text_hash(&text) {
-// //                         Some(ClipboardItem {
-// //                             text,
-// //                             metadata: Some(metadata),
-// //                         })
-// //                     } else {
-// //                         Some(ClipboardItem {
-// //                             text,
-// //                             metadata: None,
-// //                         })
-// //                     }
-// //                 } else {
-// //                     Some(ClipboardItem {
-// //                         text,
-// //                         metadata: None,
-// //                     })
-// //                 }
-// //             } else {
-// //                 None
-// //             }
-// //         }
-// //     }
-
-// //     fn open_url(&self, url: &str) {
-// //         unsafe {
-// //             let url = NSURL::alloc(nil)
-// //                 .initWithString_(ns_string(url))
-// //                 .autorelease();
-// //             let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
-// //             msg_send![workspace, openURL: url]
-// //         }
-// //     }
-
-// //     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
-// //         let url = CFString::from(url);
-// //         let username = CFString::from(username);
-// //         let password = CFData::from_buffer(password);
-
-// //         unsafe {
-// //             use security::*;
-
-// //             // First, check if there are already credentials for the given server. If so, then
-// //             // update the username and password.
-// //             let mut verb = "updating";
-// //             let mut query_attrs = CFMutableDictionary::with_capacity(2);
-// //             query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-// //             query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
-// //             let mut attrs = CFMutableDictionary::with_capacity(4);
-// //             attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-// //             attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-// //             attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
-// //             attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
-
-// //             let mut status = SecItemUpdate(
-// //                 query_attrs.as_concrete_TypeRef(),
-// //                 attrs.as_concrete_TypeRef(),
-// //             );
-
-// //             // If there were no existing credentials for the given server, then create them.
-// //             if status == errSecItemNotFound {
-// //                 verb = "creating";
-// //                 status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
-// //             }
-
-// //             if status != errSecSuccess {
-// //                 return Err(anyhow!("{} password failed: {}", verb, status));
-// //             }
-// //         }
-// //         Ok(())
-// //     }
-
-// //     fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
-// //         let url = CFString::from(url);
-// //         let cf_true = CFBoolean::true_value().as_CFTypeRef();
-
-// //         unsafe {
-// //             use security::*;
-
-// //             // Find any credentials for the given server URL.
-// //             let mut attrs = CFMutableDictionary::with_capacity(5);
-// //             attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-// //             attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-// //             attrs.set(kSecReturnAttributes as *const _, cf_true);
-// //             attrs.set(kSecReturnData as *const _, cf_true);
-
-// //             let mut result = CFTypeRef::from(ptr::null());
-// //             let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
-// //             match status {
-// //                 security::errSecSuccess => {}
-// //                 security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
-// //                 _ => return Err(anyhow!("reading password failed: {}", status)),
-// //             }
-
-// //             let result = CFType::wrap_under_create_rule(result)
-// //                 .downcast::<CFDictionary>()
-// //                 .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
-// //             let username = result
-// //                 .find(kSecAttrAccount as *const _)
-// //                 .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
-// //             let username = CFType::wrap_under_get_rule(*username)
-// //                 .downcast::<CFString>()
-// //                 .ok_or_else(|| anyhow!("account was not a string"))?;
-// //             let password = result
-// //                 .find(kSecValueData as *const _)
-// //                 .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
-// //             let password = CFType::wrap_under_get_rule(*password)
-// //                 .downcast::<CFData>()
-// //                 .ok_or_else(|| anyhow!("password was not a string"))?;
-
-// //             Ok(Some((username.to_string(), password.bytes().to_vec())))
-// //         }
-// //     }
-
-// //     fn delete_credentials(&self, url: &str) -> Result<()> {
-// //         let url = CFString::from(url);
-
-// //         unsafe {
-// //             use security::*;
-
-// //             let mut query_attrs = CFMutableDictionary::with_capacity(2);
-// //             query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
-// //             query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
-
-// //             let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
-
-// //             if status != errSecSuccess {
-// //                 return Err(anyhow!("delete password failed: {}", status));
-// //             }
-// //         }
-// //         Ok(())
-// //     }
-
-// //     fn set_cursor_style(&self, style: CursorStyle) {
-// //         unsafe {
-// //             let new_cursor: id = match style {
-// //                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
-// //                 CursorStyle::ResizeLeftRight => {
-// //                     msg_send![class!(NSCursor), resizeLeftRightCursor]
-// //                 }
-// //                 CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
-// //                 CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
-// //                 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
-// //             };
-
-// //             let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
-// //             if new_cursor != old_cursor {
-// //                 let _: () = msg_send![new_cursor, set];
-// //             }
-// //         }
-// //     }
-
-// //     fn should_auto_hide_scrollbars(&self) -> bool {
-// //         #[allow(non_upper_case_globals)]
-// //         const NSScrollerStyleOverlay: NSInteger = 1;
-
-// //         unsafe {
-// //             let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
-// //             style == NSScrollerStyleOverlay
-// //         }
-// //     }
-
-// //     fn local_timezone(&self) -> UtcOffset {
-// //         unsafe {
-// //             let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
-// //             let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
-// //             UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
-// //         }
-// //     }
-
-// //     fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
-// //         unsafe {
-// //             let bundle: id = NSBundle::mainBundle();
-// //             if bundle.is_null() {
-// //                 Err(anyhow!("app is not running inside a bundle"))
-// //             } else {
-// //                 let name = ns_string(name);
-// //                 let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
-// //                 if url.is_null() {
-// //                     Err(anyhow!("resource not found"))
-// //                 } else {
-// //                     ns_url_to_path(url)
-// //                 }
-// //             }
-// //         }
-// //     }
-
-// //     fn app_path(&self) -> Result<PathBuf> {
-// //         unsafe {
-// //             let bundle: id = NSBundle::mainBundle();
-// //             if bundle.is_null() {
-// //                 Err(anyhow!("app is not running inside a bundle"))
-// //             } else {
-// //                 Ok(path_from_objc(msg_send![bundle, bundlePath]))
-// //             }
-// //         }
-// //     }
-
-// //     fn app_version(&self) -> Result<platform::AppVersion> {
-// //         unsafe {
-// //             let bundle: id = NSBundle::mainBundle();
-// //             if bundle.is_null() {
-// //                 Err(anyhow!("app is not running inside a bundle"))
-// //             } else {
-// //                 let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
-// //                 let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
-// //                 let bytes = version.UTF8String() as *const u8;
-// //                 let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
-// //                 version.parse()
-// //             }
-// //         }
-// //     }
-
-// //     fn os_name(&self) -> &'static str {
-// //         "macOS"
-// //     }
-
-// //     fn os_version(&self) -> Result<crate::platform::AppVersion> {
-// //         unsafe {
-// //             let process_info = NSProcessInfo::processInfo(nil);
-// //             let version = process_info.operatingSystemVersion();
-// //             Ok(AppVersion {
-// //                 major: version.majorVersion as usize,
-// //                 minor: version.minorVersion as usize,
-// //                 patch: version.patchVersion as usize,
-// //             })
-// //         }
-// //     }
-
-// //     fn restart(&self) {
-// //         use std::os::unix::process::CommandExt as _;
-
-// //         let app_pid = std::process::id().to_string();
-// //         let app_path = self
-// //             .app_path()
-// //             .ok()
-// //             // When the app is not bundled, `app_path` returns the
-// //             // directory containing the executable. Disregard this
-// //             // and get the path to the executable itself.
-// //             .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
-// //             .unwrap_or_else(|| std::env::current_exe().unwrap());
-
-// //         // Wait until this process has exited and then re-open this path.
-// //         let script = r#"
-// //             while kill -0 $0 2> /dev/null; do
-// //                 sleep 0.1
-// //             done
-// //             open "$1"
-// //         "#;
-
-// //         let restart_process = Command::new("/bin/bash")
-// //             .arg("-c")
-// //             .arg(script)
-// //             .arg(app_pid)
-// //             .arg(app_path)
-// //             .process_group(0)
-// //             .spawn();
-
-// //         match restart_process {
-// //             Ok(_) => self.quit(),
-// //             Err(e) => log::error!("failed to spawn restart script: {:?}", e),
-// //         }
-// //     }
-// // }
-
-// unsafe fn path_from_objc(path: id) -> PathBuf {
-//     let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
-//     let bytes = path.UTF8String() as *const u8;
-//     let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
-//     PathBuf::from(path)
-// }
-
-// unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {
-//     let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
-//     assert!(!platform_ptr.is_null());
-//     &*(platform_ptr as *const MacForegroundPlatform)
-// }
-
-// extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
-//     unsafe {
-//         if let Some(event) = Event::from_native(native_event, None) {
-//             let platform = get_foreground_platform(this);
-//             if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
-//                 if callback(event) {
-//                     return;
-//                 }
-//             }
-//         }
-//         msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
-//     }
-// }
-
-// extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
-//     unsafe {
-//         let app: id = msg_send![APP_CLASS, sharedApplication];
-//         app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
-
-//         let platform = get_foreground_platform(this);
-//         let callback = platform.0.borrow_mut().finish_launching.take();
-//         if let Some(callback) = callback {
-//             callback();
-//         }
-//     }
-// }
-
-// extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
-//     if !has_open_windows {
-//         let platform = unsafe { get_foreground_platform(this) };
-//         if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
-//             callback();
-//         }
-//     }
-// }
-
-// extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
-//     let platform = unsafe { get_foreground_platform(this) };
-//     if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
-//         callback();
-//     }
-// }
-
-// extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
-//     let platform = unsafe { get_foreground_platform(this) };
-//     if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
-//         callback();
-//     }
-// }
-
-// extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
-//     let platform = unsafe { get_foreground_platform(this) };
-//     if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
-//         callback();
-//     }
-// }
-
-// extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
-//     let urls = unsafe {
-//         (0..urls.count())
-//             .into_iter()
-//             .filter_map(|i| {
-//                 let url = urls.objectAtIndex(i);
-//                 match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
-//                     Ok(string) => Some(string.to_string()),
-//                     Err(err) => {
-//                         log::error!("error converting path to string: {}", err);
-//                         None
-//                     }
-//                 }
-//             })
-//             .collect::<Vec<_>>()
-//     };
-//     let platform = unsafe { get_foreground_platform(this) };
-//     if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
-//         callback(urls);
-//     }
-// }
-
-// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
-//     unsafe {
-//         let platform = get_foreground_platform(this);
-//         let mut platform = platform.0.borrow_mut();
-//         if let Some(mut callback) = platform.menu_command.take() {
-//             let tag: NSInteger = msg_send![item, tag];
-//             let index = tag as usize;
-//             if let Some(action) = platform.menu_actions.get(index) {
-//                 callback(action.as_ref());
-//             }
-//             platform.menu_command = Some(callback);
-//         }
-//     }
-// }
-
-// extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
-//     unsafe {
-//         let mut result = false;
-//         let platform = get_foreground_platform(this);
-//         let mut platform = platform.0.borrow_mut();
-//         if let Some(mut callback) = platform.validate_menu_command.take() {
-//             let tag: NSInteger = msg_send![item, tag];
-//             let index = tag as usize;
-//             if let Some(action) = platform.menu_actions.get(index) {
-//                 result = callback(action.as_ref());
-//             }
-//             platform.validate_menu_command = Some(callback);
-//         }
-//         result
-//     }
-// }
-
-// extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
-//     unsafe {
-//         let platform = get_foreground_platform(this);
-//         let mut platform = platform.0.borrow_mut();
-//         if let Some(mut callback) = platform.will_open_menu.take() {
-//             callback();
-//             platform.will_open_menu = Some(callback);
-//         }
-//     }
-// }
-
-// unsafe fn ns_string(string: &str) -> id {
-//     NSString::alloc(nil).init_str(string).autorelease()
-// }
-
-// unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
-//     let path: *mut c_char = msg_send![url, fileSystemRepresentation];
-//     if path.is_null() {
-//         Err(anyhow!(
-//             "url is not a file path: {}",
-//             CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
-//         ))
-//     } else {
-//         Ok(PathBuf::from(OsStr::from_bytes(
-//             CStr::from_ptr(path).to_bytes(),
-//         )))
-//     }
-// }
-
-// mod security {
-//     #![allow(non_upper_case_globals)]
-//     use super::*;
-
-//     #[link(name = "Security", kind = "framework")]
-//     extern "C" {
-//         pub static kSecClass: CFStringRef;
-//         pub static kSecClassInternetPassword: CFStringRef;
-//         pub static kSecAttrServer: CFStringRef;
-//         pub static kSecAttrAccount: CFStringRef;
-//         pub static kSecValueData: CFStringRef;
-//         pub static kSecReturnAttributes: CFStringRef;
-//         pub static kSecReturnData: CFStringRef;
-
-//         pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
-//         pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
-//         pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
-//         pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
-//     }
-
-//     pub const errSecSuccess: OSStatus = 0;
-//     pub const errSecUserCanceled: OSStatus = -128;
-//     pub const errSecItemNotFound: OSStatus = -25300;
-// }
-
-// #[cfg(test)]
-// mod tests {
-//     use crate::platform::Platform;
-
-//     use super::*;
-
-//     #[test]
-//     fn test_clipboard() {
-//         let platform = build_platform();
-//         assert_eq!(platform.read_from_clipboard(), None);
-
-//         let item = ClipboardItem::new("1".to_string());
-//         platform.write_to_clipboard(item.clone());
-//         assert_eq!(platform.read_from_clipboard(), Some(item));
-
-//         let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
-//         platform.write_to_clipboard(item.clone());
-//         assert_eq!(platform.read_from_clipboard(), Some(item));
-
-//         let text_from_other_app = "text from other app";
-//         unsafe {
-//             let bytes = NSData::dataWithBytes_length_(
-//                 nil,
-//                 text_from_other_app.as_ptr() as *const c_void,
-//                 text_from_other_app.len() as u64,
-//             );
-//             platform
-//                 .pasteboard
-//                 .setData_forType(bytes, NSPasteboardTypeString);
-//         }
-//         assert_eq!(
-//             platform.read_from_clipboard(),
-//             Some(ClipboardItem::new(text_from_other_app.to_string()))
-//         );
-//     }
-
-//     fn build_platform() -> MacPlatform {
-//         let mut platform = MacPlatform::new();
-//         platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
-//         platform
-//     }
-// }
+use super::BoolExt;
+use crate::{
+    AnyWindowHandle, ClipboardItem, CursorStyle, Event, ForegroundExecutor, MacDispatcher,
+    MacScreen, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen,
+    PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
+};
+use anyhow::anyhow;
+use block::ConcreteBlock;
+use cocoa::{
+    appkit::{
+        NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+        NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
+    },
+    base::{id, nil, BOOL, YES},
+    foundation::{
+        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
+        NSUInteger, NSURL,
+    },
+};
+use core_foundation::{
+    base::{CFType, CFTypeRef, OSStatus, TCFType as _},
+    boolean::CFBoolean,
+    data::CFData,
+    dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
+    string::{CFString, CFStringRef},
+};
+use ctor::ctor;
+use futures::channel::oneshot;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Sel},
+    sel, sel_impl,
+};
+use ptr::null_mut;
+use std::{
+    cell::{Cell, RefCell},
+    convert::TryInto,
+    ffi::{c_void, CStr, OsStr},
+    os::{raw::c_char, unix::ffi::OsStrExt},
+    path::{Path, PathBuf},
+    process::Command,
+    ptr,
+    rc::Rc,
+    slice, str,
+    sync::Arc,
+};
+use time::UtcOffset;
+
+#[allow(non_upper_case_globals)]
+const NSUTF8StringEncoding: NSUInteger = 4;
+
+#[allow(non_upper_case_globals)]
+pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
+
+const MAC_PLATFORM_IVAR: &str = "platform";
+static mut APP_CLASS: *const Class = ptr::null();
+static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+    APP_CLASS = {
+        let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_method(
+            sel!(sendEvent:),
+            send_event as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.register()
+    };
+
+    APP_DELEGATE_CLASS = {
+        let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
+        decl.add_method(
+            sel!(applicationDidFinishLaunching:),
+            did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationShouldHandleReopen:hasVisibleWindows:),
+            should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
+        );
+        decl.add_method(
+            sel!(applicationDidBecomeActive:),
+            did_become_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationDidResignActive:),
+            did_resign_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationWillTerminate:),
+            will_terminate as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(handleGPUIMenuItem:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        // Add menu item handlers so that OS save panels have the correct key commands
+        decl.add_method(
+            sel!(cut:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(copy:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(paste:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(selectAll:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(undo:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(redo:),
+            handle_menu_item as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(validateMenuItem:),
+            validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
+        );
+        decl.add_method(
+            sel!(menuWillOpen:),
+            menu_will_open as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(application:openURLs:),
+            open_urls as extern "C" fn(&mut Object, Sel, id, id),
+        );
+        decl.register()
+    }
+}
+
+pub struct MacPlatform(RefCell<MacPlatformState>);
+
+pub struct MacPlatformState {
+    executor: Rc<ForegroundExecutor>,
+    text_system: Arc<MacTextSystem>,
+    pasteboard: id,
+    text_hash_pasteboard_type: id,
+    metadata_pasteboard_type: id,
+    become_active: Option<Box<dyn FnMut()>>,
+    resign_active: Option<Box<dyn FnMut()>>,
+    reopen: Option<Box<dyn FnMut()>>,
+    quit: Option<Box<dyn FnMut()>>,
+    event: Option<Box<dyn FnMut(Event) -> bool>>,
+    // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
+    // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
+    will_open_menu: Option<Box<dyn FnMut()>>,
+    open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
+    finish_launching: Option<Box<dyn FnOnce()>>,
+    // menu_actions: Vec<Box<dyn Action>>,
+}
+
+impl MacPlatform {
+    pub fn new() -> Self {
+        Self(RefCell::new(MacPlatformState {
+            executor: Rc::new(ForegroundExecutor::new(Arc::new(MacDispatcher)).unwrap()),
+            text_system: Arc::new(MacTextSystem::new()),
+            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
+            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
+            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
+            become_active: None,
+            resign_active: None,
+            reopen: None,
+            quit: None,
+            event: None,
+            will_open_menu: None,
+            open_urls: None,
+            finish_launching: None,
+            // menu_command: None,
+            // validate_menu_command: None,
+            // menu_actions: Default::default(),
+        }))
+    }
+
+    unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
+        let pasteboard = self.0.borrow().pasteboard;
+        let data = pasteboard.dataForType(kind);
+        if data == nil {
+            None
+        } else {
+            Some(slice::from_raw_parts(
+                data.bytes() as *mut u8,
+                data.length() as usize,
+            ))
+        }
+    }
+
+    // unsafe fn create_menu_bar(
+    //     &self,
+    //     menus: Vec<Menu>,
+    //     delegate: id,
+    //     actions: &mut Vec<Box<dyn Action>>,
+    //     keystroke_matcher: &KeymapMatcher,
+    // ) -> id {
+    //     let application_menu = NSMenu::new(nil).autorelease();
+    //     application_menu.setDelegate_(delegate);
+
+    //     for menu_config in menus {
+    //         let menu = NSMenu::new(nil).autorelease();
+    //         menu.setTitle_(ns_string(menu_config.name));
+    //         menu.setDelegate_(delegate);
+
+    //         for item_config in menu_config.items {
+    //             menu.addItem_(self.create_menu_item(
+    //                 item_config,
+    //                 delegate,
+    //                 actions,
+    //                 keystroke_matcher,
+    //             ));
+    //         }
+
+    //         let menu_item = NSMenuItem::new(nil).autorelease();
+    //         menu_item.setSubmenu_(menu);
+    //         application_menu.addItem_(menu_item);
+
+    //         if menu_config.name == "Window" {
+    //             let app: id = msg_send![APP_CLASS, sharedApplication];
+    //             app.setWindowsMenu_(menu);
+    //         }
+    //     }
+
+    //     application_menu
+    // }
+
+    // unsafe fn create_menu_item(
+    //     &self,
+    //     item: MenuItem,
+    //     delegate: id,
+    //     actions: &mut Vec<Box<dyn Action>>,
+    //     keystroke_matcher: &KeymapMatcher,
+    // ) -> id {
+    //     match item {
+    //         MenuItem::Separator => NSMenuItem::separatorItem(nil),
+    //         MenuItem::Action {
+    //             name,
+    //             action,
+    //             os_action,
+    //         } => {
+    //             // TODO
+    //             let keystrokes = keystroke_matcher
+    //                 .bindings_for_action(action.id())
+    //                 .find(|binding| binding.action().eq(action.as_ref()))
+    //                 .map(|binding| binding.keystrokes());
+    //             let selector = match os_action {
+    //                 Some(crate::OsAction::Cut) => selector("cut:"),
+    //                 Some(crate::OsAction::Copy) => selector("copy:"),
+    //                 Some(crate::OsAction::Paste) => selector("paste:"),
+    //                 Some(crate::OsAction::SelectAll) => selector("selectAll:"),
+    //                 Some(crate::OsAction::Undo) => selector("undo:"),
+    //                 Some(crate::OsAction::Redo) => selector("redo:"),
+    //                 None => selector("handleGPUIMenuItem:"),
+    //             };
+
+    //             let item;
+    //             if let Some(keystrokes) = keystrokes {
+    //                 if keystrokes.len() == 1 {
+    //                     let keystroke = &keystrokes[0];
+    //                     let mut mask = NSEventModifierFlags::empty();
+    //                     for (modifier, flag) in &[
+    //                         (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
+    //                         (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
+    //                         (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
+    //                         (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
+    //                     ] {
+    //                         if *modifier {
+    //                             mask |= *flag;
+    //                         }
+    //                     }
+
+    //                     item = NSMenuItem::alloc(nil)
+    //                         .initWithTitle_action_keyEquivalent_(
+    //                             ns_string(name),
+    //                             selector,
+    //                             ns_string(key_to_native(&keystroke.key).as_ref()),
+    //                         )
+    //                         .autorelease();
+    //                     item.setKeyEquivalentModifierMask_(mask);
+    //                 }
+    //                 // For multi-keystroke bindings, render the keystroke as part of the title.
+    //                 else {
+    //                     use std::fmt::Write;
+
+    //                     let mut name = format!("{name} [");
+    //                     for (i, keystroke) in keystrokes.iter().enumerate() {
+    //                         if i > 0 {
+    //                             name.push(' ');
+    //                         }
+    //                         write!(&mut name, "{}", keystroke).unwrap();
+    //                     }
+    //                     name.push(']');
+
+    //                     item = NSMenuItem::alloc(nil)
+    //                         .initWithTitle_action_keyEquivalent_(
+    //                             ns_string(&name),
+    //                             selector,
+    //                             ns_string(""),
+    //                         )
+    //                         .autorelease();
+    //                 }
+    //             } else {
+    //                 item = NSMenuItem::alloc(nil)
+    //                     .initWithTitle_action_keyEquivalent_(
+    //                         ns_string(name),
+    //                         selector,
+    //                         ns_string(""),
+    //                     )
+    //                     .autorelease();
+    //             }
+
+    //             let tag = actions.len() as NSInteger;
+    //             let _: () = msg_send![item, setTag: tag];
+    //             actions.push(action);
+    //             item
+    //         }
+    //         MenuItem::Submenu(Menu { name, items }) => {
+    //             let item = NSMenuItem::new(nil).autorelease();
+    //             let submenu = NSMenu::new(nil).autorelease();
+    //             submenu.setDelegate_(delegate);
+    //             for item in items {
+    //                 submenu.addItem_(self.create_menu_item(
+    //                     item,
+    //                     delegate,
+    //                     actions,
+    //                     keystroke_matcher,
+    //                 ));
+    //             }
+    //             item.setSubmenu_(submenu);
+    //             item.setTitle_(ns_string(name));
+    //             item
+    //         }
+    //     }
+    // }
+}
+
+impl Platform for MacPlatform {
+    fn executor(&self) -> Rc<ForegroundExecutor> {
+        self.0.borrow().executor.clone()
+    }
+
+    fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
+        self.0.borrow().text_system.clone()
+    }
+
+    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
+        self.0.borrow_mut().finish_launching = Some(on_finish_launching);
+
+        unsafe {
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+            app.setDelegate_(app_delegate);
+
+            let self_ptr = self as *const Self as *const c_void;
+            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
+
+            let pool = NSAutoreleasePool::new(nil);
+            app.run();
+            pool.drain();
+
+            (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+            (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
+        }
+    }
+
+    fn quit(&self) {
+        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
+        // synchronously before this method terminates. If we call `Platform::quit` while holding a
+        // borrow of the app state (which most of the time we will do), we will end up
+        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
+        // this, we make quitting the application asynchronous so that we aren't holding borrows to
+        // the app state on the stack when we actually terminate the app.
+
+        use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
+
+        unsafe {
+            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
+        }
+
+        unsafe extern "C" fn quit(_: *mut c_void) {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, terminate: nil];
+        }
+    }
+
+    fn restart(&self) {
+        use std::os::unix::process::CommandExt as _;
+
+        let app_pid = std::process::id().to_string();
+        let app_path = self
+            .app_path()
+            .ok()
+            // When the app is not bundled, `app_path` returns the
+            // directory containing the executable. Disregard this
+            // and get the path to the executable itself.
+            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
+            .unwrap_or_else(|| std::env::current_exe().unwrap());
+
+        // Wait until this process has exited and then re-open this path.
+        let script = r#"
+            while kill -0 $0 2> /dev/null; do
+                sleep 0.1
+            done
+            open "$1"
+        "#;
+
+        let restart_process = Command::new("/bin/bash")
+            .arg("-c")
+            .arg(script)
+            .arg(app_pid)
+            .arg(app_path)
+            .process_group(0)
+            .spawn();
+
+        match restart_process {
+            Ok(_) => self.quit(),
+            Err(e) => log::error!("failed to spawn restart script: {:?}", e),
+        }
+    }
+
+    fn activate(&self, ignoring_other_apps: bool) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
+        }
+    }
+
+    fn hide(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hide: nil];
+        }
+    }
+
+    fn hide_other_apps(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, hideOtherApplications: nil];
+        }
+    }
+
+    fn unhide_other_apps(&self) {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let _: () = msg_send![app, unhideAllApplications: nil];
+        }
+    }
+
+    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
+        MacScreen::all()
+            .into_iter()
+            .map(|screen| Rc::new(screen) as Rc<_>)
+            .collect()
+    }
+
+    fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>> {
+        MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    }
+
+    fn main_window(&self) -> Option<AnyWindowHandle> {
+        MacWindow::main_window()
+    }
+
+    // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
+    //     Box::new(StatusItem::add(self.fonts()))
+    // }
+
+    fn open_window(
+        &self,
+        handle: AnyWindowHandle,
+        options: WindowOptions,
+    ) -> Box<dyn PlatformWindow> {
+        Box::new(MacWindow::open(
+            handle,
+            options,
+            self.executor(),
+            self.text_system(),
+        ))
+    }
+
+    fn open_url(&self, url: &str) {
+        unsafe {
+            let url = NSURL::alloc(nil)
+                .initWithString_(ns_string(url))
+                .autorelease();
+            let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+            msg_send![workspace, openURL: url]
+        }
+    }
+
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+        self.0.borrow_mut().open_urls = Some(callback);
+    }
+
+    fn prompt_for_paths(
+        &self,
+        options: PathPromptOptions,
+    ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
+        unsafe {
+            let panel = NSOpenPanel::openPanel(nil);
+            panel.setCanChooseDirectories_(options.directories.to_objc());
+            panel.setCanChooseFiles_(options.files.to_objc());
+            panel.setAllowsMultipleSelection_(options.multiple.to_objc());
+            panel.setResolvesAliases_(false.to_objc());
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                let result = if response == NSModalResponse::NSModalResponseOk {
+                    let mut result = Vec::new();
+                    let urls = panel.URLs();
+                    for i in 0..urls.count() {
+                        let url = urls.objectAtIndex(i);
+                        if url.isFileURL() == YES {
+                            if let Ok(path) = ns_url_to_path(url) {
+                                result.push(path)
+                            }
+                        }
+                    }
+                    Some(result)
+                } else {
+                    None
+                };
+
+                if let Some(mut done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
+                }
+            });
+            let block = block.copy();
+            let _: () = msg_send![panel, beginWithCompletionHandler: block];
+            done_rx
+        }
+    }
+
+    fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
+        unsafe {
+            let panel = NSSavePanel::savePanel(nil);
+            let path = ns_string(directory.to_string_lossy().as_ref());
+            let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
+            panel.setDirectoryURL(url);
+
+            let (done_tx, done_rx) = oneshot::channel();
+            let done_tx = Cell::new(Some(done_tx));
+            let block = ConcreteBlock::new(move |response: NSModalResponse| {
+                let mut result = None;
+                if response == NSModalResponse::NSModalResponseOk {
+                    let url = panel.URL();
+                    if url.isFileURL() == YES {
+                        result = ns_url_to_path(panel.URL()).ok()
+                    }
+                }
+
+                if let Some(mut done_tx) = done_tx.take() {
+                    let _ = done_tx.send(result);
+                }
+            });
+            let block = block.copy();
+            let _: () = msg_send![panel, beginWithCompletionHandler: block];
+            done_rx
+        }
+    }
+
+    fn reveal_path(&self, path: &Path) {
+        unsafe {
+            let path = path.to_path_buf();
+            self.0
+                .borrow()
+                .executor
+                .spawn(async move {
+                    let full_path = ns_string(path.to_str().unwrap_or(""));
+                    let root_full_path = ns_string("");
+                    let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
+                    let _: BOOL = msg_send![
+                        workspace,
+                        selectFile: full_path
+                        inFileViewerRootedAtPath: root_full_path
+                    ];
+                })
+                .detach();
+        }
+    }
+
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().become_active = Some(callback);
+    }
+
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().resign_active = Some(callback);
+    }
+
+    fn on_quit(&self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().quit = Some(callback);
+    }
+
+    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+        self.0.borrow_mut().reopen = Some(callback);
+    }
+
+    fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
+        self.0.borrow_mut().event = Some(callback);
+    }
+
+    fn os_name(&self) -> &'static str {
+        "macOS"
+    }
+
+    fn os_version(&self) -> Result<SemanticVersion> {
+        unsafe {
+            let process_info = NSProcessInfo::processInfo(nil);
+            let version = process_info.operatingSystemVersion();
+            Ok(SemanticVersion {
+                major: version.majorVersion as usize,
+                minor: version.minorVersion as usize,
+                patch: version.patchVersion as usize,
+            })
+        }
+    }
+
+    fn app_version(&self) -> Result<SemanticVersion> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
+                let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+                let bytes = version.UTF8String() as *const u8;
+                let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+                version.parse()
+            }
+        }
+    }
+
+    fn app_path(&self) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                Ok(path_from_objc(msg_send![bundle, bundlePath]))
+            }
+        }
+    }
+
+    fn local_timezone(&self) -> UtcOffset {
+        unsafe {
+            let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
+            let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
+            UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
+        }
+    }
+
+    fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
+        unsafe {
+            let bundle: id = NSBundle::mainBundle();
+            if bundle.is_null() {
+                Err(anyhow!("app is not running inside a bundle"))
+            } else {
+                let name = ns_string(name);
+                let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
+                if url.is_null() {
+                    Err(anyhow!("resource not found"))
+                } else {
+                    ns_url_to_path(url)
+                }
+            }
+        }
+    }
+
+    fn set_cursor_style(&self, style: CursorStyle) {
+        unsafe {
+            let new_cursor: id = match style {
+                CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
+                CursorStyle::ResizeLeftRight => {
+                    msg_send![class!(NSCursor), resizeLeftRightCursor]
+                }
+                CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
+                CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
+                CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
+            };
+
+            let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
+            if new_cursor != old_cursor {
+                let _: () = msg_send![new_cursor, set];
+            }
+        }
+    }
+
+    fn should_auto_hide_scrollbars(&self) -> bool {
+        #[allow(non_upper_case_globals)]
+        const NSScrollerStyleOverlay: NSInteger = 1;
+
+        unsafe {
+            let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
+            style == NSScrollerStyleOverlay
+        }
+    }
+
+    fn write_to_clipboard(&self, item: ClipboardItem) {
+        let state = self.0.borrow();
+        unsafe {
+            state.pasteboard.clearContents();
+
+            let text_bytes = NSData::dataWithBytes_length_(
+                nil,
+                item.text.as_ptr() as *const c_void,
+                item.text.len() as u64,
+            );
+            state
+                .pasteboard
+                .setData_forType(text_bytes, NSPasteboardTypeString);
+
+            if let Some(metadata) = item.metadata.as_ref() {
+                let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
+                let hash_bytes = NSData::dataWithBytes_length_(
+                    nil,
+                    hash_bytes.as_ptr() as *const c_void,
+                    hash_bytes.len() as u64,
+                );
+                state
+                    .pasteboard
+                    .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
+
+                let metadata_bytes = NSData::dataWithBytes_length_(
+                    nil,
+                    metadata.as_ptr() as *const c_void,
+                    metadata.len() as u64,
+                );
+                state
+                    .pasteboard
+                    .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
+            }
+        }
+    }
+
+    fn read_from_clipboard(&self) -> Option<ClipboardItem> {
+        let state = self.0.borrow();
+        unsafe {
+            if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
+                let text = String::from_utf8_lossy(text_bytes).to_string();
+                let hash_bytes = self
+                    .read_from_pasteboard(state.text_hash_pasteboard_type)
+                    .and_then(|bytes| bytes.try_into().ok())
+                    .map(u64::from_be_bytes);
+                let metadata_bytes = self
+                    .read_from_pasteboard(state.metadata_pasteboard_type)
+                    .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
+
+                if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
+                    if hash == ClipboardItem::text_hash(&text) {
+                        Some(ClipboardItem {
+                            text,
+                            metadata: Some(metadata),
+                        })
+                    } else {
+                        Some(ClipboardItem {
+                            text,
+                            metadata: None,
+                        })
+                    }
+                } else {
+                    Some(ClipboardItem {
+                        text,
+                        metadata: None,
+                    })
+                }
+            } else {
+                None
+            }
+        }
+    }
+
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
+        let url = CFString::from(url);
+        let username = CFString::from(username);
+        let password = CFData::from_buffer(password);
+
+        unsafe {
+            use security::*;
+
+            // First, check if there are already credentials for the given server. If so, then
+            // update the username and password.
+            let mut verb = "updating";
+            let mut query_attrs = CFMutableDictionary::with_capacity(2);
+            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+            let mut attrs = CFMutableDictionary::with_capacity(4);
+            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+            attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
+            attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
+
+            let mut status = SecItemUpdate(
+                query_attrs.as_concrete_TypeRef(),
+                attrs.as_concrete_TypeRef(),
+            );
+
+            // If there were no existing credentials for the given server, then create them.
+            if status == errSecItemNotFound {
+                verb = "creating";
+                status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
+            }
+
+            if status != errSecSuccess {
+                return Err(anyhow!("{} password failed: {}", verb, status));
+            }
+        }
+        Ok(())
+    }
+
+    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+    //     self.0.borrow_mut().menu_command = Some(callback);
+    // }
+
+    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+    //     self.0.borrow_mut().will_open_menu = Some(callback);
+    // }
+
+    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+    //     self.0.borrow_mut().validate_menu_command = Some(callback);
+    // }
+
+    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+    //     unsafe {
+    //         let app: id = msg_send![APP_CLASS, sharedApplication];
+    //         let mut state = self.0.borrow_mut();
+    //         let actions = &mut state.menu_actions;
+    //         app.setMainMenu_(self.create_menu_bar(
+    //             menus,
+    //             app.delegate(),
+    //             actions,
+    //             keystroke_matcher,
+    //         ));
+    //     }
+    // }
+
+    fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
+        let url = CFString::from(url);
+        let cf_true = CFBoolean::true_value().as_CFTypeRef();
+
+        unsafe {
+            use security::*;
+
+            // Find any credentials for the given server URL.
+            let mut attrs = CFMutableDictionary::with_capacity(5);
+            attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+            attrs.set(kSecReturnAttributes as *const _, cf_true);
+            attrs.set(kSecReturnData as *const _, cf_true);
+
+            let mut result = CFTypeRef::from(ptr::null());
+            let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
+            match status {
+                security::errSecSuccess => {}
+                security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
+                _ => return Err(anyhow!("reading password failed: {}", status)),
+            }
+
+            let result = CFType::wrap_under_create_rule(result)
+                .downcast::<CFDictionary>()
+                .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
+            let username = result
+                .find(kSecAttrAccount as *const _)
+                .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
+            let username = CFType::wrap_under_get_rule(*username)
+                .downcast::<CFString>()
+                .ok_or_else(|| anyhow!("account was not a string"))?;
+            let password = result
+                .find(kSecValueData as *const _)
+                .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
+            let password = CFType::wrap_under_get_rule(*password)
+                .downcast::<CFData>()
+                .ok_or_else(|| anyhow!("password was not a string"))?;
+
+            Ok(Some((username.to_string(), password.bytes().to_vec())))
+        }
+    }
+
+    fn delete_credentials(&self, url: &str) -> Result<()> {
+        let url = CFString::from(url);
+
+        unsafe {
+            use security::*;
+
+            let mut query_attrs = CFMutableDictionary::with_capacity(2);
+            query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
+            query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
+
+            let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
+
+            if status != errSecSuccess {
+                return Err(anyhow!("delete password failed: {}", status));
+            }
+        }
+        Ok(())
+    }
+}
+
+unsafe fn path_from_objc(path: id) -> PathBuf {
+    let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+    let bytes = path.UTF8String() as *const u8;
+    let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
+    PathBuf::from(path)
+}
+
+unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
+    let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
+    assert!(!platform_ptr.is_null());
+    &*(platform_ptr as *const MacPlatform)
+}
+
+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+    unsafe {
+        if let Some(event) = Event::from_native(native_event, None) {
+            let platform = get_foreground_platform(this);
+            if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
+                if callback(event) {
+                    return;
+                }
+            }
+        }
+        msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
+    }
+}
+
+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+    unsafe {
+        let app: id = msg_send![APP_CLASS, sharedApplication];
+        app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
+
+        let platform = get_foreground_platform(this);
+        let callback = platform.0.borrow_mut().finish_launching.take();
+        if let Some(callback) = callback {
+            callback();
+        }
+    }
+}
+
+extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
+    if !has_open_windows {
+        let platform = unsafe { get_foreground_platform(this) };
+        if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
+            callback();
+        }
+    }
+}
+
+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
+    let urls = unsafe {
+        (0..urls.count())
+            .into_iter()
+            .filter_map(|i| {
+                let url = urls.objectAtIndex(i);
+                match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
+                    Ok(string) => Some(string.to_string()),
+                    Err(err) => {
+                        log::error!("error converting path to string: {}", err);
+                        None
+                    }
+                }
+            })
+            .collect::<Vec<_>>()
+    };
+    let platform = unsafe { get_foreground_platform(this) };
+    if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
+        callback(urls);
+    }
+}
+
+extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
+    todo!()
+    // unsafe {
+    //     let platform = get_foreground_platform(this);
+    //     let mut platform = platform.0.borrow_mut();
+    //     if let Some(mut callback) = platform.menu_command.take() {
+    //         let tag: NSInteger = msg_send![item, tag];
+    //         let index = tag as usize;
+    //         if let Some(action) = platform.menu_actions.get(index) {
+    //             callback(action.as_ref());
+    //         }
+    //         platform.menu_command = Some(callback);
+    //     }
+    // }
+}
+
+extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
+    todo!()
+    // unsafe {
+    //     let mut result = false;
+    //     let platform = get_foreground_platform(this);
+    //     let mut platform = platform.0.borrow_mut();
+    //     if let Some(mut callback) = platform.validate_menu_command.take() {
+    //         let tag: NSInteger = msg_send![item, tag];
+    //         let index = tag as usize;
+    //         if let Some(action) = platform.menu_actions.get(index) {
+    //             result = callback(action.as_ref());
+    //         }
+    //         platform.validate_menu_command = Some(callback);
+    //     }
+    //     result
+    // }
+}
+
+extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
+    unsafe {
+        let platform = get_foreground_platform(this);
+        let mut platform = platform.0.borrow_mut();
+        if let Some(mut callback) = platform.will_open_menu.take() {
+            callback();
+            platform.will_open_menu = Some(callback);
+        }
+    }
+}
+
+unsafe fn ns_string(string: &str) -> id {
+    NSString::alloc(nil).init_str(string).autorelease()
+}
+
+unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
+    let path: *mut c_char = msg_send![url, fileSystemRepresentation];
+    if path.is_null() {
+        Err(anyhow!(
+            "url is not a file path: {}",
+            CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
+        ))
+    } else {
+        Ok(PathBuf::from(OsStr::from_bytes(
+            CStr::from_ptr(path).to_bytes(),
+        )))
+    }
+}
+
+mod security {
+    #![allow(non_upper_case_globals)]
+    use super::*;
+
+    #[link(name = "Security", kind = "framework")]
+    extern "C" {
+        pub static kSecClass: CFStringRef;
+        pub static kSecClassInternetPassword: CFStringRef;
+        pub static kSecAttrServer: CFStringRef;
+        pub static kSecAttrAccount: CFStringRef;
+        pub static kSecValueData: CFStringRef;
+        pub static kSecReturnAttributes: CFStringRef;
+        pub static kSecReturnData: CFStringRef;
+
+        pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+        pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
+        pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
+        pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
+    }
+
+    pub const errSecSuccess: OSStatus = 0;
+    pub const errSecUserCanceled: OSStatus = -128;
+    pub const errSecItemNotFound: OSStatus = -25300;
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ClipboardItem;
+
+    use super::*;
+
+    #[test]
+    fn test_clipboard() {
+        let platform = build_platform();
+        assert_eq!(platform.read_from_clipboard(), None);
+
+        let item = ClipboardItem::new("1".to_string());
+        platform.write_to_clipboard(item.clone());
+        assert_eq!(platform.read_from_clipboard(), Some(item));
+
+        let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
+        platform.write_to_clipboard(item.clone());
+        assert_eq!(platform.read_from_clipboard(), Some(item));
+
+        let text_from_other_app = "text from other app";
+        unsafe {
+            let bytes = NSData::dataWithBytes_length_(
+                nil,
+                text_from_other_app.as_ptr() as *const c_void,
+                text_from_other_app.len() as u64,
+            );
+            platform
+                .0
+                .borrow_mut()
+                .pasteboard
+                .setData_forType(bytes, NSPasteboardTypeString);
+        }
+        assert_eq!(
+            platform.read_from_clipboard(),
+            Some(ClipboardItem::new(text_from_other_app.to_string()))
+        );
+    }
+
+    fn build_platform() -> MacPlatform {
+        let mut platform = MacPlatform::new();
+        platform.0.borrow_mut().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
+        platform
+    }
+}

crates/gpui3/src/platform/mac/text_system.rs πŸ”—

@@ -0,0 +1,757 @@
+use crate::{
+    point, px, size, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
+    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Run, RunStyle,
+    Size,
+};
+use cocoa::appkit::{CGFloat, CGPoint};
+use collections::HashMap;
+use core_foundation::{
+    array::CFIndex,
+    attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
+    base::{CFRange, TCFType},
+    string::CFString,
+};
+use core_graphics::{
+    base::{kCGImageAlphaPremultipliedLast, CGGlyph},
+    color_space::CGColorSpace,
+    context::CGContext,
+};
+use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
+use font_kit::{
+    handle::Handle, hinting::HintingOptions, metrics::Metrics, source::SystemSource,
+    sources::mem::MemSource,
+};
+use parking_lot::RwLock;
+use pathfinder_geometry::{
+    rect::{RectF, RectI},
+    transform2d::Transform2F,
+    vector::{Vector2F, Vector2I},
+};
+use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
+
+use super::open_type;
+
+#[allow(non_upper_case_globals)]
+const kCGImageAlphaOnly: u32 = 7;
+
+pub struct MacTextSystem(RwLock<TextSystemState>);
+
+struct TextSystemState {
+    memory_source: MemSource,
+    system_source: SystemSource,
+    fonts: Vec<font_kit::font::Font>,
+    font_ids_by_postscript_name: HashMap<String, FontId>,
+    postscript_names_by_font_id: HashMap<FontId, String>,
+}
+
+impl MacTextSystem {
+    pub fn new() -> Self {
+        Self(RwLock::new(TextSystemState {
+            memory_source: MemSource::empty(),
+            system_source: SystemSource::new(),
+            fonts: Vec::new(),
+            font_ids_by_postscript_name: Default::default(),
+            postscript_names_by_font_id: Default::default(),
+        }))
+    }
+}
+
+impl Default for MacTextSystem {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl PlatformTextSystem for MacTextSystem {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
+        self.0.write().add_fonts(fonts)
+    }
+
+    fn all_families(&self) -> Vec<String> {
+        self.0
+            .read()
+            .system_source
+            .all_families()
+            .expect("core text should never return an error")
+    }
+
+    fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
+        self.0.write().load_family(name, features)
+    }
+
+    fn select_font(
+        &self,
+        font_ids: &[FontId],
+        weight: FontWeight,
+        style: FontStyle,
+    ) -> anyhow::Result<FontId> {
+        self.0.read().select_font(font_ids, weight, style)
+    }
+
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
+        self.0.read().font_metrics(font_id)
+    }
+
+    fn typographic_bounds(
+        &self,
+        font_id: FontId,
+        glyph_id: GlyphId,
+    ) -> anyhow::Result<Bounds<f32>> {
+        self.0.read().typographic_bounds(font_id, glyph_id)
+    }
+
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
+        self.0.read().advance(font_id, glyph_id)
+    }
+
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
+        self.0.read().glyph_for_char(font_id, ch)
+    }
+
+    fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        subpixel_shift: Point<Pixels>,
+        scale_factor: f32,
+        options: RasterizationOptions,
+    ) -> Option<(Bounds<u32>, Vec<u8>)> {
+        self.0.read().rasterize_glyph(
+            font_id,
+            font_size,
+            glyph_id,
+            subpixel_shift,
+            scale_factor,
+            options,
+        )
+    }
+
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout {
+        self.0.write().layout_line(text, font_size, runs)
+    }
+
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize> {
+        self.0.read().wrap_line(text, font_id, font_size, width)
+    }
+}
+
+impl TextSystemState {
+    fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
+        self.memory_source.add_fonts(
+            fonts
+                .iter()
+                .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
+        )?;
+        Ok(())
+    }
+
+    fn load_family(&mut self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
+        let mut font_ids = Vec::new();
+
+        let family = self
+            .memory_source
+            .select_family_by_name(name)
+            .or_else(|_| self.system_source.select_family_by_name(name))?;
+        for font in family.fonts() {
+            let mut font = font.load()?;
+            open_type::apply_features(&mut font, features);
+            let font_id = FontId(self.fonts.len());
+            font_ids.push(font_id);
+            let postscript_name = font.postscript_name().unwrap();
+            self.font_ids_by_postscript_name
+                .insert(postscript_name.clone(), font_id);
+            self.postscript_names_by_font_id
+                .insert(font_id, postscript_name);
+            self.fonts.push(font);
+        }
+        Ok(font_ids)
+    }
+
+    fn select_font(
+        &self,
+        font_ids: &[FontId],
+        weight: FontWeight,
+        style: FontStyle,
+    ) -> anyhow::Result<FontId> {
+        let candidates = font_ids
+            .iter()
+            .map(|font_id| self.fonts[font_id.0].properties())
+            .collect::<Vec<_>>();
+        let idx = font_kit::matching::find_best_match(
+            &candidates,
+            &font_kit::properties::Properties {
+                style,
+                weight,
+                stretch: Default::default(),
+            },
+        )?;
+        Ok(font_ids[idx])
+    }
+
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
+        self.fonts[font_id.0].metrics().into()
+    }
+
+    fn typographic_bounds(
+        &self,
+        font_id: FontId,
+        glyph_id: GlyphId,
+    ) -> anyhow::Result<Bounds<f32>> {
+        Ok(self.fonts[font_id.0]
+            .typographic_bounds(glyph_id.into())?
+            .into())
+    }
+
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
+        Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
+    }
+
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
+        self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
+    }
+
+    fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
+        let postscript_name = requested_font.postscript_name();
+        if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
+            *font_id
+        } else {
+            let font_id = FontId(self.fonts.len());
+            self.font_ids_by_postscript_name
+                .insert(postscript_name.clone(), font_id);
+            self.postscript_names_by_font_id
+                .insert(font_id, postscript_name);
+            self.fonts
+                .push(font_kit::font::Font::from_core_graphics_font(
+                    requested_font.copy_to_CGFont(),
+                ));
+            font_id
+        }
+    }
+
+    fn is_emoji(&self, font_id: FontId) -> bool {
+        self.postscript_names_by_font_id
+            .get(&font_id)
+            .map_or(false, |postscript_name| {
+                postscript_name == "AppleColorEmoji"
+            })
+    }
+
+    fn rasterize_glyph(
+        &self,
+        font_id: FontId,
+        font_size: f32,
+        glyph_id: GlyphId,
+        subpixel_shift: Point<Pixels>,
+        scale_factor: f32,
+        options: RasterizationOptions,
+    ) -> Option<(Bounds<u32>, Vec<u8>)> {
+        let font = &self.fonts[font_id.0];
+        let scale = Transform2F::from_scale(scale_factor);
+        let glyph_bounds = font
+            .raster_bounds(
+                glyph_id.into(),
+                font_size,
+                scale,
+                HintingOptions::None,
+                font_kit::canvas::RasterizationOptions::GrayscaleAa,
+            )
+            .ok()?;
+
+        if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
+            None
+        } else {
+            // Make room for subpixel variants.
+            let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
+            let cx_bounds = RectI::new(
+                glyph_bounds.origin(),
+                glyph_bounds.size() + Vector2I::from(subpixel_padding),
+            );
+
+            let mut bytes;
+            let cx;
+            match options {
+                RasterizationOptions::Alpha => {
+                    bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
+                    cx = CGContext::create_bitmap_context(
+                        Some(bytes.as_mut_ptr() as *mut _),
+                        cx_bounds.width() as usize,
+                        cx_bounds.height() as usize,
+                        8,
+                        cx_bounds.width() as usize,
+                        &CGColorSpace::create_device_gray(),
+                        kCGImageAlphaOnly,
+                    );
+                }
+                RasterizationOptions::Bgra => {
+                    bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
+                    cx = CGContext::create_bitmap_context(
+                        Some(bytes.as_mut_ptr() as *mut _),
+                        cx_bounds.width() as usize,
+                        cx_bounds.height() as usize,
+                        8,
+                        cx_bounds.width() as usize * 4,
+                        &CGColorSpace::create_device_rgb(),
+                        kCGImageAlphaPremultipliedLast,
+                    );
+                }
+            }
+
+            // Move the origin to bottom left and account for scaling, this
+            // makes drawing text consistent with the font-kit's raster_bounds.
+            cx.translate(
+                -glyph_bounds.origin_x() as CGFloat,
+                (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
+            );
+            cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
+
+            cx.set_allows_font_subpixel_positioning(true);
+            cx.set_should_subpixel_position_fonts(true);
+            cx.set_allows_font_subpixel_quantization(false);
+            cx.set_should_subpixel_quantize_fonts(false);
+            font.native_font()
+                .clone_with_font_size(font_size as CGFloat)
+                .draw_glyphs(
+                    &[u32::from(glyph_id) as CGGlyph],
+                    &[CGPoint::new(
+                        (f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
+                        (f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
+                    )],
+                    cx,
+                );
+
+            if let RasterizationOptions::Bgra = options {
+                // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
+                for pixel in bytes.chunks_exact_mut(4) {
+                    pixel.swap(0, 2);
+                    let a = pixel[3] as f32 / 255.;
+                    pixel[0] = (pixel[0] as f32 / a) as u8;
+                    pixel[1] = (pixel[1] as f32 / a) as u8;
+                    pixel[2] = (pixel[2] as f32 / a) as u8;
+                }
+            }
+
+            Some((cx_bounds.into(), bytes))
+        }
+    }
+
+    fn layout_line(
+        &mut self,
+        text: &str,
+        font_size: Pixels,
+        runs: &[(usize, RunStyle)],
+    ) -> LineLayout {
+        // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
+        let mut string = CFMutableAttributedString::new();
+        {
+            string.replace_str(&CFString::new(text), CFRange::init(0, 0));
+            let utf16_line_len = string.char_len() as usize;
+
+            let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
+            let font_runs = runs
+                .iter()
+                .filter_map(|(len, style)| {
+                    let mut last_run = last_run.borrow_mut();
+                    if let Some((last_len, last_font_id)) = last_run.as_mut() {
+                        if style.font_id == *last_font_id {
+                            *last_len += *len;
+                            None
+                        } else {
+                            let result = (*last_len, *last_font_id);
+                            *last_len = *len;
+                            *last_font_id = style.font_id;
+                            Some(result)
+                        }
+                    } else {
+                        *last_run = Some((*len, style.font_id));
+                        None
+                    }
+                })
+                .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
+
+            let mut ix_converter = StringIndexConverter::new(text);
+            for (run_len, font_id) in font_runs {
+                let utf8_end = ix_converter.utf8_ix + run_len;
+                let utf16_start = ix_converter.utf16_ix;
+
+                if utf16_start >= utf16_line_len {
+                    break;
+                }
+
+                ix_converter.advance_to_utf8_ix(utf8_end);
+                let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
+
+                let cf_range =
+                    CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
+                let font = &self.fonts[font_id.0];
+                unsafe {
+                    string.set_attribute(
+                        cf_range,
+                        kCTFontAttributeName,
+                        &font.native_font().clone_with_font_size(font_size.into()),
+                    );
+                }
+
+                if utf16_end == utf16_line_len {
+                    break;
+                }
+            }
+        }
+
+        // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
+        let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
+
+        let mut runs = Vec::new();
+        for run in line.glyph_runs().into_iter() {
+            let attributes = run.attributes().unwrap();
+            let font = unsafe {
+                attributes
+                    .get(kCTFontAttributeName)
+                    .downcast::<CTFont>()
+                    .unwrap()
+            };
+            let font_id = self.id_for_native_font(font);
+
+            let mut ix_converter = StringIndexConverter::new(text);
+            let mut glyphs = Vec::new();
+            for ((glyph_id, position), glyph_utf16_ix) in run
+                .glyphs()
+                .iter()
+                .zip(run.positions().iter())
+                .zip(run.string_indices().iter())
+            {
+                let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
+                ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
+                glyphs.push(Glyph {
+                    id: (*glyph_id).into(),
+                    position: point(position.x as f32, position.y as f32).map(px),
+                    index: ix_converter.utf8_ix,
+                    is_emoji: self.is_emoji(font_id),
+                });
+            }
+
+            runs.push(Run { font_id, glyphs })
+        }
+
+        let typographic_bounds = line.get_typographic_bounds();
+        LineLayout {
+            width: typographic_bounds.width.into(),
+            ascent: typographic_bounds.ascent.into(),
+            descent: typographic_bounds.descent.into(),
+            runs,
+            font_size,
+            len: text.len(),
+        }
+    }
+
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize> {
+        let mut string = CFMutableAttributedString::new();
+        string.replace_str(&CFString::new(text), CFRange::init(0, 0));
+        let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
+        let font = &self.fonts[font_id.0];
+        unsafe {
+            string.set_attribute(
+                cf_range,
+                kCTFontAttributeName,
+                &font.native_font().clone_with_font_size(font_size.into()),
+            );
+
+            let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
+            let mut ix_converter = StringIndexConverter::new(text);
+            let mut break_indices = Vec::new();
+            while ix_converter.utf8_ix < text.len() {
+                let utf16_len = CTTypesetterSuggestLineBreak(
+                    typesetter,
+                    ix_converter.utf16_ix as isize,
+                    width.into(),
+                ) as usize;
+                ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
+                if ix_converter.utf8_ix >= text.len() {
+                    break;
+                }
+                break_indices.push(ix_converter.utf8_ix as usize);
+            }
+            break_indices
+        }
+    }
+}
+
+#[derive(Clone)]
+struct StringIndexConverter<'a> {
+    text: &'a str,
+    utf8_ix: usize,
+    utf16_ix: usize,
+}
+
+impl<'a> StringIndexConverter<'a> {
+    fn new(text: &'a str) -> Self {
+        Self {
+            text,
+            utf8_ix: 0,
+            utf16_ix: 0,
+        }
+    }
+
+    fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
+        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
+            if self.utf8_ix + ix >= utf8_target {
+                self.utf8_ix += ix;
+                return;
+            }
+            self.utf16_ix += c.len_utf16();
+        }
+        self.utf8_ix = self.text.len();
+    }
+
+    fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
+        for (ix, c) in self.text[self.utf8_ix..].char_indices() {
+            if self.utf16_ix >= utf16_target {
+                self.utf8_ix += ix;
+                return;
+            }
+            self.utf16_ix += c.len_utf16();
+        }
+        self.utf8_ix = self.text.len();
+    }
+}
+
+#[repr(C)]
+pub struct __CFTypesetter(c_void);
+
+pub type CTTypesetterRef = *const __CFTypesetter;
+
+#[link(name = "CoreText", kind = "framework")]
+extern "C" {
+    fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
+
+    fn CTTypesetterSuggestLineBreak(
+        typesetter: CTTypesetterRef,
+        start_index: CFIndex,
+        width: f64,
+    ) -> CFIndex;
+}
+
+impl From<Metrics> for FontMetrics {
+    fn from(metrics: Metrics) -> Self {
+        FontMetrics {
+            units_per_em: metrics.units_per_em,
+            ascent: metrics.ascent,
+            descent: metrics.descent,
+            line_gap: metrics.line_gap,
+            underline_position: metrics.underline_position,
+            underline_thickness: metrics.underline_thickness,
+            cap_height: metrics.cap_height,
+            x_height: metrics.x_height,
+            bounding_box: metrics.bounding_box.into(),
+        }
+    }
+}
+
+impl From<RectF> for Bounds<f32> {
+    fn from(rect: RectF) -> Self {
+        Bounds {
+            origin: point(rect.origin_x(), rect.origin_y()),
+            size: size(rect.width(), rect.height()),
+        }
+    }
+}
+
+impl From<RectI> for Bounds<u32> {
+    fn from(rect: RectI) -> Self {
+        Bounds {
+            origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
+            size: size(rect.width() as u32, rect.height() as u32),
+        }
+    }
+}
+
+impl From<Point<u32>> for Vector2I {
+    fn from(size: Point<u32>) -> Self {
+        Vector2I::new(size.x as i32, size.y as i32)
+    }
+}
+
+impl From<Vector2F> for Size<f32> {
+    fn from(vec: Vector2F) -> Self {
+        size(vec.x(), vec.y())
+    }
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate::AppContext;
+//     use font_kit::properties::{Style, Weight};
+//     use platform::FontSystem as _;
+
+//     #[crate::test(self, retries = 5)]
+//     fn test_layout_str(_: &mut AppContext) {
+//         // This is failing intermittently on CI and we don't have time to figure it out
+//         let fonts = FontSystem::new();
+//         let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
+//         let menlo_regular = RunStyle {
+//             font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo_italic = RunStyle {
+//             font_id: fonts
+//                 .select_font(&menlo, Properties::new().style(Style::Italic))
+//                 .unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo_bold = RunStyle {
+//             font_id: fonts
+//                 .select_font(&menlo, Properties::new().weight(Weight::BOLD))
+//                 .unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         assert_ne!(menlo_regular, menlo_italic);
+//         assert_ne!(menlo_regular, menlo_bold);
+//         assert_ne!(menlo_italic, menlo_bold);
+
+//         let line = fonts.layout_line(
+//             "hello world",
+//             16.0,
+//             &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
+//         );
+//         assert_eq!(line.runs.len(), 3);
+//         assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
+//         assert_eq!(line.runs[0].glyphs.len(), 2);
+//         assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
+//         assert_eq!(line.runs[1].glyphs.len(), 4);
+//         assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
+//         assert_eq!(line.runs[2].glyphs.len(), 5);
+//     }
+
+//     #[test]
+//     fn test_glyph_offsets() -> anyhow::Result<()> {
+//         let fonts = FontSystem::new();
+//         let zapfino = fonts.load_family("Zapfino", &Default::default())?;
+//         let zapfino_regular = RunStyle {
+//             font_id: fonts.select_font(&zapfino, &Properties::new())?,
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+//         let menlo = fonts.load_family("Menlo", &Default::default())?;
+//         let menlo_regular = RunStyle {
+//             font_id: fonts.select_font(&menlo, &Properties::new())?,
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+
+//         let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
+//         let line = fonts.layout_line(
+//             text,
+//             16.0,
+//             &[
+//                 (9, zapfino_regular),
+//                 (13, menlo_regular),
+//                 (text.len() - 22, zapfino_regular),
+//             ],
+//         );
+//         assert_eq!(
+//             line.runs
+//                 .iter()
+//                 .flat_map(|r| r.glyphs.iter())
+//                 .map(|g| g.index)
+//                 .collect::<Vec<_>>(),
+//             vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
+//         );
+//         Ok(())
+//     }
+
+//     #[test]
+//     #[ignore]
+//     fn test_rasterize_glyph() {
+//         use std::{fs::File, io::BufWriter, path::Path};
+
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
+//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
+//         let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
+
+//         const VARIANTS: usize = 1;
+//         for i in 0..VARIANTS {
+//             let variant = i as f32 / VARIANTS as f32;
+//             let (bounds, bytes) = fonts
+//                 .rasterize_glyph(
+//                     font_id,
+//                     16.0,
+//                     glyph_id,
+//                     vec2f(variant, variant),
+//                     2.,
+//                     RasterizationOptions::Alpha,
+//                 )
+//                 .unwrap();
+
+//             let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
+//             let path = Path::new(&name);
+//             let file = File::create(path).unwrap();
+//             let w = &mut BufWriter::new(file);
+
+//             let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
+//             encoder.set_color(png::ColorType::Grayscale);
+//             encoder.set_depth(png::BitDepth::Eight);
+//             let mut writer = encoder.write_header().unwrap();
+//             writer.write_image_data(&bytes).unwrap();
+//         }
+//     }
+
+//     #[test]
+//     fn test_wrap_line() {
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
+//         let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
+
+//         let line = "one two three four five\n";
+//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
+//         assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
+
+//         let line = "aaa Ξ±Ξ±Ξ± βœ‹βœ‹βœ‹ πŸŽ‰πŸŽ‰πŸŽ‰\n";
+//         let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
+//         assert_eq!(
+//             wrap_boundaries,
+//             &["aaa Ξ±Ξ±Ξ± ".len(), "aaa Ξ±Ξ±Ξ± βœ‹βœ‹βœ‹ ".len(),]
+//         );
+//     }
+
+//     #[test]
+//     fn test_layout_line_bom_char() {
+//         let fonts = FontSystem::new();
+//         let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
+//         let style = RunStyle {
+//             font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
+//             color: Default::default(),
+//             underline: Default::default(),
+//         };
+
+//         let line = "\u{feff}";
+//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
+//         assert_eq!(layout.len, line.len());
+//         assert!(layout.runs.is_empty());
+
+//         let line = "a\u{feff}b";
+//         let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
+//         assert_eq!(layout.len, line.len());
+//         assert_eq!(layout.runs.len(), 1);
+//         assert_eq!(layout.runs[0].glyphs.len(), 2);
+//         assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
+//                                                      // There's no glyph for \u{feff}
+//         assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
+//     }
+// }

crates/gpui3/src/platform/test.rs πŸ”—

@@ -9,7 +9,51 @@ impl TestPlatform {
 }
 
 impl Platform for TestPlatform {
-    fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
+    fn executor(&self) -> std::rc::Rc<crate::ForegroundExecutor> {
+        todo!()
+    }
+
+    fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
+        todo!()
+    }
+
+    fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
+        todo!()
+    }
+
+    fn quit(&self) {
+        todo!()
+    }
+
+    fn restart(&self) {
+        todo!()
+    }
+
+    fn activate(&self, ignoring_other_apps: bool) {
+        todo!()
+    }
+
+    fn hide(&self) {
+        todo!()
+    }
+
+    fn hide_other_apps(&self) {
+        todo!()
+    }
+
+    fn unhide_other_apps(&self) {
+        todo!()
+    }
+
+    fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
+        todo!()
+    }
+
+    fn screen_by_id(&self, id: uuid::Uuid) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
+        todo!()
+    }
+
+    fn main_window(&self) -> Option<crate::AnyWindowHandle> {
         todo!()
     }
 
@@ -21,7 +65,101 @@ impl Platform for TestPlatform {
         todo!()
     }
 
-    fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
+    fn open_url(&self, url: &str) {
+        todo!()
+    }
+
+    fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
+        todo!()
+    }
+
+    fn prompt_for_paths(
+        &self,
+        options: crate::PathPromptOptions,
+    ) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
+        todo!()
+    }
+
+    fn prompt_for_new_path(
+        &self,
+        directory: &std::path::Path,
+    ) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
+        todo!()
+    }
+
+    fn reveal_path(&self, path: &std::path::Path) {
+        todo!()
+    }
+
+    fn on_become_active(&self, callback: Box<dyn FnMut()>) {
+        todo!()
+    }
+
+    fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
+        todo!()
+    }
+
+    fn on_quit(&self, callback: Box<dyn FnMut()>) {
+        todo!()
+    }
+
+    fn on_reopen(&self, callback: Box<dyn FnMut()>) {
+        todo!()
+    }
+
+    fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
+        todo!()
+    }
+
+    fn os_name(&self) -> &'static str {
+        todo!()
+    }
+
+    fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
+        todo!()
+    }
+
+    fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
+        todo!()
+    }
+
+    fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
+        todo!()
+    }
+
+    fn local_timezone(&self) -> time::UtcOffset {
+        todo!()
+    }
+
+    fn path_for_auxiliary_executable(&self, name: &str) -> anyhow::Result<std::path::PathBuf> {
+        todo!()
+    }
+
+    fn set_cursor_style(&self, style: crate::CursorStyle) {
+        todo!()
+    }
+
+    fn should_auto_hide_scrollbars(&self) -> bool {
+        todo!()
+    }
+
+    fn write_to_clipboard(&self, item: crate::ClipboardItem) {
+        todo!()
+    }
+
+    fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
+        todo!()
+    }
+
+    fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> anyhow::Result<()> {
+        todo!()
+    }
+
+    fn read_credentials(&self, url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
+        todo!()
+    }
+
+    fn delete_credentials(&self, url: &str) -> anyhow::Result<()> {
         todo!()
     }
 }

crates/gpui3/src/scene.rs πŸ”—

@@ -1,3 +1,5 @@
+use crate::{text::GlyphId, FontId};
+
 use super::{Bounds, Hsla, Pixels, Point};
 use bytemuck::{Pod, Zeroable};
 use plane_split::BspSplitter;
@@ -39,7 +41,7 @@ impl Scene {
 #[derive(Clone, Debug)]
 pub enum Primitive {
     Quad(Quad),
-    Glyph(Glyph),
+    Glyph(RenderedGlyph),
     Underline(Underline),
 }
 
@@ -58,7 +60,7 @@ impl Primitive {
 #[derive(Default)]
 pub struct PrimitiveBatch {
     pub quads: Vec<Quad>,
-    pub glyphs: Vec<Glyph>,
+    pub glyphs: Vec<RenderedGlyph>,
     pub underlines: Vec<Underline>,
 }
 
@@ -96,26 +98,24 @@ unsafe impl Zeroable for Quad {}
 
 unsafe impl Pod for Quad {}
 
-#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
-pub struct GlyphId(u32);
-
-#[derive(Clone, Debug)]
-pub struct Glyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub color: Hsla,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 impl From<Quad> for Primitive {
     fn from(quad: Quad) -> Self {
         Primitive::Quad(quad)
     }
 }
 
-impl From<Glyph> for Primitive {
-    fn from(glyph: Glyph) -> Self {
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub struct RenderedGlyph {
+    pub font_id: FontId,
+    pub font_size: f32,
+    pub id: GlyphId,
+    pub origin: Point<Pixels>,
+    pub color: Hsla,
+}
+
+impl From<RenderedGlyph> for Primitive {
+    fn from(glyph: RenderedGlyph) -> Self {
         Primitive::Glyph(glyph)
     }
 }

crates/gpui3/src/text.rs πŸ”—

@@ -1,8 +1,7 @@
 use crate::{black, px};
 
 use super::{
-    point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle,
-    WindowContext,
+    point, Bounds, FontId, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle, WindowContext,
 };
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
@@ -27,6 +26,35 @@ pub struct RunStyle {
     pub underline: Option<UnderlineStyle>,
 }
 
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+pub struct GlyphId(u32);
+
+impl From<GlyphId> for u32 {
+    fn from(value: GlyphId) -> Self {
+        value.0
+    }
+}
+
+impl From<u16> for GlyphId {
+    fn from(num: u16) -> Self {
+        GlyphId(num as u32)
+    }
+}
+
+impl From<u32> for GlyphId {
+    fn from(num: u32) -> Self {
+        GlyphId(num)
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct Glyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
 impl TextLayoutCache {
     pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
         Self {
@@ -726,7 +754,7 @@ mod tests {
         let cx = AppContext::test();
 
         let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().font_system();
+        let font_system = cx.platform().text_system();
         let family = font_cache
             .load_family(&["Courier"], &Default::default())
             .unwrap();
@@ -793,7 +821,7 @@ mod tests {
     fn test_wrap_shaped_line() {
         let cx = AppContext::test();
         let font_cache = cx.font_cache().clone();
-        let font_system = cx.platform().font_system();
+        let font_system = cx.platform().text_system();
         let text_layout_cache = TextLayoutCache::new(font_system.clone());
 
         let family = font_cache

crates/storybook/Cargo.toml πŸ”—

@@ -13,22 +13,16 @@ test-support = []
 
 [dependencies]
 anyhow.workspace = true
-bytemuck = "1.14.0"
 derive_more.workspace = true
 gpui2 = { path = "../gpui2" }
-itertools = "0.11.0"
 log.workspace = true
-plane-split = "0.18.0"
-raw-window-handle = "0.5.2"
 refineable = { path = "../refineable" }
 rust-embed.workspace = true
 serde.workspace = true
 settings = { path = "../settings" }
 simplelog = "0.9"
-slotmap = "1.0.6"
 theme = { path = "../theme" }
 util = { path = "../util" }
-wgpu = "0.17.0"
 
 [dev-dependencies]
 gpui2 = { path = "../gpui2", features = ["test-support"] }