History

Checkpoint

Nathan Sobo committed 3 years ago

Commit 83dae461 parent 7885eaf17 files · +2700 −1192Browse files

Unified · Split

17 files
  1. Cargo.lock +15 −29
  2. crates/gpui/src/platform/mac/platform.rs +3 −3
  3. crates/gpui3/Cargo.toml +1 −0
  4. crates/gpui3/src/app.rs +13 −8
  5. crates/gpui3/src/executor.rs +1 −1
  6. crates/gpui3/src/fonts.rs +3 −3
  7. crates/gpui3/src/geometry.rs +6 −0
  8. crates/gpui3/src/gpui3.rs +1 −0
  9. crates/gpui3/src/platform.rs +176 −11
  10. crates/gpui3/src/platform/mac.rs +11 −7
  11. crates/gpui3/src/platform/mac/open_type.rs +395 −0
  12. crates/gpui3/src/platform/mac/platform.rs +1130 −1102
  13. crates/gpui3/src/platform/mac/text_system.rs +757 −0
  14. crates/gpui3/src/platform/test.rs +140 −2
  15. crates/gpui3/src/scene.rs +16 −16
  16. crates/gpui3/src/text.rs +32 −4
  17. crates/storybook/Cargo.toml +0 −6
Cargo.lock+15−29
@@ -983,7 +983,7 @@ dependencies = [ · 982 unmodified lines
983983 "collections",
984984 "editor",
985985 "gpui",
986− "itertools 0.10.5",
986+ "itertools",
987987 "language",
988988 "outline",
989989 "project",
@@ -1960,7 +1960,7 @@ dependencies = [ · 970 unmodified lines
19601960 "cranelift-codegen",
19611961 "cranelift-entity",
19621962 "cranelift-frontend",
1963− "itertools 0.10.5",
1963+ "itertools",
19641964 "log",
19651965 "smallvec",
19661966 "wasmparser",
@@ -2392,7 +2392,7 @@ dependencies = [ · 425 unmodified lines
23922392 "git",
23932393 "gpui",
23942394 "indoc",
2395− "itertools 0.10.5",
2395+ "itertools",
23962396 "language",
23972397 "lazy_static",
23982398 "log",
@@ -3273,7 +3273,7 @@ dependencies = [ · 874 unmodified lines
32733273 "futures 0.3.28",
32743274 "gpui_macros",
32753275 "image",
3276− "itertools 0.10.5",
3276+ "itertools",
32773277 "lazy_static",
32783278 "log",
32793279 "media",
@@ -3365,7 +3365,7 @@ dependencies = [ · 85 unmodified lines
33653365 "futures 0.3.28",
33663366 "gpui_macros",
33673367 "image",
3368− "itertools 0.10.5",
3368+ "itertools",
33693369 "lazy_static",
33703370 "log",
33713371 "media",
@@ -3374,6 +3374,7 @@ dependencies = [ · 2 unmodified lines
33743374 "ordered-float",
33753375 "parking",
33763376 "parking_lot 0.11.2",
3377+ "pathfinder_geometry",
33773378 "plane-split",
33783379 "png",
33793380 "postage",
@@ -3971,15 +3972,6 @@ dependencies = [ · 591 unmodified lines
39713972 "either",
39723973 ]
39733974
3974−[[package]]
3975−name = "itertools"
3976−version = "0.11.0"
3977−source = "registry+https://github.com/rust-lang/crates.io-index"
3978−checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
3979−dependencies = [
3980− "either",
3981−]
3982−
39833975 [[package]]
39843976 name = "itoa"
39853977 version = "1.0.9"
@@ -5772,7 +5764,7 @@ dependencies = [ · 1786 unmodified lines
57725764 "globset",
57735765 "gpui",
57745766 "ignore",
5775− "itertools 0.10.5",
5767+ "itertools",
57765768 "language",
57775769 "lazy_static",
57785770 "log",
@@ -5896,7 +5888,7 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" · 117 unmodified lines
58965888 dependencies = [
58975889 "bytes 1.4.0",
58985890 "heck 0.3.3",
5899− "itertools 0.10.5",
5891+ "itertools",
59005892 "lazy_static",
59015893 "log",
59025894 "multimap",
@@ -5915,7 +5907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 12 unmodified lines
59155907 checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba"
59165908 dependencies = [
59175909 "anyhow",
5918− "itertools 0.10.5",
5910+ "itertools",
59195911 "proc-macro2",
59205912 "quote",
59215913 "syn 1.0.109",
@@ -5928,7 +5920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 6 unmodified lines
59285920 checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
59295921 dependencies = [
59305922 "anyhow",
5931− "itertools 0.10.5",
5923+ "itertools",
59325924 "proc-macro2",
59335925 "quote",
59345926 "syn 1.0.109",
@@ -7568,7 +7560,7 @@ version = "0.2.1" · 1633 unmodified lines
75687560 source = "registry+https://github.com/rust-lang/crates.io-index"
75697561 checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
75707562 dependencies = [
7571− "itertools 0.10.5",
7563+ "itertools",
75727564 "nom",
75737565 "unicode_categories",
75747566 ]
@@ -7692,22 +7684,16 @@ name = "storybook" · 117 unmodified lines
76927684 version = "0.1.0"
76937685 dependencies = [
76947686 "anyhow",
7695− "bytemuck",
76967687 "derive_more",
76977688 "gpui2",
7698− "itertools 0.11.0",
76997689 "log",
7700− "plane-split",
7701− "raw-window-handle",
77027690 "refineable",
77037691 "rust-embed",
77047692 "serde",
77057693 "settings",
77067694 "simplelog",
7707− "slotmap",
77087695 "theme",
77097696 "util",
7710− "wgpu",
77117697 ]
77127698
77137699 [[package]]
@@ -7977,7 +7963,7 @@ dependencies = [ · 263 unmodified lines
79777963 "dirs 4.0.0",
79787964 "futures 0.3.28",
79797965 "gpui",
7980− "itertools 0.10.5",
7966+ "itertools",
79817967 "lazy_static",
79827968 "libc",
79837969 "mio-extras",
@@ -8008,7 +7994,7 @@ dependencies = [ · 24 unmodified lines
80087994 "editor",
80097995 "futures 0.3.28",
80107996 "gpui",
8011− "itertools 0.10.5",
7997+ "itertools",
80127998 "language",
80137999 "lazy_static",
80148000 "libc",
@@ -9190,7 +9176,7 @@ dependencies = [ · 1175 unmodified lines
91909176 "futures 0.3.28",
91919177 "gpui",
91929178 "indoc",
9193− "itertools 0.10.5",
9179+ "itertools",
91949180 "language",
91959181 "language_selector",
91969182 "log",
@@ -10137,7 +10123,7 @@ dependencies = [ · 940 unmodified lines
1013710123 "gpui",
1013810124 "indoc",
1013910125 "install_cli",
10140− "itertools 0.10.5",
10126+ "itertools",
1014110127 "language",
1014210128 "lazy_static",
1014310129 "log",
crates/gpui/src/platform/mac/platform.rs+3−3
@@ -1,6 +1,6 @@
11 use super::{
22 event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
3− FontSystem, MacWindow,
3+ MacWindow, TextSystem,
44 };
55 use crate::{
66 executor,
@@ -488,7 +488,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { · 481 unmodified lines
488488
489489 pub struct MacPlatform {
490490 dispatcher: Arc<Dispatcher>,
491− fonts: Arc<FontSystem>,
491+ fonts: Arc<TextSystem>,
492492 pasteboard: id,
493493 text_hash_pasteboard_type: id,
494494 metadata_pasteboard_type: id,
@@ -498,7 +498,7 @@ impl MacPlatform { · 3 unmodified lines
498498 pub fn new() -> Self {
499499 Self {
500500 dispatcher: Arc::new(Dispatcher),
501− fonts: Arc::new(FontSystem::new()),
501+ fonts: Arc::new(TextSystem::new()),
502502 pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
503503 text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
504504 metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
crates/gpui3/Cargo.toml+1−0
@@ -35,6 +35,7 @@ num_cpus = "1.13" · 34 unmodified lines
3535 ordered-float.workspace = true
3636 parking = "2.0.0"
3737 parking_lot.workspace = true
38+pathfinder_geometry = "0.5"
3839 postage.workspace = true
3940 rand.workspace = true
4041 refineable.workspace = true
crates/gpui3/src/app.rs+13−8
@@ -1,14 +1,19 @@
1+use crate::{
2+ Context, FontCache, LayoutId, Platform, Reference, View, Window, WindowContext, WindowHandle,
3+ WindowId,
4+};
15 use anyhow::{anyhow, Result};
26 use slotmap::SlotMap;
3−use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};
7+use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
48
5−use crate::FontCache;
9+#[derive(Clone)]
10+pub struct App(Rc<RefCell<AppContext>>);
611
7−use super::{
8− platform::Platform,
9− window::{Window, WindowHandle, WindowId},
10− Context, LayoutId, Reference, View, WindowContext,
11−};
12+impl App {
13+ pub fn new(platform: Rc<dyn Platform>) -> Self {
14+ Self(Rc::new(RefCell::new(AppContext::new(platform))))
15+ }
16+}
1217
1318 pub struct AppContext {
1419 platform: Rc<dyn Platform>,
@@ -21,7 +26,7 @@ pub struct AppContext { · 6 unmodified lines
2126
2227 impl AppContext {
2328 pub fn new(platform: Rc<dyn Platform>) -> Self {
24− let font_cache = Arc::new(FontCache::new(platform.font_system()));
29+ let font_cache = Arc::new(FontCache::new(platform.text_system()));
2530 AppContext {
2631 platform,
2732 font_cache,
crates/gpui3/src/executor.rs+1−1
@@ -619,7 +619,7 @@ impl ExecutorEvent { · 618 unmodified lines
619619 }
620620
621621 impl ForegroundExecutor {
622− pub fn platform(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
622+ pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
623623 if dispatcher.is_main_thread() {
624624 Ok(Self::Platform {
625625 dispatcher,
crates/gpui3/src/fonts.rs+3−3
@@ -245,7 +245,7 @@ impl FontCache { · 244 unmodified lines
245245 .typographic_bounds(font_id, glyph_id)
246246 .unwrap();
247247 }
248− bounds.size.width * self.em_size(font_id, font_size)
248+ self.em_size(font_id, font_size) * bounds.size.width
249249 }
250250
251251 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
@@ -256,7 +256,7 @@ impl FontCache { · 4 unmodified lines
256256 glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
257257 advance = state.font_system.advance(font_id, glyph_id).unwrap();
258258 }
259− advance.x * self.em_size(font_id, font_size)
259+ self.em_size(font_id, font_size) * advance.width
260260 }
261261
262262 pub fn line_height(&self, font_size: Pixels) -> Pixels {
@@ -356,7 +356,7 @@ mod tests { · 93 unmodified lines
356356 #[test]
357357 fn test_select_font() {
358358 let platform = TestPlatform::new();
359− let fonts = FontCache::new(platform.font_system());
359+ let fonts = FontCache::new(platform.text_system());
360360 let arial = fonts
361361 .load_family(
362362 &["Arial"],
crates/gpui3/src/geometry.rs+6−0
@@ -246,6 +246,12 @@ impl std::hash::Hash for Pixels { · 245 unmodified lines
246246 }
247247 }
248248
249+impl From<f64> for Pixels {
250+ fn from(val: f64) -> Self {
251+ Pixels(val as f32)
252+ }
253+}
254+
249255 unsafe impl bytemuck::Pod for Pixels {}
250256 unsafe impl bytemuck::Zeroable for Pixels {}
251257
crates/gpui3/src/gpui3.rs+1−0
@@ -34,6 +34,7 @@ pub use styled::*; · 33 unmodified lines
3434 pub use taffy::LayoutId;
3535 use taffy::TaffyLayoutEngine;
3636 use text::*;
37+pub use text::{Glyph, GlyphId};
3738 pub use util::arc_cow::ArcCow;
3839 pub use window::*;
3940
crates/gpui3/src/platform.rs+176−11
@@ -6,13 +6,26 @@ mod mac; · 5 unmodified lines
66 mod test;
77
88 use crate::{
9− AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,
10− LineLayout, Pixels, Point, RunStyle, SharedString, Size,
9+ AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight,
10+ ForegroundExecutor, GlyphId, LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size,
1111 };
12+use anyhow::anyhow;
1213 use async_task::Runnable;
1314 use futures::channel::oneshot;
1415 use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
15−use std::{any::Any, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
16+use seahash::SeaHasher;
17+use serde::{Deserialize, Serialize};
18+use std::hash::{Hash, Hasher};
19+use std::{
20+ any::Any,
21+ fmt::{self, Debug, Display},
22+ ops::Range,
23+ path::{Path, PathBuf},
24+ rc::Rc,
25+ str::FromStr,
26+ sync::Arc,
27+};
28+pub use time::UtcOffset;
1629 use uuid::Uuid;
1730
1831 pub use events::*;
@@ -22,15 +35,64 @@ pub use mac::*; · 3 unmodified lines
2235 #[cfg(any(test, feature = "test"))]
2336 pub use test::*;
2437
38+// #[cfg(target_os = "macos")]
39+// pub fn current() -> Rc<dyn Platform> {
40+// MacPlatform
41+// }
42+
2543 pub trait Platform {
26− fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;
27− fn font_system(&self) -> Arc<dyn PlatformTextSystem>;
44+ fn executor(&self) -> Rc<ForegroundExecutor>;
45+ fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
2846
47+ fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
48+ fn quit(&self);
49+ fn restart(&self);
50+ fn activate(&self, ignoring_other_apps: bool);
51+ fn hide(&self);
52+ fn hide_other_apps(&self);
53+ fn unhide_other_apps(&self);
54+
55+ fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
56+ fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>>;
57+ fn main_window(&self) -> Option<AnyWindowHandle>;
2958 fn open_window(
3059 &self,
3160 handle: AnyWindowHandle,
3261 options: WindowOptions,
3362 ) -> Box<dyn PlatformWindow>;
63+ // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn PlatformWindow>;
64+
65+ fn open_url(&self, url: &str);
66+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>);
67+ fn prompt_for_paths(
68+ &self,
69+ options: PathPromptOptions,
70+ ) -> oneshot::Receiver<Option<Vec<PathBuf>>>;
71+ fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>;
72+ fn reveal_path(&self, path: &Path);
73+
74+ fn on_become_active(&self, callback: Box<dyn FnMut()>);
75+ fn on_resign_active(&self, callback: Box<dyn FnMut()>);
76+ fn on_quit(&self, callback: Box<dyn FnMut()>);
77+ fn on_reopen(&self, callback: Box<dyn FnMut()>);
78+ fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>);
79+
80+ fn os_name(&self) -> &'static str;
81+ fn os_version(&self) -> Result<SemanticVersion>;
82+ fn app_version(&self) -> Result<SemanticVersion>;
83+ fn app_path(&self) -> Result<PathBuf>;
84+ fn local_timezone(&self) -> UtcOffset;
85+ fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf>;
86+
87+ fn set_cursor_style(&self, style: CursorStyle);
88+ fn should_auto_hide_scrollbars(&self) -> bool;
89+
90+ fn write_to_clipboard(&self, item: ClipboardItem);
91+ fn read_from_clipboard(&self) -> Option<ClipboardItem>;
92+
93+ fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()>;
94+ fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
95+ fn delete_credentials(&self, url: &str) -> Result<()>;
3496 }
3597
3698 pub trait PlatformScreen: Debug {
@@ -90,12 +152,9 @@ pub trait PlatformTextSystem: Send + Sync { · 53 unmodified lines
90152 style: FontStyle,
91153 ) -> anyhow::Result<FontId>;
92154 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
93− fn typographic_bounds(
94− &self,
95− font_id: FontId,
96− glyph_id: GlyphId,
97− ) -> anyhow::Result<Bounds<Pixels>>;
98− fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Point<Pixels>>;
155+ fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId)
156+ -> anyhow::Result<Bounds<f32>>;
157+ fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>>;
99158 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
100159 fn rasterize_glyph(
101160 &self,
@@ -224,3 +283,109 @@ pub enum WindowPromptLevel { · 122 unmodified lines
224283 Warning,
225284 Critical,
226285 }
286+
287+#[derive(Copy, Clone, Debug)]
288+pub struct PathPromptOptions {
289+ pub files: bool,
290+ pub directories: bool,
291+ pub multiple: bool,
292+}
293+
294+#[derive(Copy, Clone, Debug)]
295+pub enum PromptLevel {
296+ Info,
297+ Warning,
298+ Critical,
299+}
300+
301+#[derive(Copy, Clone, Debug)]
302+pub enum CursorStyle {
303+ Arrow,
304+ ResizeLeftRight,
305+ ResizeUpDown,
306+ PointingHand,
307+ IBeam,
308+}
309+
310+impl Default for CursorStyle {
311+ fn default() -> Self {
312+ Self::Arrow
313+ }
314+}
315+
316+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
317+pub struct SemanticVersion {
318+ major: usize,
319+ minor: usize,
320+ patch: usize,
321+}
322+
323+impl FromStr for SemanticVersion {
324+ type Err = anyhow::Error;
325+
326+ fn from_str(s: &str) -> Result<Self> {
327+ let mut components = s.trim().split('.');
328+ let major = components
329+ .next()
330+ .ok_or_else(|| anyhow!("missing major version number"))?
331+ .parse()?;
332+ let minor = components
333+ .next()
334+ .ok_or_else(|| anyhow!("missing minor version number"))?
335+ .parse()?;
336+ let patch = components
337+ .next()
338+ .ok_or_else(|| anyhow!("missing patch version number"))?
339+ .parse()?;
340+ Ok(Self {
341+ major,
342+ minor,
343+ patch,
344+ })
345+ }
346+}
347+
348+impl Display for SemanticVersion {
349+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350+ write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
351+ }
352+}
353+
354+#[derive(Clone, Debug, Eq, PartialEq)]
355+pub struct ClipboardItem {
356+ pub(crate) text: String,
357+ pub(crate) metadata: Option<String>,
358+}
359+
360+impl ClipboardItem {
361+ pub fn new(text: String) -> Self {
362+ Self {
363+ text,
364+ metadata: None,
365+ }
366+ }
367+
368+ pub fn with_metadata<T: Serialize>(mut self, metadata: T) -> Self {
369+ self.metadata = Some(serde_json::to_string(&metadata).unwrap());
370+ self
371+ }
372+
373+ pub fn text(&self) -> &String {
374+ &self.text
375+ }
376+
377+ pub fn metadata<T>(&self) -> Option<T>
378+ where
379+ T: for<'a> Deserialize<'a>,
380+ {
381+ self.metadata
382+ .as_ref()
383+ .and_then(|m| serde_json::from_str(m).ok())
384+ }
385+
386+ pub(crate) fn text_hash(text: &str) -> u64 {
387+ let mut hasher = SeaHasher::new();
388+ text.hash(&mut hasher);
389+ hasher.finish()
390+ }
391+}
crates/gpui3/src/platform/mac.rs+11−7
@@ -2,18 +2,13 @@ · 1 unmodified line
22 ///! an origin at the bottom left of the main display.
33 mod dispatcher;
44 mod events;
5+mod open_type;
56 mod platform;
67 mod screen;
8+mod text_system;
79 mod window;
810 mod window_appearence;
911
10−use std::{
11− ffi::{c_char, CStr, OsStr},
12− ops::Range,
13− os::unix::prelude::OsStrExt,
14− path::PathBuf,
15−};
16−
1712 use crate::{px, size, Pixels, Size};
1813 use anyhow::anyhow;
1914 use cocoa::{
@@ -25,8 +20,17 @@ use objc::{ · 5 unmodified lines
2520 runtime::{BOOL, NO, YES},
2621 sel, sel_impl,
2722 };
23+use std::{
24+ ffi::{c_char, CStr, OsStr},
25+ ops::Range,
26+ os::unix::prelude::OsStrExt,
27+ path::PathBuf,
28+};
29+
30+pub use dispatcher::*;
2831 pub use platform::*;
2932 pub use screen::*;
33+pub use text_system::*;
3034 pub use window::*;
3135
3236 trait BoolExt {
crates/gpui3/src/platform/mac/open_type.rs+395−0
@@ -0,0 +1,395 @@
1+#![allow(unused, non_upper_case_globals)]
2+
3+use std::ptr;
4+
5+use crate::FontFeatures;
6+use cocoa::appkit::CGFloat;
7+use core_foundation::{base::TCFType, number::CFNumber};
8+use core_graphics::geometry::CGAffineTransform;
9+use core_text::{
10+ font::{CTFont, CTFontRef},
11+ font_descriptor::{
12+ CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
13+ },
14+};
15+use font_kit::font::Font;
16+
17+const kCaseSensitiveLayoutOffSelector: i32 = 1;
18+const kCaseSensitiveLayoutOnSelector: i32 = 0;
19+const kCaseSensitiveLayoutType: i32 = 33;
20+const kCaseSensitiveSpacingOffSelector: i32 = 3;
21+const kCaseSensitiveSpacingOnSelector: i32 = 2;
22+const kCharacterAlternativesType: i32 = 17;
23+const kCommonLigaturesOffSelector: i32 = 3;
24+const kCommonLigaturesOnSelector: i32 = 2;
25+const kContextualAlternatesOffSelector: i32 = 1;
26+const kContextualAlternatesOnSelector: i32 = 0;
27+const kContextualAlternatesType: i32 = 36;
28+const kContextualLigaturesOffSelector: i32 = 19;
29+const kContextualLigaturesOnSelector: i32 = 18;
30+const kContextualSwashAlternatesOffSelector: i32 = 5;
31+const kContextualSwashAlternatesOnSelector: i32 = 4;
32+const kDefaultLowerCaseSelector: i32 = 0;
33+const kDefaultUpperCaseSelector: i32 = 0;
34+const kDiagonalFractionsSelector: i32 = 2;
35+const kFractionsType: i32 = 11;
36+const kHistoricalLigaturesOffSelector: i32 = 21;
37+const kHistoricalLigaturesOnSelector: i32 = 20;
38+const kHojoCharactersSelector: i32 = 12;
39+const kInferiorsSelector: i32 = 2;
40+const kJIS2004CharactersSelector: i32 = 11;
41+const kLigaturesType: i32 = 1;
42+const kLowerCasePetiteCapsSelector: i32 = 2;
43+const kLowerCaseSmallCapsSelector: i32 = 1;
44+const kLowerCaseType: i32 = 37;
45+const kLowerCaseNumbersSelector: i32 = 0;
46+const kMathematicalGreekOffSelector: i32 = 11;
47+const kMathematicalGreekOnSelector: i32 = 10;
48+const kMonospacedNumbersSelector: i32 = 0;
49+const kNLCCharactersSelector: i32 = 13;
50+const kNoFractionsSelector: i32 = 0;
51+const kNormalPositionSelector: i32 = 0;
52+const kNoStyleOptionsSelector: i32 = 0;
53+const kNumberCaseType: i32 = 21;
54+const kNumberSpacingType: i32 = 6;
55+const kOrdinalsSelector: i32 = 3;
56+const kProportionalNumbersSelector: i32 = 1;
57+const kQuarterWidthTextSelector: i32 = 4;
58+const kScientificInferiorsSelector: i32 = 4;
59+const kSlashedZeroOffSelector: i32 = 5;
60+const kSlashedZeroOnSelector: i32 = 4;
61+const kStyleOptionsType: i32 = 19;
62+const kStylisticAltEighteenOffSelector: i32 = 37;
63+const kStylisticAltEighteenOnSelector: i32 = 36;
64+const kStylisticAltEightOffSelector: i32 = 17;
65+const kStylisticAltEightOnSelector: i32 = 16;
66+const kStylisticAltElevenOffSelector: i32 = 23;
67+const kStylisticAltElevenOnSelector: i32 = 22;
68+const kStylisticAlternativesType: i32 = 35;
69+const kStylisticAltFifteenOffSelector: i32 = 31;
70+const kStylisticAltFifteenOnSelector: i32 = 30;
71+const kStylisticAltFiveOffSelector: i32 = 11;
72+const kStylisticAltFiveOnSelector: i32 = 10;
73+const kStylisticAltFourOffSelector: i32 = 9;
74+const kStylisticAltFourOnSelector: i32 = 8;
75+const kStylisticAltFourteenOffSelector: i32 = 29;
76+const kStylisticAltFourteenOnSelector: i32 = 28;
77+const kStylisticAltNineOffSelector: i32 = 19;
78+const kStylisticAltNineOnSelector: i32 = 18;
79+const kStylisticAltNineteenOffSelector: i32 = 39;
80+const kStylisticAltNineteenOnSelector: i32 = 38;
81+const kStylisticAltOneOffSelector: i32 = 3;
82+const kStylisticAltOneOnSelector: i32 = 2;
83+const kStylisticAltSevenOffSelector: i32 = 15;
84+const kStylisticAltSevenOnSelector: i32 = 14;
85+const kStylisticAltSeventeenOffSelector: i32 = 35;
86+const kStylisticAltSeventeenOnSelector: i32 = 34;
87+const kStylisticAltSixOffSelector: i32 = 13;
88+const kStylisticAltSixOnSelector: i32 = 12;
89+const kStylisticAltSixteenOffSelector: i32 = 33;
90+const kStylisticAltSixteenOnSelector: i32 = 32;
91+const kStylisticAltTenOffSelector: i32 = 21;
92+const kStylisticAltTenOnSelector: i32 = 20;
93+const kStylisticAltThirteenOffSelector: i32 = 27;
94+const kStylisticAltThirteenOnSelector: i32 = 26;
95+const kStylisticAltThreeOffSelector: i32 = 7;
96+const kStylisticAltThreeOnSelector: i32 = 6;
97+const kStylisticAltTwelveOffSelector: i32 = 25;
98+const kStylisticAltTwelveOnSelector: i32 = 24;
99+const kStylisticAltTwentyOffSelector: i32 = 41;
100+const kStylisticAltTwentyOnSelector: i32 = 40;
101+const kStylisticAltTwoOffSelector: i32 = 5;
102+const kStylisticAltTwoOnSelector: i32 = 4;
103+const kSuperiorsSelector: i32 = 1;
104+const kSwashAlternatesOffSelector: i32 = 3;
105+const kSwashAlternatesOnSelector: i32 = 2;
106+const kTitlingCapsSelector: i32 = 4;
107+const kTypographicExtrasType: i32 = 14;
108+const kVerticalFractionsSelector: i32 = 1;
109+const kVerticalPositionType: i32 = 10;
110+
111+pub fn apply_features(font: &mut Font, features: &FontFeatures) {
112+ // See https://chromium.googlesource.com/chromium/src/+/66.0.3359.158/third_party/harfbuzz-ng/src/hb-coretext.cc
113+ // for a reference implementation.
114+ toggle_open_type_feature(
115+ font,
116+ features.calt,
117+ kContextualAlternatesType,
118+ kContextualAlternatesOnSelector,
119+ kContextualAlternatesOffSelector,
120+ );
121+ toggle_open_type_feature(
122+ font,
123+ features.case,
124+ kCaseSensitiveLayoutType,
125+ kCaseSensitiveLayoutOnSelector,
126+ kCaseSensitiveLayoutOffSelector,
127+ );
128+ toggle_open_type_feature(
129+ font,
130+ features.cpsp,
131+ kCaseSensitiveLayoutType,
132+ kCaseSensitiveSpacingOnSelector,
133+ kCaseSensitiveSpacingOffSelector,
134+ );
135+ toggle_open_type_feature(
136+ font,
137+ features.frac,
138+ kFractionsType,
139+ kDiagonalFractionsSelector,
140+ kNoFractionsSelector,
141+ );
142+ toggle_open_type_feature(
143+ font,
144+ features.liga,
145+ kLigaturesType,
146+ kCommonLigaturesOnSelector,
147+ kCommonLigaturesOffSelector,
148+ );
149+ toggle_open_type_feature(
150+ font,
151+ features.onum,
152+ kNumberCaseType,
153+ kLowerCaseNumbersSelector,
154+ 2,
155+ );
156+ toggle_open_type_feature(
157+ font,
158+ features.ordn,
159+ kVerticalPositionType,
160+ kOrdinalsSelector,
161+ kNormalPositionSelector,
162+ );
163+ toggle_open_type_feature(
164+ font,
165+ features.pnum,
166+ kNumberSpacingType,
167+ kProportionalNumbersSelector,
168+ 4,
169+ );
170+ toggle_open_type_feature(
171+ font,
172+ features.ss01,
173+ kStylisticAlternativesType,
174+ kStylisticAltOneOnSelector,
175+ kStylisticAltOneOffSelector,
176+ );
177+ toggle_open_type_feature(
178+ font,
179+ features.ss02,
180+ kStylisticAlternativesType,
181+ kStylisticAltTwoOnSelector,
182+ kStylisticAltTwoOffSelector,
183+ );
184+ toggle_open_type_feature(
185+ font,
186+ features.ss03,
187+ kStylisticAlternativesType,
188+ kStylisticAltThreeOnSelector,
189+ kStylisticAltThreeOffSelector,
190+ );
191+ toggle_open_type_feature(
192+ font,
193+ features.ss04,
194+ kStylisticAlternativesType,
195+ kStylisticAltFourOnSelector,
196+ kStylisticAltFourOffSelector,
197+ );
198+ toggle_open_type_feature(
199+ font,
200+ features.ss05,
201+ kStylisticAlternativesType,
202+ kStylisticAltFiveOnSelector,
203+ kStylisticAltFiveOffSelector,
204+ );
205+ toggle_open_type_feature(
206+ font,
207+ features.ss06,
208+ kStylisticAlternativesType,
209+ kStylisticAltSixOnSelector,
210+ kStylisticAltSixOffSelector,
211+ );
212+ toggle_open_type_feature(
213+ font,
214+ features.ss07,
215+ kStylisticAlternativesType,
216+ kStylisticAltSevenOnSelector,
217+ kStylisticAltSevenOffSelector,
218+ );
219+ toggle_open_type_feature(
220+ font,
221+ features.ss08,
222+ kStylisticAlternativesType,
223+ kStylisticAltEightOnSelector,
224+ kStylisticAltEightOffSelector,
225+ );
226+ toggle_open_type_feature(
227+ font,
228+ features.ss09,
229+ kStylisticAlternativesType,
230+ kStylisticAltNineOnSelector,
231+ kStylisticAltNineOffSelector,
232+ );
233+ toggle_open_type_feature(
234+ font,
235+ features.ss10,
236+ kStylisticAlternativesType,
237+ kStylisticAltTenOnSelector,
238+ kStylisticAltTenOffSelector,
239+ );
240+ toggle_open_type_feature(
241+ font,
242+ features.ss11,
243+ kStylisticAlternativesType,
244+ kStylisticAltElevenOnSelector,
245+ kStylisticAltElevenOffSelector,
246+ );
247+ toggle_open_type_feature(
248+ font,
249+ features.ss12,
250+ kStylisticAlternativesType,
251+ kStylisticAltTwelveOnSelector,
252+ kStylisticAltTwelveOffSelector,
253+ );
254+ toggle_open_type_feature(
255+ font,
256+ features.ss13,
257+ kStylisticAlternativesType,
258+ kStylisticAltThirteenOnSelector,
259+ kStylisticAltThirteenOffSelector,
260+ );
261+ toggle_open_type_feature(
262+ font,
263+ features.ss14,
264+ kStylisticAlternativesType,
265+ kStylisticAltFourteenOnSelector,
266+ kStylisticAltFourteenOffSelector,
267+ );
268+ toggle_open_type_feature(
269+ font,
270+ features.ss15,
271+ kStylisticAlternativesType,
272+ kStylisticAltFifteenOnSelector,
273+ kStylisticAltFifteenOffSelector,
274+ );
275+ toggle_open_type_feature(
276+ font,
277+ features.ss16,
278+ kStylisticAlternativesType,
279+ kStylisticAltSixteenOnSelector,
280+ kStylisticAltSixteenOffSelector,
281+ );
282+ toggle_open_type_feature(
283+ font,
284+ features.ss17,
285+ kStylisticAlternativesType,
286+ kStylisticAltSeventeenOnSelector,
287+ kStylisticAltSeventeenOffSelector,
288+ );
289+ toggle_open_type_feature(
290+ font,
291+ features.ss18,
292+ kStylisticAlternativesType,
293+ kStylisticAltEighteenOnSelector,
294+ kStylisticAltEighteenOffSelector,
295+ );
296+ toggle_open_type_feature(
297+ font,
298+ features.ss19,
299+ kStylisticAlternativesType,
300+ kStylisticAltNineteenOnSelector,
301+ kStylisticAltNineteenOffSelector,
302+ );
303+ toggle_open_type_feature(
304+ font,
305+ features.ss20,
306+ kStylisticAlternativesType,
307+ kStylisticAltTwentyOnSelector,
308+ kStylisticAltTwentyOffSelector,
309+ );
310+ toggle_open_type_feature(
311+ font,
312+ features.subs,
313+ kVerticalPositionType,
314+ kInferiorsSelector,
315+ kNormalPositionSelector,
316+ );
317+ toggle_open_type_feature(
318+ font,
319+ features.sups,
320+ kVerticalPositionType,
321+ kSuperiorsSelector,
322+ kNormalPositionSelector,
323+ );
324+ toggle_open_type_feature(
325+ font,
326+ features.swsh,
327+ kContextualAlternatesType,
328+ kSwashAlternatesOnSelector,
329+ kSwashAlternatesOffSelector,
330+ );
331+ toggle_open_type_feature(
332+ font,
333+ features.titl,
334+ kStyleOptionsType,
335+ kTitlingCapsSelector,
336+ kNoStyleOptionsSelector,
337+ );
338+ toggle_open_type_feature(
339+ font,
340+ features.tnum,
341+ kNumberSpacingType,
342+ kMonospacedNumbersSelector,
343+ 4,
344+ );
345+ toggle_open_type_feature(
346+ font,
347+ features.zero,
348+ kTypographicExtrasType,
349+ kSlashedZeroOnSelector,
350+ kSlashedZeroOffSelector,
351+ );
352+}
353+
354+fn toggle_open_type_feature(
355+ font: &mut Font,
356+ enabled: Option<bool>,
357+ type_identifier: i32,
358+ on_selector_identifier: i32,
359+ off_selector_identifier: i32,
360+) {
361+ if let Some(enabled) = enabled {
362+ let native_font = font.native_font();
363+ unsafe {
364+ let selector_identifier = if enabled {
365+ on_selector_identifier
366+ } else {
367+ off_selector_identifier
368+ };
369+ let new_descriptor = CTFontDescriptorCreateCopyWithFeature(
370+ native_font.copy_descriptor().as_concrete_TypeRef(),
371+ CFNumber::from(type_identifier).as_concrete_TypeRef(),
372+ CFNumber::from(selector_identifier).as_concrete_TypeRef(),
373+ );
374+ let new_descriptor = CTFontDescriptor::wrap_under_create_rule(new_descriptor);
375+ let new_font = CTFontCreateCopyWithAttributes(
376+ font.native_font().as_concrete_TypeRef(),
377+ 0.0,
378+ ptr::null(),
379+ new_descriptor.as_concrete_TypeRef(),
380+ );
381+ let new_font = CTFont::wrap_under_create_rule(new_font);
382+ *font = Font::from_native_font(new_font);
383+ }
384+ }
385+}
386+
387+#[link(name = "CoreText", kind = "framework")]
388+extern "C" {
389+ fn CTFontCreateCopyWithAttributes(
390+ font: CTFontRef,
391+ size: CGFloat,
392+ matrix: *const CGAffineTransform,
393+ attributes: CTFontDescriptorRef,
394+ ) -> CTFontRef;
395+}
crates/gpui3/src/platform/mac/platform.rs+1130−1102
@@ -1,1102 +1,1130 @@
1−// use anyhow::{anyhow, Result};
2−// use block::ConcreteBlock;
3−// use cocoa::{
4−// appkit::{
5−// NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
6−// NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,
7−// NSSavePanel, NSWindow,
8−// },
9−// base::{id, nil, selector, BOOL, YES},
10−// foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSUInteger, NSURL},
11−// };
12−// use core_foundation::{
13−// base::{CFTypeRef, OSStatus},
14−// dictionary::CFDictionaryRef,
15−// string::CFStringRef,
16−// };
17−// use ctor::ctor;
18−// use objc::{
19−// class,
20−// declare::ClassDecl,
21−// msg_send,
22−// runtime::{Class, Object, Sel},
23−// sel, sel_impl,
24−// };
25−
26−// use postage::oneshot;
27−// use ptr::null_mut;
28−// use std::{
29−// cell::{Cell, RefCell},
30−// convert::TryInto,
31−// ffi::{c_void, CStr, OsStr},
32−// os::{raw::c_char, unix::ffi::OsStrExt},
33−// path::{Path, PathBuf},
34−// process::Command,
35−// ptr,
36−// rc::Rc,
37−// slice, str,
38−// sync::Arc,
39−// };
40−
41−// use crate::Event;
42−
43−// #[allow(non_upper_case_globals)]
44−// const NSUTF8StringEncoding: NSUInteger = 4;
45−
46−// const MAC_PLATFORM_IVAR: &str = "platform";
47−// static mut APP_CLASS: *const Class = ptr::null();
48−// static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
49−
50−// #[ctor]
51−// unsafe fn build_classes() {
52−// APP_CLASS = {
53−// let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
54−// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
55−// decl.add_method(
56−// sel!(sendEvent:),
57−// send_event as extern "C" fn(&mut Object, Sel, id),
58−// );
59−// decl.register()
60−// };
61−
62−// APP_DELEGATE_CLASS = {
63−// let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
64−// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
65−// decl.add_method(
66−// sel!(applicationDidFinishLaunching:),
67−// did_finish_launching as extern "C" fn(&mut Object, Sel, id),
68−// );
69−// decl.add_method(
70−// sel!(applicationShouldHandleReopen:hasVisibleWindows:),
71−// should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
72−// );
73−// decl.add_method(
74−// sel!(applicationDidBecomeActive:),
75−// did_become_active as extern "C" fn(&mut Object, Sel, id),
76−// );
77−// decl.add_method(
78−// sel!(applicationDidResignActive:),
79−// did_resign_active as extern "C" fn(&mut Object, Sel, id),
80−// );
81−// decl.add_method(
82−// sel!(applicationWillTerminate:),
83−// will_terminate as extern "C" fn(&mut Object, Sel, id),
84−// );
85−// decl.add_method(
86−// sel!(handleGPUIMenuItem:),
87−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
88−// );
89−// // Add menu item handlers so that OS save panels have the correct key commands
90−// decl.add_method(
91−// sel!(cut:),
92−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
93−// );
94−// decl.add_method(
95−// sel!(copy:),
96−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
97−// );
98−// decl.add_method(
99−// sel!(paste:),
100−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
101−// );
102−// decl.add_method(
103−// sel!(selectAll:),
104−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
105−// );
106−// decl.add_method(
107−// sel!(undo:),
108−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
109−// );
110−// decl.add_method(
111−// sel!(redo:),
112−// handle_menu_item as extern "C" fn(&mut Object, Sel, id),
113−// );
114−// decl.add_method(
115−// sel!(validateMenuItem:),
116−// validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
117−// );
118−// decl.add_method(
119−// sel!(menuWillOpen:),
120−// menu_will_open as extern "C" fn(&mut Object, Sel, id),
121−// );
122−// decl.add_method(
123−// sel!(application:openURLs:),
124−// open_urls as extern "C" fn(&mut Object, Sel, id, id),
125−// );
126−// decl.register()
127−// }
128−// }
129−
130−// pub struct MacForegroundPlatformState {
131−// dispatcher: Arc<MacDispatcher>,
132−// fonts: Arc<MacFontSystem>,
133−// pasteboard: id,
134−// text_hash_pasteboard_type: id,
135−// metadata_pasteboard_type: id,
136−// become_active: Option<Box<dyn FnMut()>>,
137−// resign_active: Option<Box<dyn FnMut()>>,
138−// reopen: Option<Box<dyn FnMut()>>,
139−// quit: Option<Box<dyn FnMut()>>,
140−// event: Option<Box<dyn FnMut(Event) -> bool>>,
141−// // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
142−// validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
143−// will_open_menu: Option<Box<dyn FnMut()>>,
144−// open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
145−// finish_launching: Option<Box<dyn FnOnce()>>,
146−// // menu_actions: Vec<Box<dyn Action>>,
147−// // foreground: Rc<executor::Foreground>,
148−// }
149−
150−// impl MacForegroundPlatform {
151−// pub fn new(foreground: Rc<executor::Foreground>) -> Self {
152−// Self(RefCell::new(MacForegroundPlatformState {
153−// become_active: None,
154−// resign_active: None,
155−// reopen: None,
156−// quit: None,
157−// event: None,
158−// // menu_command: None,
159−// validate_menu_command: None,
160−// will_open_menu: None,
161−// open_urls: None,
162−// finish_launching: None,
163−// menu_actions: Default::default(),
164−// foreground,
165−// }))
166−// }
167−
168−// unsafe fn create_menu_bar(
169−// &self,
170−// menus: Vec<Menu>,
171−// delegate: id,
172−// actions: &mut Vec<Box<dyn Action>>,
173−// keystroke_matcher: &KeymapMatcher,
174−// ) -> id {
175−// let application_menu = NSMenu::new(nil).autorelease();
176−// application_menu.setDelegate_(delegate);
177−
178−// for menu_config in menus {
179−// let menu = NSMenu::new(nil).autorelease();
180−// menu.setTitle_(ns_string(menu_config.name));
181−// menu.setDelegate_(delegate);
182−
183−// for item_config in menu_config.items {
184−// menu.addItem_(self.create_menu_item(
185−// item_config,
186−// delegate,
187−// actions,
188−// keystroke_matcher,
189−// ));
190−// }
191−
192−// let menu_item = NSMenuItem::new(nil).autorelease();
193−// menu_item.setSubmenu_(menu);
194−// application_menu.addItem_(menu_item);
195−
196−// if menu_config.name == "Window" {
197−// let app: id = msg_send![APP_CLASS, sharedApplication];
198−// app.setWindowsMenu_(menu);
199−// }
200−// }
201−
202−// application_menu
203−// }
204−
205−// // unsafe fn create_menu_item(
206−// // &self,
207−// // item: MenuItem,
208−// // delegate: id,
209−// // actions: &mut Vec<Box<dyn Action>>,
210−// // keystroke_matcher: &KeymapMatcher,
211−// // ) -> id {
212−// // match item {
213−// // MenuItem::Separator => NSMenuItem::separatorItem(nil),
214−// // MenuItem::Action {
215−// // name,
216−// // action,
217−// // os_action,
218−// // } => {
219−// // // TODO
220−// // let keystrokes = keystroke_matcher
221−// // .bindings_for_action(action.id())
222−// // .find(|binding| binding.action().eq(action.as_ref()))
223−// // .map(|binding| binding.keystrokes());
224−// // let selector = match os_action {
225−// // Some(crate::OsAction::Cut) => selector("cut:"),
226−// // Some(crate::OsAction::Copy) => selector("copy:"),
227−// // Some(crate::OsAction::Paste) => selector("paste:"),
228−// // Some(crate::OsAction::SelectAll) => selector("selectAll:"),
229−// // Some(crate::OsAction::Undo) => selector("undo:"),
230−// // Some(crate::OsAction::Redo) => selector("redo:"),
231−// // None => selector("handleGPUIMenuItem:"),
232−// // };
233−
234−// // let item;
235−// // if let Some(keystrokes) = keystrokes {
236−// // if keystrokes.len() == 1 {
237−// // let keystroke = &keystrokes[0];
238−// // let mut mask = NSEventModifierFlags::empty();
239−// // for (modifier, flag) in &[
240−// // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
241−// // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
242−// // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
243−// // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
244−// // ] {
245−// // if *modifier {
246−// // mask |= *flag;
247−// // }
248−// // }
249−
250−// // item = NSMenuItem::alloc(nil)
251−// // .initWithTitle_action_keyEquivalent_(
252−// // ns_string(name),
253−// // selector,
254−// // ns_string(key_to_native(&keystroke.key).as_ref()),
255−// // )
256−// // .autorelease();
257−// // item.setKeyEquivalentModifierMask_(mask);
258−// // }
259−// // // For multi-keystroke bindings, render the keystroke as part of the title.
260−// // else {
261−// // use std::fmt::Write;
262−
263−// // let mut name = format!("{name} [");
264−// // for (i, keystroke) in keystrokes.iter().enumerate() {
265−// // if i > 0 {
266−// // name.push(' ');
267−// // }
268−// // write!(&mut name, "{}", keystroke).unwrap();
269−// // }
270−// // name.push(']');
271−
272−// // item = NSMenuItem::alloc(nil)
273−// // .initWithTitle_action_keyEquivalent_(
274−// // ns_string(&name),
275−// // selector,
276−// // ns_string(""),
277−// // )
278−// // .autorelease();
279−// // }
280−// // } else {
281−// // item = NSMenuItem::alloc(nil)
282−// // .initWithTitle_action_keyEquivalent_(
283−// // ns_string(name),
284−// // selector,
285−// // ns_string(""),
286−// // )
287−// // .autorelease();
288−// // }
289−
290−// // let tag = actions.len() as NSInteger;
291−// // let _: () = msg_send![item, setTag: tag];
292−// // actions.push(action);
293−// // item
294−// // }
295−// // MenuItem::Submenu(Menu { name, items }) => {
296−// // let item = NSMenuItem::new(nil).autorelease();
297−// // let submenu = NSMenu::new(nil).autorelease();
298−// // submenu.setDelegate_(delegate);
299−// // for item in items {
300−// // submenu.addItem_(self.create_menu_item(
301−// // item,
302−// // delegate,
303−// // actions,
304−// // keystroke_matcher,
305−// // ));
306−// // }
307−// // item.setSubmenu_(submenu);
308−// // item.setTitle_(ns_string(name));
309−// // item
310−// // }
311−// // }
312−// // }
313−
314−// unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
315−// let data = self.pasteboard.dataForType(kind);
316−// if data == nil {
317−// None
318−// } else {
319−// Some(slice::from_raw_parts(
320−// data.bytes() as *mut u8,
321−// data.length() as usize,
322−// ))
323−// }
324−// }
325−// }
326−
327−// // impl platform::ForegroundPlatform for MacForegroundPlatform {
328−// // fn on_become_active(&self, callback: Box<dyn FnMut()>) {
329−// // self.0.borrow_mut().become_active = Some(callback);
330−// // }
331−
332−// // fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
333−// // self.0.borrow_mut().resign_active = Some(callback);
334−// // }
335−
336−// // fn on_quit(&self, callback: Box<dyn FnMut()>) {
337−// // self.0.borrow_mut().quit = Some(callback);
338−// // }
339−
340−// // fn on_reopen(&self, callback: Box<dyn FnMut()>) {
341−// // self.0.borrow_mut().reopen = Some(callback);
342−// // }
343−
344−// // fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
345−// // self.0.borrow_mut().event = Some(callback);
346−// // }
347−
348−// // fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
349−// // self.0.borrow_mut().open_urls = Some(callback);
350−// // }
351−
352−// // fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
353−// // self.0.borrow_mut().finish_launching = Some(on_finish_launching);
354−
355−// // unsafe {
356−// // let app: id = msg_send![APP_CLASS, sharedApplication];
357−// // let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
358−// // app.setDelegate_(app_delegate);
359−
360−// // let self_ptr = self as *const Self as *const c_void;
361−// // (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
362−// // (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
363−
364−// // let pool = NSAutoreleasePool::new(nil);
365−// // app.run();
366−// // pool.drain();
367−
368−// // (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
369−// // (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
370−// // }
371−// // }
372−
373−// // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
374−// // self.0.borrow_mut().menu_command = Some(callback);
375−// // }
376−
377−// // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
378−// // self.0.borrow_mut().will_open_menu = Some(callback);
379−// // }
380−
381−// // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
382−// // self.0.borrow_mut().validate_menu_command = Some(callback);
383−// // }
384−
385−// // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
386−// // unsafe {
387−// // let app: id = msg_send![APP_CLASS, sharedApplication];
388−// // let mut state = self.0.borrow_mut();
389−// // let actions = &mut state.menu_actions;
390−// // app.setMainMenu_(self.create_menu_bar(
391−// // menus,
392−// // app.delegate(),
393−// // actions,
394−// // keystroke_matcher,
395−// // ));
396−// // }
397−// // }
398−
399−// // fn prompt_for_paths(
400−// // &self,
401−// // options: platform::PathPromptOptions,
402−// // ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
403−// // unsafe {
404−// // let panel = NSOpenPanel::openPanel(nil);
405−// // panel.setCanChooseDirectories_(options.directories.to_objc());
406−// // panel.setCanChooseFiles_(options.files.to_objc());
407−// // panel.setAllowsMultipleSelection_(options.multiple.to_objc());
408−// // panel.setResolvesAliases_(false.to_objc());
409−// // let (done_tx, done_rx) = oneshot::channel();
410−// // let done_tx = Cell::new(Some(done_tx));
411−// // let block = ConcreteBlock::new(move |response: NSModalResponse| {
412−// // let result = if response == NSModalResponse::NSModalResponseOk {
413−// // let mut result = Vec::new();
414−// // let urls = panel.URLs();
415−// // for i in 0..urls.count() {
416−// // let url = urls.objectAtIndex(i);
417−// // if url.isFileURL() == YES {
418−// // if let Ok(path) = ns_url_to_path(url) {
419−// // result.push(path)
420−// // }
421−// // }
422−// // }
423−// // Some(result)
424−// // } else {
425−// // None
426−// // };
427−
428−// // if let Some(mut done_tx) = done_tx.take() {
429−// // let _ = postage::sink::Sink::try_send(&mut done_tx, result);
430−// // }
431−// // });
432−// // let block = block.copy();
433−// // let _: () = msg_send![panel, beginWithCompletionHandler: block];
434−// // done_rx
435−// // }
436−// // }
437−
438−// // fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
439−// // unsafe {
440−// // let panel = NSSavePanel::savePanel(nil);
441−// // let path = ns_string(directory.to_string_lossy().as_ref());
442−// // let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
443−// // panel.setDirectoryURL(url);
444−
445−// // let (done_tx, done_rx) = oneshot::channel();
446−// // let done_tx = Cell::new(Some(done_tx));
447−// // let block = ConcreteBlock::new(move |response: NSModalResponse| {
448−// // let mut result = None;
449−// // if response == NSModalResponse::NSModalResponseOk {
450−// // let url = panel.URL();
451−// // if url.isFileURL() == YES {
452−// // result = ns_url_to_path(panel.URL()).ok()
453−// // }
454−// // }
455−
456−// // if let Some(mut done_tx) = done_tx.take() {
457−// // let _ = postage::sink::Sink::try_send(&mut done_tx, result);
458−// // }
459−// // });
460−// // let block = block.copy();
461−// // let _: () = msg_send![panel, beginWithCompletionHandler: block];
462−// // done_rx
463−// // }
464−// // }
465−
466−// // fn reveal_path(&self, path: &Path) {
467−// // unsafe {
468−// // let path = path.to_path_buf();
469−// // self.0
470−// // .borrow()
471−// // .foreground
472−// // .spawn(async move {
473−// // let full_path = ns_string(path.to_str().unwrap_or(""));
474−// // let root_full_path = ns_string("");
475−// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
476−// // let _: BOOL = msg_send![
477−// // workspace,
478−// // selectFile: full_path
479−// // inFileViewerRootedAtPath: root_full_path
480−// // ];
481−// // })
482−// // .detach();
483−// // }
484−// // }
485−// // }
486−
487−// // impl Platform for MacPlatform {
488−// // fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
489−// // self.dispatcher.clone()
490−// // }
491−
492−// // fn fonts(&self) -> Arc<dyn platform::FontSystem> {
493−// // self.fonts.clone()
494−// // }
495−
496−// // fn activate(&self, ignoring_other_apps: bool) {
497−// // unsafe {
498−// // let app = NSApplication::sharedApplication(nil);
499−// // app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
500−// // }
501−// // }
502−
503−// // fn hide(&self) {
504−// // unsafe {
505−// // let app = NSApplication::sharedApplication(nil);
506−// // let _: () = msg_send![app, hide: nil];
507−// // }
508−// // }
509−
510−// // fn hide_other_apps(&self) {
511−// // unsafe {
512−// // let app = NSApplication::sharedApplication(nil);
513−// // let _: () = msg_send![app, hideOtherApplications: nil];
514−// // }
515−// // }
516−
517−// // fn unhide_other_apps(&self) {
518−// // unsafe {
519−// // let app = NSApplication::sharedApplication(nil);
520−// // let _: () = msg_send![app, unhideAllApplications: nil];
521−// // }
522−// // }
523−
524−// // fn quit(&self) {
525−// // // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
526−// // // synchronously before this method terminates. If we call `Platform::quit` while holding a
527−// // // borrow of the app state (which most of the time we will do), we will end up
528−// // // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
529−// // // this, we make quitting the application asynchronous so that we aren't holding borrows to
530−// // // the app state on the stack when we actually terminate the app.
531−
532−// // use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
533−
534−// // unsafe {
535−// // dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
536−// // }
537−
538−// // unsafe extern "C" fn quit(_: *mut c_void) {
539−// // let app = NSApplication::sharedApplication(nil);
540−// // let _: () = msg_send![app, terminate: nil];
541−// // }
542−// // }
543−
544−// // fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {
545−// // Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
546−// // }
547−
548−// // fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {
549−// // Screen::all()
550−// // .into_iter()
551−// // .map(|screen| Rc::new(screen) as Rc<_>)
552−// // .collect()
553−// // }
554−
555−// // fn open_window(
556−// // &self,
557−// // handle: AnyWindowHandle,
558−// // options: platform::WindowOptions,
559−// // executor: Rc<executor::Foreground>,
560−// // ) -> Box<dyn platform::Window> {
561−// // Box::new(MacWindow::open(handle, options, executor, self.fonts()))
562−// // }
563−
564−// // fn main_window(&self) -> Option<AnyWindowHandle> {
565−// // MacWindow::main_window()
566−// // }
567−
568−// // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
569−// // Box::new(StatusItem::add(self.fonts()))
570−// // }
571−
572−// // fn write_to_clipboard(&self, item: ClipboardItem) {
573−// // unsafe {
574−// // self.pasteboard.clearContents();
575−
576−// // let text_bytes = NSData::dataWithBytes_length_(
577−// // nil,
578−// // item.text.as_ptr() as *const c_void,
579−// // item.text.len() as u64,
580−// // );
581−// // self.pasteboard
582−// // .setData_forType(text_bytes, NSPasteboardTypeString);
583−
584−// // if let Some(metadata) = item.metadata.as_ref() {
585−// // let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
586−// // let hash_bytes = NSData::dataWithBytes_length_(
587−// // nil,
588−// // hash_bytes.as_ptr() as *const c_void,
589−// // hash_bytes.len() as u64,
590−// // );
591−// // self.pasteboard
592−// // .setData_forType(hash_bytes, self.text_hash_pasteboard_type);
593−
594−// // let metadata_bytes = NSData::dataWithBytes_length_(
595−// // nil,
596−// // metadata.as_ptr() as *const c_void,
597−// // metadata.len() as u64,
598−// // );
599−// // self.pasteboard
600−// // .setData_forType(metadata_bytes, self.metadata_pasteboard_type);
601−// // }
602−// // }
603−// // }
604−
605−// // fn read_from_clipboard(&self) -> Option<ClipboardItem> {
606−// // unsafe {
607−// // if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
608−// // let text = String::from_utf8_lossy(text_bytes).to_string();
609−// // let hash_bytes = self
610−// // .read_from_pasteboard(self.text_hash_pasteboard_type)
611−// // .and_then(|bytes| bytes.try_into().ok())
612−// // .map(u64::from_be_bytes);
613−// // let metadata_bytes = self
614−// // .read_from_pasteboard(self.metadata_pasteboard_type)
615−// // .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
616−
617−// // if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
618−// // if hash == ClipboardItem::text_hash(&text) {
619−// // Some(ClipboardItem {
620−// // text,
621−// // metadata: Some(metadata),
622−// // })
623−// // } else {
624−// // Some(ClipboardItem {
625−// // text,
626−// // metadata: None,
627−// // })
628−// // }
629−// // } else {
630−// // Some(ClipboardItem {
631−// // text,
632−// // metadata: None,
633−// // })
634−// // }
635−// // } else {
636−// // None
637−// // }
638−// // }
639−// // }
640−
641−// // fn open_url(&self, url: &str) {
642−// // unsafe {
643−// // let url = NSURL::alloc(nil)
644−// // .initWithString_(ns_string(url))
645−// // .autorelease();
646−// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
647−// // msg_send![workspace, openURL: url]
648−// // }
649−// // }
650−
651−// // fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
652−// // let url = CFString::from(url);
653−// // let username = CFString::from(username);
654−// // let password = CFData::from_buffer(password);
655−
656−// // unsafe {
657−// // use security::*;
658−
659−// // // First, check if there are already credentials for the given server. If so, then
660−// // // update the username and password.
661−// // let mut verb = "updating";
662−// // let mut query_attrs = CFMutableDictionary::with_capacity(2);
663−// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
664−// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
665−
666−// // let mut attrs = CFMutableDictionary::with_capacity(4);
667−// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
668−// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
669−// // attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
670−// // attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
671−
672−// // let mut status = SecItemUpdate(
673−// // query_attrs.as_concrete_TypeRef(),
674−// // attrs.as_concrete_TypeRef(),
675−// // );
676−
677−// // // If there were no existing credentials for the given server, then create them.
678−// // if status == errSecItemNotFound {
679−// // verb = "creating";
680−// // status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
681−// // }
682−
683−// // if status != errSecSuccess {
684−// // return Err(anyhow!("{} password failed: {}", verb, status));
685−// // }
686−// // }
687−// // Ok(())
688−// // }
689−
690−// // fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
691−// // let url = CFString::from(url);
692−// // let cf_true = CFBoolean::true_value().as_CFTypeRef();
693−
694−// // unsafe {
695−// // use security::*;
696−
697−// // // Find any credentials for the given server URL.
698−// // let mut attrs = CFMutableDictionary::with_capacity(5);
699−// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
700−// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
701−// // attrs.set(kSecReturnAttributes as *const _, cf_true);
702−// // attrs.set(kSecReturnData as *const _, cf_true);
703−
704−// // let mut result = CFTypeRef::from(ptr::null());
705−// // let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
706−// // match status {
707−// // security::errSecSuccess => {}
708−// // security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
709−// // _ => return Err(anyhow!("reading password failed: {}", status)),
710−// // }
711−
712−// // let result = CFType::wrap_under_create_rule(result)
713−// // .downcast::<CFDictionary>()
714−// // .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
715−// // let username = result
716−// // .find(kSecAttrAccount as *const _)
717−// // .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
718−// // let username = CFType::wrap_under_get_rule(*username)
719−// // .downcast::<CFString>()
720−// // .ok_or_else(|| anyhow!("account was not a string"))?;
721−// // let password = result
722−// // .find(kSecValueData as *const _)
723−// // .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
724−// // let password = CFType::wrap_under_get_rule(*password)
725−// // .downcast::<CFData>()
726−// // .ok_or_else(|| anyhow!("password was not a string"))?;
727−
728−// // Ok(Some((username.to_string(), password.bytes().to_vec())))
729−// // }
730−// // }
731−
732−// // fn delete_credentials(&self, url: &str) -> Result<()> {
733−// // let url = CFString::from(url);
734−
735−// // unsafe {
736−// // use security::*;
737−
738−// // let mut query_attrs = CFMutableDictionary::with_capacity(2);
739−// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
740−// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
741−
742−// // let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
743−
744−// // if status != errSecSuccess {
745−// // return Err(anyhow!("delete password failed: {}", status));
746−// // }
747−// // }
748−// // Ok(())
749−// // }
750−
751−// // fn set_cursor_style(&self, style: CursorStyle) {
752−// // unsafe {
753−// // let new_cursor: id = match style {
754−// // CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
755−// // CursorStyle::ResizeLeftRight => {
756−// // msg_send![class!(NSCursor), resizeLeftRightCursor]
757−// // }
758−// // CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
759−// // CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
760−// // CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
761−// // };
762−
763−// // let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
764−// // if new_cursor != old_cursor {
765−// // let _: () = msg_send![new_cursor, set];
766−// // }
767−// // }
768−// // }
769−
770−// // fn should_auto_hide_scrollbars(&self) -> bool {
771−// // #[allow(non_upper_case_globals)]
772−// // const NSScrollerStyleOverlay: NSInteger = 1;
773−
774−// // unsafe {
775−// // let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
776−// // style == NSScrollerStyleOverlay
777−// // }
778−// // }
779−
780−// // fn local_timezone(&self) -> UtcOffset {
781−// // unsafe {
782−// // let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
783−// // let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
784−// // UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
785−// // }
786−// // }
787−
788−// // fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
789−// // unsafe {
790−// // let bundle: id = NSBundle::mainBundle();
791−// // if bundle.is_null() {
792−// // Err(anyhow!("app is not running inside a bundle"))
793−// // } else {
794−// // let name = ns_string(name);
795−// // let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
796−// // if url.is_null() {
797−// // Err(anyhow!("resource not found"))
798−// // } else {
799−// // ns_url_to_path(url)
800−// // }
801−// // }
802−// // }
803−// // }
804−
805−// // fn app_path(&self) -> Result<PathBuf> {
806−// // unsafe {
807−// // let bundle: id = NSBundle::mainBundle();
808−// // if bundle.is_null() {
809−// // Err(anyhow!("app is not running inside a bundle"))
810−// // } else {
811−// // Ok(path_from_objc(msg_send![bundle, bundlePath]))
812−// // }
813−// // }
814−// // }
815−
816−// // fn app_version(&self) -> Result<platform::AppVersion> {
817−// // unsafe {
818−// // let bundle: id = NSBundle::mainBundle();
819−// // if bundle.is_null() {
820−// // Err(anyhow!("app is not running inside a bundle"))
821−// // } else {
822−// // let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
823−// // let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
824−// // let bytes = version.UTF8String() as *const u8;
825−// // let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
826−// // version.parse()
827−// // }
828−// // }
829−// // }
830−
831−// // fn os_name(&self) -> &'static str {
832−// // "macOS"
833−// // }
834−
835−// // fn os_version(&self) -> Result<crate::platform::AppVersion> {
836−// // unsafe {
837−// // let process_info = NSProcessInfo::processInfo(nil);
838−// // let version = process_info.operatingSystemVersion();
839−// // Ok(AppVersion {
840−// // major: version.majorVersion as usize,
841−// // minor: version.minorVersion as usize,
842−// // patch: version.patchVersion as usize,
843−// // })
844−// // }
845−// // }
846−
847−// // fn restart(&self) {
848−// // use std::os::unix::process::CommandExt as _;
849−
850−// // let app_pid = std::process::id().to_string();
851−// // let app_path = self
852−// // .app_path()
853−// // .ok()
854−// // // When the app is not bundled, `app_path` returns the
855−// // // directory containing the executable. Disregard this
856−// // // and get the path to the executable itself.
857−// // .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
858−// // .unwrap_or_else(|| std::env::current_exe().unwrap());
859−
860−// // // Wait until this process has exited and then re-open this path.
861−// // let script = r#"
862−// // while kill -0 $0 2> /dev/null; do
863−// // sleep 0.1
864−// // done
865−// // open "$1"
866−// // "#;
867−
868−// // let restart_process = Command::new("/bin/bash")
869−// // .arg("-c")
870−// // .arg(script)
871−// // .arg(app_pid)
872−// // .arg(app_path)
873−// // .process_group(0)
874−// // .spawn();
875−
876−// // match restart_process {
877−// // Ok(_) => self.quit(),
878−// // Err(e) => log::error!("failed to spawn restart script: {:?}", e),
879−// // }
880−// // }
881−// // }
882−
883−// unsafe fn path_from_objc(path: id) -> PathBuf {
884−// let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
885−// let bytes = path.UTF8String() as *const u8;
886−// let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
887−// PathBuf::from(path)
888−// }
889−
890−// unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {
891−// let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
892−// assert!(!platform_ptr.is_null());
893−// &*(platform_ptr as *const MacForegroundPlatform)
894−// }
895−
896−// extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
897−// unsafe {
898−// if let Some(event) = Event::from_native(native_event, None) {
899−// let platform = get_foreground_platform(this);
900−// if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
901−// if callback(event) {
902−// return;
903−// }
904−// }
905−// }
906−// msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
907−// }
908−// }
909−
910−// extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
911−// unsafe {
912−// let app: id = msg_send![APP_CLASS, sharedApplication];
913−// app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
914−
915−// let platform = get_foreground_platform(this);
916−// let callback = platform.0.borrow_mut().finish_launching.take();
917−// if let Some(callback) = callback {
918−// callback();
919−// }
920−// }
921−// }
922−
923−// extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
924−// if !has_open_windows {
925−// let platform = unsafe { get_foreground_platform(this) };
926−// if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
927−// callback();
928−// }
929−// }
930−// }
931−
932−// extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
933−// let platform = unsafe { get_foreground_platform(this) };
934−// if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
935−// callback();
936−// }
937−// }
938−
939−// extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
940−// let platform = unsafe { get_foreground_platform(this) };
941−// if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
942−// callback();
943−// }
944−// }
945−
946−// extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
947−// let platform = unsafe { get_foreground_platform(this) };
948−// if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
949−// callback();
950−// }
951−// }
952−
953−// extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
954−// let urls = unsafe {
955−// (0..urls.count())
956−// .into_iter()
957−// .filter_map(|i| {
958−// let url = urls.objectAtIndex(i);
959−// match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
960−// Ok(string) => Some(string.to_string()),
961−// Err(err) => {
962−// log::error!("error converting path to string: {}", err);
963−// None
964−// }
965−// }
966−// })
967−// .collect::<Vec<_>>()
968−// };
969−// let platform = unsafe { get_foreground_platform(this) };
970−// if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
971−// callback(urls);
972−// }
973−// }
974−
975−// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
976−// unsafe {
977−// let platform = get_foreground_platform(this);
978−// let mut platform = platform.0.borrow_mut();
979−// if let Some(mut callback) = platform.menu_command.take() {
980−// let tag: NSInteger = msg_send![item, tag];
981−// let index = tag as usize;
982−// if let Some(action) = platform.menu_actions.get(index) {
983−// callback(action.as_ref());
984−// }
985−// platform.menu_command = Some(callback);
986−// }
987−// }
988−// }
989−
990−// extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
991−// unsafe {
992−// let mut result = false;
993−// let platform = get_foreground_platform(this);
994−// let mut platform = platform.0.borrow_mut();
995−// if let Some(mut callback) = platform.validate_menu_command.take() {
996−// let tag: NSInteger = msg_send![item, tag];
997−// let index = tag as usize;
998−// if let Some(action) = platform.menu_actions.get(index) {
999−// result = callback(action.as_ref());
1000−// }
1001−// platform.validate_menu_command = Some(callback);
1002−// }
1003−// result
1004−// }
1005−// }
1006−
1007−// extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
1008−// unsafe {
1009−// let platform = get_foreground_platform(this);
1010−// let mut platform = platform.0.borrow_mut();
1011−// if let Some(mut callback) = platform.will_open_menu.take() {
1012−// callback();
1013−// platform.will_open_menu = Some(callback);
1014−// }
1015−// }
1016−// }
1017−
1018−// unsafe fn ns_string(string: &str) -> id {
1019−// NSString::alloc(nil).init_str(string).autorelease()
1020−// }
1021−
1022−// unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
1023−// let path: *mut c_char = msg_send![url, fileSystemRepresentation];
1024−// if path.is_null() {
1025−// Err(anyhow!(
1026−// "url is not a file path: {}",
1027−// CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
1028−// ))
1029−// } else {
1030−// Ok(PathBuf::from(OsStr::from_bytes(
1031−// CStr::from_ptr(path).to_bytes(),
1032−// )))
1033−// }
1034−// }
1035−
1036−// mod security {
1037−// #![allow(non_upper_case_globals)]
1038−// use super::*;
1039−
1040−// #[link(name = "Security", kind = "framework")]
1041−// extern "C" {
1042−// pub static kSecClass: CFStringRef;
1043−// pub static kSecClassInternetPassword: CFStringRef;
1044−// pub static kSecAttrServer: CFStringRef;
1045−// pub static kSecAttrAccount: CFStringRef;
1046−// pub static kSecValueData: CFStringRef;
1047−// pub static kSecReturnAttributes: CFStringRef;
1048−// pub static kSecReturnData: CFStringRef;
1049−
1050−// pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1051−// pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
1052−// pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
1053−// pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1054−// }
1055−
1056−// pub const errSecSuccess: OSStatus = 0;
1057−// pub const errSecUserCanceled: OSStatus = -128;
1058−// pub const errSecItemNotFound: OSStatus = -25300;
1059−// }
1060−
1061−// #[cfg(test)]
1062−// mod tests {
1063−// use crate::platform::Platform;
1064−
1065−// use super::*;
1066−
1067−// #[test]
1068−// fn test_clipboard() {
1069−// let platform = build_platform();
1070−// assert_eq!(platform.read_from_clipboard(), None);
1071−
1072−// let item = ClipboardItem::new("1".to_string());
1073−// platform.write_to_clipboard(item.clone());
1074−// assert_eq!(platform.read_from_clipboard(), Some(item));
1075−
1076−// let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
1077−// platform.write_to_clipboard(item.clone());
1078−// assert_eq!(platform.read_from_clipboard(), Some(item));
1079−
1080−// let text_from_other_app = "text from other app";
1081−// unsafe {
1082−// let bytes = NSData::dataWithBytes_length_(
1083−// nil,
1084−// text_from_other_app.as_ptr() as *const c_void,
1085−// text_from_other_app.len() as u64,
1086−// );
1087−// platform
1088−// .pasteboard
1089−// .setData_forType(bytes, NSPasteboardTypeString);
1090−// }
1091−// assert_eq!(
1092−// platform.read_from_clipboard(),
1093−// Some(ClipboardItem::new(text_from_other_app.to_string()))
1094−// );
1095−// }
1096−
1097−// fn build_platform() -> MacPlatform {
1098−// let mut platform = MacPlatform::new();
1099−// platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
1100−// platform
1101−// }
1102−// }
1+use super::BoolExt;
2+use crate::{
3+ AnyWindowHandle, ClipboardItem, CursorStyle, Event, ForegroundExecutor, MacDispatcher,
4+ MacScreen, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen,
5+ PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
6+};
7+use anyhow::anyhow;
8+use block::ConcreteBlock;
9+use cocoa::{
10+ appkit::{
11+ NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
12+ NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
13+ },
14+ base::{id, nil, BOOL, YES},
15+ foundation::{
16+ NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
17+ NSUInteger, NSURL,
18+ },
19+};
20+use core_foundation::{
21+ base::{CFType, CFTypeRef, OSStatus, TCFType as _},
22+ boolean::CFBoolean,
23+ data::CFData,
24+ dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
25+ string::{CFString, CFStringRef},
26+};
27+use ctor::ctor;
28+use futures::channel::oneshot;
29+use objc::{
30+ class,
31+ declare::ClassDecl,
32+ msg_send,
33+ runtime::{Class, Object, Sel},
34+ sel, sel_impl,
35+};
36+use ptr::null_mut;
37+use std::{
38+ cell::{Cell, RefCell},
39+ convert::TryInto,
40+ ffi::{c_void, CStr, OsStr},
41+ os::{raw::c_char, unix::ffi::OsStrExt},
42+ path::{Path, PathBuf},
43+ process::Command,
44+ ptr,
45+ rc::Rc,
46+ slice, str,
47+ sync::Arc,
48+};
49+use time::UtcOffset;
50+
51+#[allow(non_upper_case_globals)]
52+const NSUTF8StringEncoding: NSUInteger = 4;
53+
54+#[allow(non_upper_case_globals)]
55+pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
56+
57+const MAC_PLATFORM_IVAR: &str = "platform";
58+static mut APP_CLASS: *const Class = ptr::null();
59+static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
60+
61+#[ctor]
62+unsafe fn build_classes() {
63+ APP_CLASS = {
64+ let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
65+ decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
66+ decl.add_method(
67+ sel!(sendEvent:),
68+ send_event as extern "C" fn(&mut Object, Sel, id),
69+ );
70+ decl.register()
71+ };
72+
73+ APP_DELEGATE_CLASS = {
74+ let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
75+ decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
76+ decl.add_method(
77+ sel!(applicationDidFinishLaunching:),
78+ did_finish_launching as extern "C" fn(&mut Object, Sel, id),
79+ );
80+ decl.add_method(
81+ sel!(applicationShouldHandleReopen:hasVisibleWindows:),
82+ should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
83+ );
84+ decl.add_method(
85+ sel!(applicationDidBecomeActive:),
86+ did_become_active as extern "C" fn(&mut Object, Sel, id),
87+ );
88+ decl.add_method(
89+ sel!(applicationDidResignActive:),
90+ did_resign_active as extern "C" fn(&mut Object, Sel, id),
91+ );
92+ decl.add_method(
93+ sel!(applicationWillTerminate:),
94+ will_terminate as extern "C" fn(&mut Object, Sel, id),
95+ );
96+ decl.add_method(
97+ sel!(handleGPUIMenuItem:),
98+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
99+ );
100+ // Add menu item handlers so that OS save panels have the correct key commands
101+ decl.add_method(
102+ sel!(cut:),
103+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
104+ );
105+ decl.add_method(
106+ sel!(copy:),
107+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
108+ );
109+ decl.add_method(
110+ sel!(paste:),
111+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
112+ );
113+ decl.add_method(
114+ sel!(selectAll:),
115+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
116+ );
117+ decl.add_method(
118+ sel!(undo:),
119+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
120+ );
121+ decl.add_method(
122+ sel!(redo:),
123+ handle_menu_item as extern "C" fn(&mut Object, Sel, id),
124+ );
125+ decl.add_method(
126+ sel!(validateMenuItem:),
127+ validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
128+ );
129+ decl.add_method(
130+ sel!(menuWillOpen:),
131+ menu_will_open as extern "C" fn(&mut Object, Sel, id),
132+ );
133+ decl.add_method(
134+ sel!(application:openURLs:),
135+ open_urls as extern "C" fn(&mut Object, Sel, id, id),
136+ );
137+ decl.register()
138+ }
139+}
140+
141+pub struct MacPlatform(RefCell<MacPlatformState>);
142+
143+pub struct MacPlatformState {
144+ executor: Rc<ForegroundExecutor>,
145+ text_system: Arc<MacTextSystem>,
146+ pasteboard: id,
147+ text_hash_pasteboard_type: id,
148+ metadata_pasteboard_type: id,
149+ become_active: Option<Box<dyn FnMut()>>,
150+ resign_active: Option<Box<dyn FnMut()>>,
151+ reopen: Option<Box<dyn FnMut()>>,
152+ quit: Option<Box<dyn FnMut()>>,
153+ event: Option<Box<dyn FnMut(Event) -> bool>>,
154+ // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
155+ // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
156+ will_open_menu: Option<Box<dyn FnMut()>>,
157+ open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
158+ finish_launching: Option<Box<dyn FnOnce()>>,
159+ // menu_actions: Vec<Box<dyn Action>>,
160+}
161+
162+impl MacPlatform {
163+ pub fn new() -> Self {
164+ Self(RefCell::new(MacPlatformState {
165+ executor: Rc::new(ForegroundExecutor::new(Arc::new(MacDispatcher)).unwrap()),
166+ text_system: Arc::new(MacTextSystem::new()),
167+ pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
168+ text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
169+ metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
170+ become_active: None,
171+ resign_active: None,
172+ reopen: None,
173+ quit: None,
174+ event: None,
175+ will_open_menu: None,
176+ open_urls: None,
177+ finish_launching: None,
178+ // menu_command: None,
179+ // validate_menu_command: None,
180+ // menu_actions: Default::default(),
181+ }))
182+ }
183+
184+ unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
185+ let pasteboard = self.0.borrow().pasteboard;
186+ let data = pasteboard.dataForType(kind);
187+ if data == nil {
188+ None
189+ } else {
190+ Some(slice::from_raw_parts(
191+ data.bytes() as *mut u8,
192+ data.length() as usize,
193+ ))
194+ }
195+ }
196+
197+ // unsafe fn create_menu_bar(
198+ // &self,
199+ // menus: Vec<Menu>,
200+ // delegate: id,
201+ // actions: &mut Vec<Box<dyn Action>>,
202+ // keystroke_matcher: &KeymapMatcher,
203+ // ) -> id {
204+ // let application_menu = NSMenu::new(nil).autorelease();
205+ // application_menu.setDelegate_(delegate);
206+
207+ // for menu_config in menus {
208+ // let menu = NSMenu::new(nil).autorelease();
209+ // menu.setTitle_(ns_string(menu_config.name));
210+ // menu.setDelegate_(delegate);
211+
212+ // for item_config in menu_config.items {
213+ // menu.addItem_(self.create_menu_item(
214+ // item_config,
215+ // delegate,
216+ // actions,
217+ // keystroke_matcher,
218+ // ));
219+ // }
220+
221+ // let menu_item = NSMenuItem::new(nil).autorelease();
222+ // menu_item.setSubmenu_(menu);
223+ // application_menu.addItem_(menu_item);
224+
225+ // if menu_config.name == "Window" {
226+ // let app: id = msg_send![APP_CLASS, sharedApplication];
227+ // app.setWindowsMenu_(menu);
228+ // }
229+ // }
230+
231+ // application_menu
232+ // }
233+
234+ // unsafe fn create_menu_item(
235+ // &self,
236+ // item: MenuItem,
237+ // delegate: id,
238+ // actions: &mut Vec<Box<dyn Action>>,
239+ // keystroke_matcher: &KeymapMatcher,
240+ // ) -> id {
241+ // match item {
242+ // MenuItem::Separator => NSMenuItem::separatorItem(nil),
243+ // MenuItem::Action {
244+ // name,
245+ // action,
246+ // os_action,
247+ // } => {
248+ // // TODO
249+ // let keystrokes = keystroke_matcher
250+ // .bindings_for_action(action.id())
251+ // .find(|binding| binding.action().eq(action.as_ref()))
252+ // .map(|binding| binding.keystrokes());
253+ // let selector = match os_action {
254+ // Some(crate::OsAction::Cut) => selector("cut:"),
255+ // Some(crate::OsAction::Copy) => selector("copy:"),
256+ // Some(crate::OsAction::Paste) => selector("paste:"),
257+ // Some(crate::OsAction::SelectAll) => selector("selectAll:"),
258+ // Some(crate::OsAction::Undo) => selector("undo:"),
259+ // Some(crate::OsAction::Redo) => selector("redo:"),
260+ // None => selector("handleGPUIMenuItem:"),
261+ // };
262+
263+ // let item;
264+ // if let Some(keystrokes) = keystrokes {
265+ // if keystrokes.len() == 1 {
266+ // let keystroke = &keystrokes[0];
267+ // let mut mask = NSEventModifierFlags::empty();
268+ // for (modifier, flag) in &[
269+ // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
270+ // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
271+ // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
272+ // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
273+ // ] {
274+ // if *modifier {
275+ // mask |= *flag;
276+ // }
277+ // }
278+
279+ // item = NSMenuItem::alloc(nil)
280+ // .initWithTitle_action_keyEquivalent_(
281+ // ns_string(name),
282+ // selector,
283+ // ns_string(key_to_native(&keystroke.key).as_ref()),
284+ // )
285+ // .autorelease();
286+ // item.setKeyEquivalentModifierMask_(mask);
287+ // }
288+ // // For multi-keystroke bindings, render the keystroke as part of the title.
289+ // else {
290+ // use std::fmt::Write;
291+
292+ // let mut name = format!("{name} [");
293+ // for (i, keystroke) in keystrokes.iter().enumerate() {
294+ // if i > 0 {
295+ // name.push(' ');
296+ // }
297+ // write!(&mut name, "{}", keystroke).unwrap();
298+ // }
299+ // name.push(']');
300+
301+ // item = NSMenuItem::alloc(nil)
302+ // .initWithTitle_action_keyEquivalent_(
303+ // ns_string(&name),
304+ // selector,
305+ // ns_string(""),
306+ // )
307+ // .autorelease();
308+ // }
309+ // } else {
310+ // item = NSMenuItem::alloc(nil)
311+ // .initWithTitle_action_keyEquivalent_(
312+ // ns_string(name),
313+ // selector,
314+ // ns_string(""),
315+ // )
316+ // .autorelease();
317+ // }
318+
319+ // let tag = actions.len() as NSInteger;
320+ // let _: () = msg_send![item, setTag: tag];
321+ // actions.push(action);
322+ // item
323+ // }
324+ // MenuItem::Submenu(Menu { name, items }) => {
325+ // let item = NSMenuItem::new(nil).autorelease();
326+ // let submenu = NSMenu::new(nil).autorelease();
327+ // submenu.setDelegate_(delegate);
328+ // for item in items {
329+ // submenu.addItem_(self.create_menu_item(
330+ // item,
331+ // delegate,
332+ // actions,
333+ // keystroke_matcher,
334+ // ));
335+ // }
336+ // item.setSubmenu_(submenu);
337+ // item.setTitle_(ns_string(name));
338+ // item
339+ // }
340+ // }
341+ // }
342+}
343+
344+impl Platform for MacPlatform {
345+ fn executor(&self) -> Rc<ForegroundExecutor> {
346+ self.0.borrow().executor.clone()
347+ }
348+
349+ fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
350+ self.0.borrow().text_system.clone()
351+ }
352+
353+ fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
354+ self.0.borrow_mut().finish_launching = Some(on_finish_launching);
355+
356+ unsafe {
357+ let app: id = msg_send![APP_CLASS, sharedApplication];
358+ let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
359+ app.setDelegate_(app_delegate);
360+
361+ let self_ptr = self as *const Self as *const c_void;
362+ (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
363+ (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
364+
365+ let pool = NSAutoreleasePool::new(nil);
366+ app.run();
367+ pool.drain();
368+
369+ (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
370+ (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
371+ }
372+ }
373+
374+ fn quit(&self) {
375+ // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
376+ // synchronously before this method terminates. If we call `Platform::quit` while holding a
377+ // borrow of the app state (which most of the time we will do), we will end up
378+ // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
379+ // this, we make quitting the application asynchronous so that we aren't holding borrows to
380+ // the app state on the stack when we actually terminate the app.
381+
382+ use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
383+
384+ unsafe {
385+ dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
386+ }
387+
388+ unsafe extern "C" fn quit(_: *mut c_void) {
389+ let app = NSApplication::sharedApplication(nil);
390+ let _: () = msg_send![app, terminate: nil];
391+ }
392+ }
393+
394+ fn restart(&self) {
395+ use std::os::unix::process::CommandExt as _;
396+
397+ let app_pid = std::process::id().to_string();
398+ let app_path = self
399+ .app_path()
400+ .ok()
401+ // When the app is not bundled, `app_path` returns the
402+ // directory containing the executable. Disregard this
403+ // and get the path to the executable itself.
404+ .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
405+ .unwrap_or_else(|| std::env::current_exe().unwrap());
406+
407+ // Wait until this process has exited and then re-open this path.
408+ let script = r#"
409+ while kill -0 $0 2> /dev/null; do
410+ sleep 0.1
411+ done
412+ open "$1"
413+ "#;
414+
415+ let restart_process = Command::new("/bin/bash")
416+ .arg("-c")
417+ .arg(script)
418+ .arg(app_pid)
419+ .arg(app_path)
420+ .process_group(0)
421+ .spawn();
422+
423+ match restart_process {
424+ Ok(_) => self.quit(),
425+ Err(e) => log::error!("failed to spawn restart script: {:?}", e),
426+ }
427+ }
428+
429+ fn activate(&self, ignoring_other_apps: bool) {
430+ unsafe {
431+ let app = NSApplication::sharedApplication(nil);
432+ app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
433+ }
434+ }
435+
436+ fn hide(&self) {
437+ unsafe {
438+ let app = NSApplication::sharedApplication(nil);
439+ let _: () = msg_send![app, hide: nil];
440+ }
441+ }
442+
443+ fn hide_other_apps(&self) {
444+ unsafe {
445+ let app = NSApplication::sharedApplication(nil);
446+ let _: () = msg_send![app, hideOtherApplications: nil];
447+ }
448+ }
449+
450+ fn unhide_other_apps(&self) {
451+ unsafe {
452+ let app = NSApplication::sharedApplication(nil);
453+ let _: () = msg_send![app, unhideAllApplications: nil];
454+ }
455+ }
456+
457+ fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
458+ MacScreen::all()
459+ .into_iter()
460+ .map(|screen| Rc::new(screen) as Rc<_>)
461+ .collect()
462+ }
463+
464+ fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>> {
465+ MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
466+ }
467+
468+ fn main_window(&self) -> Option<AnyWindowHandle> {
469+ MacWindow::main_window()
470+ }
471+
472+ // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
473+ // Box::new(StatusItem::add(self.fonts()))
474+ // }
475+
476+ fn open_window(
477+ &self,
478+ handle: AnyWindowHandle,
479+ options: WindowOptions,
480+ ) -> Box<dyn PlatformWindow> {
481+ Box::new(MacWindow::open(
482+ handle,
483+ options,
484+ self.executor(),
485+ self.text_system(),
486+ ))
487+ }
488+
489+ fn open_url(&self, url: &str) {
490+ unsafe {
491+ let url = NSURL::alloc(nil)
492+ .initWithString_(ns_string(url))
493+ .autorelease();
494+ let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
495+ msg_send![workspace, openURL: url]
496+ }
497+ }
498+
499+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
500+ self.0.borrow_mut().open_urls = Some(callback);
501+ }
502+
503+ fn prompt_for_paths(
504+ &self,
505+ options: PathPromptOptions,
506+ ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
507+ unsafe {
508+ let panel = NSOpenPanel::openPanel(nil);
509+ panel.setCanChooseDirectories_(options.directories.to_objc());
510+ panel.setCanChooseFiles_(options.files.to_objc());
511+ panel.setAllowsMultipleSelection_(options.multiple.to_objc());
512+ panel.setResolvesAliases_(false.to_objc());
513+ let (done_tx, done_rx) = oneshot::channel();
514+ let done_tx = Cell::new(Some(done_tx));
515+ let block = ConcreteBlock::new(move |response: NSModalResponse| {
516+ let result = if response == NSModalResponse::NSModalResponseOk {
517+ let mut result = Vec::new();
518+ let urls = panel.URLs();
519+ for i in 0..urls.count() {
520+ let url = urls.objectAtIndex(i);
521+ if url.isFileURL() == YES {
522+ if let Ok(path) = ns_url_to_path(url) {
523+ result.push(path)
524+ }
525+ }
526+ }
527+ Some(result)
528+ } else {
529+ None
530+ };
531+
532+ if let Some(mut done_tx) = done_tx.take() {
533+ let _ = done_tx.send(result);
534+ }
535+ });
536+ let block = block.copy();
537+ let _: () = msg_send![panel, beginWithCompletionHandler: block];
538+ done_rx
539+ }
540+ }
541+
542+ fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
543+ unsafe {
544+ let panel = NSSavePanel::savePanel(nil);
545+ let path = ns_string(directory.to_string_lossy().as_ref());
546+ let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
547+ panel.setDirectoryURL(url);
548+
549+ let (done_tx, done_rx) = oneshot::channel();
550+ let done_tx = Cell::new(Some(done_tx));
551+ let block = ConcreteBlock::new(move |response: NSModalResponse| {
552+ let mut result = None;
553+ if response == NSModalResponse::NSModalResponseOk {
554+ let url = panel.URL();
555+ if url.isFileURL() == YES {
556+ result = ns_url_to_path(panel.URL()).ok()
557+ }
558+ }
559+
560+ if let Some(mut done_tx) = done_tx.take() {
561+ let _ = done_tx.send(result);
562+ }
563+ });
564+ let block = block.copy();
565+ let _: () = msg_send![panel, beginWithCompletionHandler: block];
566+ done_rx
567+ }
568+ }
569+
570+ fn reveal_path(&self, path: &Path) {
571+ unsafe {
572+ let path = path.to_path_buf();
573+ self.0
574+ .borrow()
575+ .executor
576+ .spawn(async move {
577+ let full_path = ns_string(path.to_str().unwrap_or(""));
578+ let root_full_path = ns_string("");
579+ let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
580+ let _: BOOL = msg_send![
581+ workspace,
582+ selectFile: full_path
583+ inFileViewerRootedAtPath: root_full_path
584+ ];
585+ })
586+ .detach();
587+ }
588+ }
589+
590+ fn on_become_active(&self, callback: Box<dyn FnMut()>) {
591+ self.0.borrow_mut().become_active = Some(callback);
592+ }
593+
594+ fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
595+ self.0.borrow_mut().resign_active = Some(callback);
596+ }
597+
598+ fn on_quit(&self, callback: Box<dyn FnMut()>) {
599+ self.0.borrow_mut().quit = Some(callback);
600+ }
601+
602+ fn on_reopen(&self, callback: Box<dyn FnMut()>) {
603+ self.0.borrow_mut().reopen = Some(callback);
604+ }
605+
606+ fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
607+ self.0.borrow_mut().event = Some(callback);
608+ }
609+
610+ fn os_name(&self) -> &'static str {
611+ "macOS"
612+ }
613+
614+ fn os_version(&self) -> Result<SemanticVersion> {
615+ unsafe {
616+ let process_info = NSProcessInfo::processInfo(nil);
617+ let version = process_info.operatingSystemVersion();
618+ Ok(SemanticVersion {
619+ major: version.majorVersion as usize,
620+ minor: version.minorVersion as usize,
621+ patch: version.patchVersion as usize,
622+ })
623+ }
624+ }
625+
626+ fn app_version(&self) -> Result<SemanticVersion> {
627+ unsafe {
628+ let bundle: id = NSBundle::mainBundle();
629+ if bundle.is_null() {
630+ Err(anyhow!("app is not running inside a bundle"))
631+ } else {
632+ let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
633+ let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
634+ let bytes = version.UTF8String() as *const u8;
635+ let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
636+ version.parse()
637+ }
638+ }
639+ }
640+
641+ fn app_path(&self) -> Result<PathBuf> {
642+ unsafe {
643+ let bundle: id = NSBundle::mainBundle();
644+ if bundle.is_null() {
645+ Err(anyhow!("app is not running inside a bundle"))
646+ } else {
647+ Ok(path_from_objc(msg_send![bundle, bundlePath]))
648+ }
649+ }
650+ }
651+
652+ fn local_timezone(&self) -> UtcOffset {
653+ unsafe {
654+ let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
655+ let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
656+ UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
657+ }
658+ }
659+
660+ fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
661+ unsafe {
662+ let bundle: id = NSBundle::mainBundle();
663+ if bundle.is_null() {
664+ Err(anyhow!("app is not running inside a bundle"))
665+ } else {
666+ let name = ns_string(name);
667+ let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
668+ if url.is_null() {
669+ Err(anyhow!("resource not found"))
670+ } else {
671+ ns_url_to_path(url)
672+ }
673+ }
674+ }
675+ }
676+
677+ fn set_cursor_style(&self, style: CursorStyle) {
678+ unsafe {
679+ let new_cursor: id = match style {
680+ CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
681+ CursorStyle::ResizeLeftRight => {
682+ msg_send![class!(NSCursor), resizeLeftRightCursor]
683+ }
684+ CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
685+ CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
686+ CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
687+ };
688+
689+ let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
690+ if new_cursor != old_cursor {
691+ let _: () = msg_send![new_cursor, set];
692+ }
693+ }
694+ }
695+
696+ fn should_auto_hide_scrollbars(&self) -> bool {
697+ #[allow(non_upper_case_globals)]
698+ const NSScrollerStyleOverlay: NSInteger = 1;
699+
700+ unsafe {
701+ let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
702+ style == NSScrollerStyleOverlay
703+ }
704+ }
705+
706+ fn write_to_clipboard(&self, item: ClipboardItem) {
707+ let state = self.0.borrow();
708+ unsafe {
709+ state.pasteboard.clearContents();
710+
711+ let text_bytes = NSData::dataWithBytes_length_(
712+ nil,
713+ item.text.as_ptr() as *const c_void,
714+ item.text.len() as u64,
715+ );
716+ state
717+ .pasteboard
718+ .setData_forType(text_bytes, NSPasteboardTypeString);
719+
720+ if let Some(metadata) = item.metadata.as_ref() {
721+ let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
722+ let hash_bytes = NSData::dataWithBytes_length_(
723+ nil,
724+ hash_bytes.as_ptr() as *const c_void,
725+ hash_bytes.len() as u64,
726+ );
727+ state
728+ .pasteboard
729+ .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
730+
731+ let metadata_bytes = NSData::dataWithBytes_length_(
732+ nil,
733+ metadata.as_ptr() as *const c_void,
734+ metadata.len() as u64,
735+ );
736+ state
737+ .pasteboard
738+ .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
739+ }
740+ }
741+ }
742+
743+ fn read_from_clipboard(&self) -> Option<ClipboardItem> {
744+ let state = self.0.borrow();
745+ unsafe {
746+ if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
747+ let text = String::from_utf8_lossy(text_bytes).to_string();
748+ let hash_bytes = self
749+ .read_from_pasteboard(state.text_hash_pasteboard_type)
750+ .and_then(|bytes| bytes.try_into().ok())
751+ .map(u64::from_be_bytes);
752+ let metadata_bytes = self
753+ .read_from_pasteboard(state.metadata_pasteboard_type)
754+ .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
755+
756+ if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
757+ if hash == ClipboardItem::text_hash(&text) {
758+ Some(ClipboardItem {
759+ text,
760+ metadata: Some(metadata),
761+ })
762+ } else {
763+ Some(ClipboardItem {
764+ text,
765+ metadata: None,
766+ })
767+ }
768+ } else {
769+ Some(ClipboardItem {
770+ text,
771+ metadata: None,
772+ })
773+ }
774+ } else {
775+ None
776+ }
777+ }
778+ }
779+
780+ fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
781+ let url = CFString::from(url);
782+ let username = CFString::from(username);
783+ let password = CFData::from_buffer(password);
784+
785+ unsafe {
786+ use security::*;
787+
788+ // First, check if there are already credentials for the given server. If so, then
789+ // update the username and password.
790+ let mut verb = "updating";
791+ let mut query_attrs = CFMutableDictionary::with_capacity(2);
792+ query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
793+ query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
794+
795+ let mut attrs = CFMutableDictionary::with_capacity(4);
796+ attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
797+ attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
798+ attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
799+ attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
800+
801+ let mut status = SecItemUpdate(
802+ query_attrs.as_concrete_TypeRef(),
803+ attrs.as_concrete_TypeRef(),
804+ );
805+
806+ // If there were no existing credentials for the given server, then create them.
807+ if status == errSecItemNotFound {
808+ verb = "creating";
809+ status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
810+ }
811+
812+ if status != errSecSuccess {
813+ return Err(anyhow!("{} password failed: {}", verb, status));
814+ }
815+ }
816+ Ok(())
817+ }
818+
819+ // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
820+ // self.0.borrow_mut().menu_command = Some(callback);
821+ // }
822+
823+ // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
824+ // self.0.borrow_mut().will_open_menu = Some(callback);
825+ // }
826+
827+ // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
828+ // self.0.borrow_mut().validate_menu_command = Some(callback);
829+ // }
830+
831+ // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
832+ // unsafe {
833+ // let app: id = msg_send![APP_CLASS, sharedApplication];
834+ // let mut state = self.0.borrow_mut();
835+ // let actions = &mut state.menu_actions;
836+ // app.setMainMenu_(self.create_menu_bar(
837+ // menus,
838+ // app.delegate(),
839+ // actions,
840+ // keystroke_matcher,
841+ // ));
842+ // }
843+ // }
844+
845+ fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
846+ let url = CFString::from(url);
847+ let cf_true = CFBoolean::true_value().as_CFTypeRef();
848+
849+ unsafe {
850+ use security::*;
851+
852+ // Find any credentials for the given server URL.
853+ let mut attrs = CFMutableDictionary::with_capacity(5);
854+ attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
855+ attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
856+ attrs.set(kSecReturnAttributes as *const _, cf_true);
857+ attrs.set(kSecReturnData as *const _, cf_true);
858+
859+ let mut result = CFTypeRef::from(ptr::null());
860+ let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
861+ match status {
862+ security::errSecSuccess => {}
863+ security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
864+ _ => return Err(anyhow!("reading password failed: {}", status)),
865+ }
866+
867+ let result = CFType::wrap_under_create_rule(result)
868+ .downcast::<CFDictionary>()
869+ .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
870+ let username = result
871+ .find(kSecAttrAccount as *const _)
872+ .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
873+ let username = CFType::wrap_under_get_rule(*username)
874+ .downcast::<CFString>()
875+ .ok_or_else(|| anyhow!("account was not a string"))?;
876+ let password = result
877+ .find(kSecValueData as *const _)
878+ .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
879+ let password = CFType::wrap_under_get_rule(*password)
880+ .downcast::<CFData>()
881+ .ok_or_else(|| anyhow!("password was not a string"))?;
882+
883+ Ok(Some((username.to_string(), password.bytes().to_vec())))
884+ }
885+ }
886+
887+ fn delete_credentials(&self, url: &str) -> Result<()> {
888+ let url = CFString::from(url);
889+
890+ unsafe {
891+ use security::*;
892+
893+ let mut query_attrs = CFMutableDictionary::with_capacity(2);
894+ query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
895+ query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
896+
897+ let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
898+
899+ if status != errSecSuccess {
900+ return Err(anyhow!("delete password failed: {}", status));
901+ }
902+ }
903+ Ok(())
904+ }
905+}
906+
907+unsafe fn path_from_objc(path: id) -> PathBuf {
908+ let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
909+ let bytes = path.UTF8String() as *const u8;
910+ let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
911+ PathBuf::from(path)
912+}
913+
914+unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
915+ let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
916+ assert!(!platform_ptr.is_null());
917+ &*(platform_ptr as *const MacPlatform)
918+}
919+
920+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
921+ unsafe {
922+ if let Some(event) = Event::from_native(native_event, None) {
923+ let platform = get_foreground_platform(this);
924+ if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
925+ if callback(event) {
926+ return;
927+ }
928+ }
929+ }
930+ msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
931+ }
932+}
933+
934+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
935+ unsafe {
936+ let app: id = msg_send![APP_CLASS, sharedApplication];
937+ app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
938+
939+ let platform = get_foreground_platform(this);
940+ let callback = platform.0.borrow_mut().finish_launching.take();
941+ if let Some(callback) = callback {
942+ callback();
943+ }
944+ }
945+}
946+
947+extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
948+ if !has_open_windows {
949+ let platform = unsafe { get_foreground_platform(this) };
950+ if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
951+ callback();
952+ }
953+ }
954+}
955+
956+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
957+ let platform = unsafe { get_foreground_platform(this) };
958+ if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
959+ callback();
960+ }
961+}
962+
963+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
964+ let platform = unsafe { get_foreground_platform(this) };
965+ if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
966+ callback();
967+ }
968+}
969+
970+extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
971+ let platform = unsafe { get_foreground_platform(this) };
972+ if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
973+ callback();
974+ }
975+}
976+
977+extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
978+ let urls = unsafe {
979+ (0..urls.count())
980+ .into_iter()
981+ .filter_map(|i| {
982+ let url = urls.objectAtIndex(i);
983+ match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
984+ Ok(string) => Some(string.to_string()),
985+ Err(err) => {
986+ log::error!("error converting path to string: {}", err);
987+ None
988+ }
989+ }
990+ })
991+ .collect::<Vec<_>>()
992+ };
993+ let platform = unsafe { get_foreground_platform(this) };
994+ if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
995+ callback(urls);
996+ }
997+}
998+
999+extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
1000+ todo!()
1001+ // unsafe {
1002+ // let platform = get_foreground_platform(this);
1003+ // let mut platform = platform.0.borrow_mut();
1004+ // if let Some(mut callback) = platform.menu_command.take() {
1005+ // let tag: NSInteger = msg_send![item, tag];
1006+ // let index = tag as usize;
1007+ // if let Some(action) = platform.menu_actions.get(index) {
1008+ // callback(action.as_ref());
1009+ // }
1010+ // platform.menu_command = Some(callback);
1011+ // }
1012+ // }
1013+}
1014+
1015+extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
1016+ todo!()
1017+ // unsafe {
1018+ // let mut result = false;
1019+ // let platform = get_foreground_platform(this);
1020+ // let mut platform = platform.0.borrow_mut();
1021+ // if let Some(mut callback) = platform.validate_menu_command.take() {
1022+ // let tag: NSInteger = msg_send![item, tag];
1023+ // let index = tag as usize;
1024+ // if let Some(action) = platform.menu_actions.get(index) {
1025+ // result = callback(action.as_ref());
1026+ // }
1027+ // platform.validate_menu_command = Some(callback);
1028+ // }
1029+ // result
1030+ // }
1031+}
1032+
1033+extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
1034+ unsafe {
1035+ let platform = get_foreground_platform(this);
1036+ let mut platform = platform.0.borrow_mut();
1037+ if let Some(mut callback) = platform.will_open_menu.take() {
1038+ callback();
1039+ platform.will_open_menu = Some(callback);
1040+ }
1041+ }
1042+}
1043+
1044+unsafe fn ns_string(string: &str) -> id {
1045+ NSString::alloc(nil).init_str(string).autorelease()
1046+}
1047+
1048+unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
1049+ let path: *mut c_char = msg_send![url, fileSystemRepresentation];
1050+ if path.is_null() {
1051+ Err(anyhow!(
1052+ "url is not a file path: {}",
1053+ CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
1054+ ))
1055+ } else {
1056+ Ok(PathBuf::from(OsStr::from_bytes(
1057+ CStr::from_ptr(path).to_bytes(),
1058+ )))
1059+ }
1060+}
1061+
1062+mod security {
1063+ #![allow(non_upper_case_globals)]
1064+ use super::*;
1065+
1066+ #[link(name = "Security", kind = "framework")]
1067+ extern "C" {
1068+ pub static kSecClass: CFStringRef;
1069+ pub static kSecClassInternetPassword: CFStringRef;
1070+ pub static kSecAttrServer: CFStringRef;
1071+ pub static kSecAttrAccount: CFStringRef;
1072+ pub static kSecValueData: CFStringRef;
1073+ pub static kSecReturnAttributes: CFStringRef;
1074+ pub static kSecReturnData: CFStringRef;
1075+
1076+ pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1077+ pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
1078+ pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
1079+ pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1080+ }
1081+
1082+ pub const errSecSuccess: OSStatus = 0;
1083+ pub const errSecUserCanceled: OSStatus = -128;
1084+ pub const errSecItemNotFound: OSStatus = -25300;
1085+}
1086+
1087+#[cfg(test)]
1088+mod tests {
1089+ use crate::ClipboardItem;
1090+
1091+ use super::*;
1092+
1093+ #[test]
1094+ fn test_clipboard() {
1095+ let platform = build_platform();
1096+ assert_eq!(platform.read_from_clipboard(), None);
1097+
1098+ let item = ClipboardItem::new("1".to_string());
1099+ platform.write_to_clipboard(item.clone());
1100+ assert_eq!(platform.read_from_clipboard(), Some(item));
1101+
1102+ let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);
1103+ platform.write_to_clipboard(item.clone());
1104+ assert_eq!(platform.read_from_clipboard(), Some(item));
1105+
1106+ let text_from_other_app = "text from other app";
1107+ unsafe {
1108+ let bytes = NSData::dataWithBytes_length_(
1109+ nil,
1110+ text_from_other_app.as_ptr() as *const c_void,
1111+ text_from_other_app.len() as u64,
1112+ );
1113+ platform
1114+ .0
1115+ .borrow_mut()
1116+ .pasteboard
1117+ .setData_forType(bytes, NSPasteboardTypeString);
1118+ }
1119+ assert_eq!(
1120+ platform.read_from_clipboard(),
1121+ Some(ClipboardItem::new(text_from_other_app.to_string()))
1122+ );
1123+ }
1124+
1125+ fn build_platform() -> MacPlatform {
1126+ let mut platform = MacPlatform::new();
1127+ platform.0.borrow_mut().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };
1128+ platform
1129+ }
1130+}
crates/gpui3/src/platform/mac/text_system.rs+757−0
@@ -0,0 +1,757 @@
1+use crate::{
2+ point, px, size, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
3+ GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Run, RunStyle,
4+ Size,
5+};
6+use cocoa::appkit::{CGFloat, CGPoint};
7+use collections::HashMap;
8+use core_foundation::{
9+ array::CFIndex,
10+ attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
11+ base::{CFRange, TCFType},
12+ string::CFString,
13+};
14+use core_graphics::{
15+ base::{kCGImageAlphaPremultipliedLast, CGGlyph},
16+ color_space::CGColorSpace,
17+ context::CGContext,
18+};
19+use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
20+use font_kit::{
21+ handle::Handle, hinting::HintingOptions, metrics::Metrics, source::SystemSource,
22+ sources::mem::MemSource,
23+};
24+use parking_lot::RwLock;
25+use pathfinder_geometry::{
26+ rect::{RectF, RectI},
27+ transform2d::Transform2F,
28+ vector::{Vector2F, Vector2I},
29+};
30+use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
31+
32+use super::open_type;
33+
34+#[allow(non_upper_case_globals)]
35+const kCGImageAlphaOnly: u32 = 7;
36+
37+pub struct MacTextSystem(RwLock<TextSystemState>);
38+
39+struct TextSystemState {
40+ memory_source: MemSource,
41+ system_source: SystemSource,
42+ fonts: Vec<font_kit::font::Font>,
43+ font_ids_by_postscript_name: HashMap<String, FontId>,
44+ postscript_names_by_font_id: HashMap<FontId, String>,
45+}
46+
47+impl MacTextSystem {
48+ pub fn new() -> Self {
49+ Self(RwLock::new(TextSystemState {
50+ memory_source: MemSource::empty(),
51+ system_source: SystemSource::new(),
52+ fonts: Vec::new(),
53+ font_ids_by_postscript_name: Default::default(),
54+ postscript_names_by_font_id: Default::default(),
55+ }))
56+ }
57+}
58+
59+impl Default for MacTextSystem {
60+ fn default() -> Self {
61+ Self::new()
62+ }
63+}
64+
65+impl PlatformTextSystem for MacTextSystem {
66+ fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
67+ self.0.write().add_fonts(fonts)
68+ }
69+
70+ fn all_families(&self) -> Vec<String> {
71+ self.0
72+ .read()
73+ .system_source
74+ .all_families()
75+ .expect("core text should never return an error")
76+ }
77+
78+ fn load_family(&self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
79+ self.0.write().load_family(name, features)
80+ }
81+
82+ fn select_font(
83+ &self,
84+ font_ids: &[FontId],
85+ weight: FontWeight,
86+ style: FontStyle,
87+ ) -> anyhow::Result<FontId> {
88+ self.0.read().select_font(font_ids, weight, style)
89+ }
90+
91+ fn font_metrics(&self, font_id: FontId) -> FontMetrics {
92+ self.0.read().font_metrics(font_id)
93+ }
94+
95+ fn typographic_bounds(
96+ &self,
97+ font_id: FontId,
98+ glyph_id: GlyphId,
99+ ) -> anyhow::Result<Bounds<f32>> {
100+ self.0.read().typographic_bounds(font_id, glyph_id)
101+ }
102+
103+ fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
104+ self.0.read().advance(font_id, glyph_id)
105+ }
106+
107+ fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
108+ self.0.read().glyph_for_char(font_id, ch)
109+ }
110+
111+ fn rasterize_glyph(
112+ &self,
113+ font_id: FontId,
114+ font_size: f32,
115+ glyph_id: GlyphId,
116+ subpixel_shift: Point<Pixels>,
117+ scale_factor: f32,
118+ options: RasterizationOptions,
119+ ) -> Option<(Bounds<u32>, Vec<u8>)> {
120+ self.0.read().rasterize_glyph(
121+ font_id,
122+ font_size,
123+ glyph_id,
124+ subpixel_shift,
125+ scale_factor,
126+ options,
127+ )
128+ }
129+
130+ fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, RunStyle)]) -> LineLayout {
131+ self.0.write().layout_line(text, font_size, runs)
132+ }
133+
134+ fn wrap_line(
135+ &self,
136+ text: &str,
137+ font_id: FontId,
138+ font_size: Pixels,
139+ width: Pixels,
140+ ) -> Vec<usize> {
141+ self.0.read().wrap_line(text, font_id, font_size, width)
142+ }
143+}
144+
145+impl TextSystemState {
146+ fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> anyhow::Result<()> {
147+ self.memory_source.add_fonts(
148+ fonts
149+ .iter()
150+ .map(|bytes| Handle::from_memory(bytes.clone(), 0)),
151+ )?;
152+ Ok(())
153+ }
154+
155+ fn load_family(&mut self, name: &str, features: &FontFeatures) -> anyhow::Result<Vec<FontId>> {
156+ let mut font_ids = Vec::new();
157+
158+ let family = self
159+ .memory_source
160+ .select_family_by_name(name)
161+ .or_else(|_| self.system_source.select_family_by_name(name))?;
162+ for font in family.fonts() {
163+ let mut font = font.load()?;
164+ open_type::apply_features(&mut font, features);
165+ let font_id = FontId(self.fonts.len());
166+ font_ids.push(font_id);
167+ let postscript_name = font.postscript_name().unwrap();
168+ self.font_ids_by_postscript_name
169+ .insert(postscript_name.clone(), font_id);
170+ self.postscript_names_by_font_id
171+ .insert(font_id, postscript_name);
172+ self.fonts.push(font);
173+ }
174+ Ok(font_ids)
175+ }
176+
177+ fn select_font(
178+ &self,
179+ font_ids: &[FontId],
180+ weight: FontWeight,
181+ style: FontStyle,
182+ ) -> anyhow::Result<FontId> {
183+ let candidates = font_ids
184+ .iter()
185+ .map(|font_id| self.fonts[font_id.0].properties())
186+ .collect::<Vec<_>>();
187+ let idx = font_kit::matching::find_best_match(
188+ &candidates,
189+ &font_kit::properties::Properties {
190+ style,
191+ weight,
192+ stretch: Default::default(),
193+ },
194+ )?;
195+ Ok(font_ids[idx])
196+ }
197+
198+ fn font_metrics(&self, font_id: FontId) -> FontMetrics {
199+ self.fonts[font_id.0].metrics().into()
200+ }
201+
202+ fn typographic_bounds(
203+ &self,
204+ font_id: FontId,
205+ glyph_id: GlyphId,
206+ ) -> anyhow::Result<Bounds<f32>> {
207+ Ok(self.fonts[font_id.0]
208+ .typographic_bounds(glyph_id.into())?
209+ .into())
210+ }
211+
212+ fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>> {
213+ Ok(self.fonts[font_id.0].advance(glyph_id.into())?.into())
214+ }
215+
216+ fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
217+ self.fonts[font_id.0].glyph_for_char(ch).map(Into::into)
218+ }
219+
220+ fn id_for_native_font(&mut self, requested_font: CTFont) -> FontId {
221+ let postscript_name = requested_font.postscript_name();
222+ if let Some(font_id) = self.font_ids_by_postscript_name.get(&postscript_name) {
223+ *font_id
224+ } else {
225+ let font_id = FontId(self.fonts.len());
226+ self.font_ids_by_postscript_name
227+ .insert(postscript_name.clone(), font_id);
228+ self.postscript_names_by_font_id
229+ .insert(font_id, postscript_name);
230+ self.fonts
231+ .push(font_kit::font::Font::from_core_graphics_font(
232+ requested_font.copy_to_CGFont(),
233+ ));
234+ font_id
235+ }
236+ }
237+
238+ fn is_emoji(&self, font_id: FontId) -> bool {
239+ self.postscript_names_by_font_id
240+ .get(&font_id)
241+ .map_or(false, |postscript_name| {
242+ postscript_name == "AppleColorEmoji"
243+ })
244+ }
245+
246+ fn rasterize_glyph(
247+ &self,
248+ font_id: FontId,
249+ font_size: f32,
250+ glyph_id: GlyphId,
251+ subpixel_shift: Point<Pixels>,
252+ scale_factor: f32,
253+ options: RasterizationOptions,
254+ ) -> Option<(Bounds<u32>, Vec<u8>)> {
255+ let font = &self.fonts[font_id.0];
256+ let scale = Transform2F::from_scale(scale_factor);
257+ let glyph_bounds = font
258+ .raster_bounds(
259+ glyph_id.into(),
260+ font_size,
261+ scale,
262+ HintingOptions::None,
263+ font_kit::canvas::RasterizationOptions::GrayscaleAa,
264+ )
265+ .ok()?;
266+
267+ if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
268+ None
269+ } else {
270+ // Make room for subpixel variants.
271+ let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
272+ let cx_bounds = RectI::new(
273+ glyph_bounds.origin(),
274+ glyph_bounds.size() + Vector2I::from(subpixel_padding),
275+ );
276+
277+ let mut bytes;
278+ let cx;
279+ match options {
280+ RasterizationOptions::Alpha => {
281+ bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
282+ cx = CGContext::create_bitmap_context(
283+ Some(bytes.as_mut_ptr() as *mut _),
284+ cx_bounds.width() as usize,
285+ cx_bounds.height() as usize,
286+ 8,
287+ cx_bounds.width() as usize,
288+ &CGColorSpace::create_device_gray(),
289+ kCGImageAlphaOnly,
290+ );
291+ }
292+ RasterizationOptions::Bgra => {
293+ bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
294+ cx = CGContext::create_bitmap_context(
295+ Some(bytes.as_mut_ptr() as *mut _),
296+ cx_bounds.width() as usize,
297+ cx_bounds.height() as usize,
298+ 8,
299+ cx_bounds.width() as usize * 4,
300+ &CGColorSpace::create_device_rgb(),
301+ kCGImageAlphaPremultipliedLast,
302+ );
303+ }
304+ }
305+
306+ // Move the origin to bottom left and account for scaling, this
307+ // makes drawing text consistent with the font-kit's raster_bounds.
308+ cx.translate(
309+ -glyph_bounds.origin_x() as CGFloat,
310+ (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
311+ );
312+ cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
313+
314+ cx.set_allows_font_subpixel_positioning(true);
315+ cx.set_should_subpixel_position_fonts(true);
316+ cx.set_allows_font_subpixel_quantization(false);
317+ cx.set_should_subpixel_quantize_fonts(false);
318+ font.native_font()
319+ .clone_with_font_size(font_size as CGFloat)
320+ .draw_glyphs(
321+ &[u32::from(glyph_id) as CGGlyph],
322+ &[CGPoint::new(
323+ (f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
324+ (f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
325+ )],
326+ cx,
327+ );
328+
329+ if let RasterizationOptions::Bgra = options {
330+ // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
331+ for pixel in bytes.chunks_exact_mut(4) {
332+ pixel.swap(0, 2);
333+ let a = pixel[3] as f32 / 255.;
334+ pixel[0] = (pixel[0] as f32 / a) as u8;
335+ pixel[1] = (pixel[1] as f32 / a) as u8;
336+ pixel[2] = (pixel[2] as f32 / a) as u8;
337+ }
338+ }
339+
340+ Some((cx_bounds.into(), bytes))
341+ }
342+ }
343+
344+ fn layout_line(
345+ &mut self,
346+ text: &str,
347+ font_size: Pixels,
348+ runs: &[(usize, RunStyle)],
349+ ) -> LineLayout {
350+ // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
351+ let mut string = CFMutableAttributedString::new();
352+ {
353+ string.replace_str(&CFString::new(text), CFRange::init(0, 0));
354+ let utf16_line_len = string.char_len() as usize;
355+
356+ let last_run: RefCell<Option<(usize, FontId)>> = Default::default();
357+ let font_runs = runs
358+ .iter()
359+ .filter_map(|(len, style)| {
360+ let mut last_run = last_run.borrow_mut();
361+ if let Some((last_len, last_font_id)) = last_run.as_mut() {
362+ if style.font_id == *last_font_id {
363+ *last_len += *len;
364+ None
365+ } else {
366+ let result = (*last_len, *last_font_id);
367+ *last_len = *len;
368+ *last_font_id = style.font_id;
369+ Some(result)
370+ }
371+ } else {
372+ *last_run = Some((*len, style.font_id));
373+ None
374+ }
375+ })
376+ .chain(std::iter::from_fn(|| last_run.borrow_mut().take()));
377+
378+ let mut ix_converter = StringIndexConverter::new(text);
379+ for (run_len, font_id) in font_runs {
380+ let utf8_end = ix_converter.utf8_ix + run_len;
381+ let utf16_start = ix_converter.utf16_ix;
382+
383+ if utf16_start >= utf16_line_len {
384+ break;
385+ }
386+
387+ ix_converter.advance_to_utf8_ix(utf8_end);
388+ let utf16_end = cmp::min(ix_converter.utf16_ix, utf16_line_len);
389+
390+ let cf_range =
391+ CFRange::init(utf16_start as isize, (utf16_end - utf16_start) as isize);
392+ let font = &self.fonts[font_id.0];
393+ unsafe {
394+ string.set_attribute(
395+ cf_range,
396+ kCTFontAttributeName,
397+ &font.native_font().clone_with_font_size(font_size.into()),
398+ );
399+ }
400+
401+ if utf16_end == utf16_line_len {
402+ break;
403+ }
404+ }
405+ }
406+
407+ // Retrieve the glyphs from the shaped line, converting UTF16 offsets to UTF8 offsets.
408+ let line = CTLine::new_with_attributed_string(string.as_concrete_TypeRef());
409+
410+ let mut runs = Vec::new();
411+ for run in line.glyph_runs().into_iter() {
412+ let attributes = run.attributes().unwrap();
413+ let font = unsafe {
414+ attributes
415+ .get(kCTFontAttributeName)
416+ .downcast::<CTFont>()
417+ .unwrap()
418+ };
419+ let font_id = self.id_for_native_font(font);
420+
421+ let mut ix_converter = StringIndexConverter::new(text);
422+ let mut glyphs = Vec::new();
423+ for ((glyph_id, position), glyph_utf16_ix) in run
424+ .glyphs()
425+ .iter()
426+ .zip(run.positions().iter())
427+ .zip(run.string_indices().iter())
428+ {
429+ let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
430+ ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
431+ glyphs.push(Glyph {
432+ id: (*glyph_id).into(),
433+ position: point(position.x as f32, position.y as f32).map(px),
434+ index: ix_converter.utf8_ix,
435+ is_emoji: self.is_emoji(font_id),
436+ });
437+ }
438+
439+ runs.push(Run { font_id, glyphs })
440+ }
441+
442+ let typographic_bounds = line.get_typographic_bounds();
443+ LineLayout {
444+ width: typographic_bounds.width.into(),
445+ ascent: typographic_bounds.ascent.into(),
446+ descent: typographic_bounds.descent.into(),
447+ runs,
448+ font_size,
449+ len: text.len(),
450+ }
451+ }
452+
453+ fn wrap_line(
454+ &self,
455+ text: &str,
456+ font_id: FontId,
457+ font_size: Pixels,
458+ width: Pixels,
459+ ) -> Vec<usize> {
460+ let mut string = CFMutableAttributedString::new();
461+ string.replace_str(&CFString::new(text), CFRange::init(0, 0));
462+ let cf_range = CFRange::init(0, text.encode_utf16().count() as isize);
463+ let font = &self.fonts[font_id.0];
464+ unsafe {
465+ string.set_attribute(
466+ cf_range,
467+ kCTFontAttributeName,
468+ &font.native_font().clone_with_font_size(font_size.into()),
469+ );
470+
471+ let typesetter = CTTypesetterCreateWithAttributedString(string.as_concrete_TypeRef());
472+ let mut ix_converter = StringIndexConverter::new(text);
473+ let mut break_indices = Vec::new();
474+ while ix_converter.utf8_ix < text.len() {
475+ let utf16_len = CTTypesetterSuggestLineBreak(
476+ typesetter,
477+ ix_converter.utf16_ix as isize,
478+ width.into(),
479+ ) as usize;
480+ ix_converter.advance_to_utf16_ix(ix_converter.utf16_ix + utf16_len);
481+ if ix_converter.utf8_ix >= text.len() {
482+ break;
483+ }
484+ break_indices.push(ix_converter.utf8_ix as usize);
485+ }
486+ break_indices
487+ }
488+ }
489+}
490+
491+#[derive(Clone)]
492+struct StringIndexConverter<'a> {
493+ text: &'a str,
494+ utf8_ix: usize,
495+ utf16_ix: usize,
496+}
497+
498+impl<'a> StringIndexConverter<'a> {
499+ fn new(text: &'a str) -> Self {
500+ Self {
501+ text,
502+ utf8_ix: 0,
503+ utf16_ix: 0,
504+ }
505+ }
506+
507+ fn advance_to_utf8_ix(&mut self, utf8_target: usize) {
508+ for (ix, c) in self.text[self.utf8_ix..].char_indices() {
509+ if self.utf8_ix + ix >= utf8_target {
510+ self.utf8_ix += ix;
511+ return;
512+ }
513+ self.utf16_ix += c.len_utf16();
514+ }
515+ self.utf8_ix = self.text.len();
516+ }
517+
518+ fn advance_to_utf16_ix(&mut self, utf16_target: usize) {
519+ for (ix, c) in self.text[self.utf8_ix..].char_indices() {
520+ if self.utf16_ix >= utf16_target {
521+ self.utf8_ix += ix;
522+ return;
523+ }
524+ self.utf16_ix += c.len_utf16();
525+ }
526+ self.utf8_ix = self.text.len();
527+ }
528+}
529+
530+#[repr(C)]
531+pub struct __CFTypesetter(c_void);
532+
533+pub type CTTypesetterRef = *const __CFTypesetter;
534+
535+#[link(name = "CoreText", kind = "framework")]
536+extern "C" {
537+ fn CTTypesetterCreateWithAttributedString(string: CFAttributedStringRef) -> CTTypesetterRef;
538+
539+ fn CTTypesetterSuggestLineBreak(
540+ typesetter: CTTypesetterRef,
541+ start_index: CFIndex,
542+ width: f64,
543+ ) -> CFIndex;
544+}
545+
546+impl From<Metrics> for FontMetrics {
547+ fn from(metrics: Metrics) -> Self {
548+ FontMetrics {
549+ units_per_em: metrics.units_per_em,
550+ ascent: metrics.ascent,
551+ descent: metrics.descent,
552+ line_gap: metrics.line_gap,
553+ underline_position: metrics.underline_position,
554+ underline_thickness: metrics.underline_thickness,
555+ cap_height: metrics.cap_height,
556+ x_height: metrics.x_height,
557+ bounding_box: metrics.bounding_box.into(),
558+ }
559+ }
560+}
561+
562+impl From<RectF> for Bounds<f32> {
563+ fn from(rect: RectF) -> Self {
564+ Bounds {
565+ origin: point(rect.origin_x(), rect.origin_y()),
566+ size: size(rect.width(), rect.height()),
567+ }
568+ }
569+}
570+
571+impl From<RectI> for Bounds<u32> {
572+ fn from(rect: RectI) -> Self {
573+ Bounds {
574+ origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
575+ size: size(rect.width() as u32, rect.height() as u32),
576+ }
577+ }
578+}
579+
580+impl From<Point<u32>> for Vector2I {
581+ fn from(size: Point<u32>) -> Self {
582+ Vector2I::new(size.x as i32, size.y as i32)
583+ }
584+}
585+
586+impl From<Vector2F> for Size<f32> {
587+ fn from(vec: Vector2F) -> Self {
588+ size(vec.x(), vec.y())
589+ }
590+}
591+
592+// #[cfg(test)]
593+// mod tests {
594+// use super::*;
595+// use crate::AppContext;
596+// use font_kit::properties::{Style, Weight};
597+// use platform::FontSystem as _;
598+
599+// #[crate::test(self, retries = 5)]
600+// fn test_layout_str(_: &mut AppContext) {
601+// // This is failing intermittently on CI and we don't have time to figure it out
602+// let fonts = FontSystem::new();
603+// let menlo = fonts.load_family("Menlo", &Default::default()).unwrap();
604+// let menlo_regular = RunStyle {
605+// font_id: fonts.select_font(&menlo, &Properties::new()).unwrap(),
606+// color: Default::default(),
607+// underline: Default::default(),
608+// };
609+// let menlo_italic = RunStyle {
610+// font_id: fonts
611+// .select_font(&menlo, Properties::new().style(Style::Italic))
612+// .unwrap(),
613+// color: Default::default(),
614+// underline: Default::default(),
615+// };
616+// let menlo_bold = RunStyle {
617+// font_id: fonts
618+// .select_font(&menlo, Properties::new().weight(Weight::BOLD))
619+// .unwrap(),
620+// color: Default::default(),
621+// underline: Default::default(),
622+// };
623+// assert_ne!(menlo_regular, menlo_italic);
624+// assert_ne!(menlo_regular, menlo_bold);
625+// assert_ne!(menlo_italic, menlo_bold);
626+
627+// let line = fonts.layout_line(
628+// "hello world",
629+// 16.0,
630+// &[(2, menlo_bold), (4, menlo_italic), (5, menlo_regular)],
631+// );
632+// assert_eq!(line.runs.len(), 3);
633+// assert_eq!(line.runs[0].font_id, menlo_bold.font_id);
634+// assert_eq!(line.runs[0].glyphs.len(), 2);
635+// assert_eq!(line.runs[1].font_id, menlo_italic.font_id);
636+// assert_eq!(line.runs[1].glyphs.len(), 4);
637+// assert_eq!(line.runs[2].font_id, menlo_regular.font_id);
638+// assert_eq!(line.runs[2].glyphs.len(), 5);
639+// }
640+
641+// #[test]
642+// fn test_glyph_offsets() -> anyhow::Result<()> {
643+// let fonts = FontSystem::new();
644+// let zapfino = fonts.load_family("Zapfino", &Default::default())?;
645+// let zapfino_regular = RunStyle {
646+// font_id: fonts.select_font(&zapfino, &Properties::new())?,
647+// color: Default::default(),
648+// underline: Default::default(),
649+// };
650+// let menlo = fonts.load_family("Menlo", &Default::default())?;
651+// let menlo_regular = RunStyle {
652+// font_id: fonts.select_font(&menlo, &Properties::new())?,
653+// color: Default::default(),
654+// underline: Default::default(),
655+// };
656+
657+// let text = "This is, m𐍈re 𐍈r less, Zapfino!𐍈";
658+// let line = fonts.layout_line(
659+// text,
660+// 16.0,
661+// &[
662+// (9, zapfino_regular),
663+// (13, menlo_regular),
664+// (text.len() - 22, zapfino_regular),
665+// ],
666+// );
667+// assert_eq!(
668+// line.runs
669+// .iter()
670+// .flat_map(|r| r.glyphs.iter())
671+// .map(|g| g.index)
672+// .collect::<Vec<_>>(),
673+// vec![0, 2, 4, 5, 7, 8, 9, 10, 14, 15, 16, 17, 21, 22, 23, 24, 26, 27, 28, 29, 36, 37],
674+// );
675+// Ok(())
676+// }
677+
678+// #[test]
679+// #[ignore]
680+// fn test_rasterize_glyph() {
681+// use std::{fs::File, io::BufWriter, path::Path};
682+
683+// let fonts = FontSystem::new();
684+// let font_ids = fonts.load_family("Fira Code", &Default::default()).unwrap();
685+// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
686+// let glyph_id = fonts.glyph_for_char(font_id, 'G').unwrap();
687+
688+// const VARIANTS: usize = 1;
689+// for i in 0..VARIANTS {
690+// let variant = i as f32 / VARIANTS as f32;
691+// let (bounds, bytes) = fonts
692+// .rasterize_glyph(
693+// font_id,
694+// 16.0,
695+// glyph_id,
696+// vec2f(variant, variant),
697+// 2.,
698+// RasterizationOptions::Alpha,
699+// )
700+// .unwrap();
701+
702+// let name = format!("/Users/as-cii/Desktop/twog-{}.png", i);
703+// let path = Path::new(&name);
704+// let file = File::create(path).unwrap();
705+// let w = &mut BufWriter::new(file);
706+
707+// let mut encoder = png::Encoder::new(w, bounds.width() as u32, bounds.height() as u32);
708+// encoder.set_color(png::ColorType::Grayscale);
709+// encoder.set_depth(png::BitDepth::Eight);
710+// let mut writer = encoder.write_header().unwrap();
711+// writer.write_image_data(&bytes).unwrap();
712+// }
713+// }
714+
715+// #[test]
716+// fn test_wrap_line() {
717+// let fonts = FontSystem::new();
718+// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
719+// let font_id = fonts.select_font(&font_ids, &Default::default()).unwrap();
720+
721+// let line = "one two three four five\n";
722+// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
723+// assert_eq!(wrap_boundaries, &["one two ".len(), "one two three ".len()]);
724+
725+// let line = "aaa ααα ✋✋✋ 🎉🎉🎉\n";
726+// let wrap_boundaries = fonts.wrap_line(line, font_id, 16., 64.0);
727+// assert_eq!(
728+// wrap_boundaries,
729+// &["aaa ααα ".len(), "aaa ααα ✋✋✋ ".len(),]
730+// );
731+// }
732+
733+// #[test]
734+// fn test_layout_line_bom_char() {
735+// let fonts = FontSystem::new();
736+// let font_ids = fonts.load_family("Helvetica", &Default::default()).unwrap();
737+// let style = RunStyle {
738+// font_id: fonts.select_font(&font_ids, &Default::default()).unwrap(),
739+// color: Default::default(),
740+// underline: Default::default(),
741+// };
742+
743+// let line = "\u{feff}";
744+// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
745+// assert_eq!(layout.len, line.len());
746+// assert!(layout.runs.is_empty());
747+
748+// let line = "a\u{feff}b";
749+// let layout = fonts.layout_line(line, 16., &[(line.len(), style)]);
750+// assert_eq!(layout.len, line.len());
751+// assert_eq!(layout.runs.len(), 1);
752+// assert_eq!(layout.runs[0].glyphs.len(), 2);
753+// assert_eq!(layout.runs[0].glyphs[0].id, 68); // a
754+// // There's no glyph for \u{feff}
755+// assert_eq!(layout.runs[0].glyphs[1].id, 69); // b
756+// }
757+// }
crates/gpui3/src/platform/test.rs+140−2
@@ -9,7 +9,51 @@ impl TestPlatform { · 8 unmodified lines
99 }
1010
1111 impl Platform for TestPlatform {
12− fn font_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
12+ fn executor(&self) -> std::rc::Rc<crate::ForegroundExecutor> {
13+ todo!()
14+ }
15+
16+ fn text_system(&self) -> std::sync::Arc<dyn crate::PlatformTextSystem> {
17+ todo!()
18+ }
19+
20+ fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
21+ todo!()
22+ }
23+
24+ fn quit(&self) {
25+ todo!()
26+ }
27+
28+ fn restart(&self) {
29+ todo!()
30+ }
31+
32+ fn activate(&self, ignoring_other_apps: bool) {
33+ todo!()
34+ }
35+
36+ fn hide(&self) {
37+ todo!()
38+ }
39+
40+ fn hide_other_apps(&self) {
41+ todo!()
42+ }
43+
44+ fn unhide_other_apps(&self) {
45+ todo!()
46+ }
47+
48+ fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
49+ todo!()
50+ }
51+
52+ fn screen_by_id(&self, id: uuid::Uuid) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
53+ todo!()
54+ }
55+
56+ fn main_window(&self) -> Option<crate::AnyWindowHandle> {
1357 todo!()
1458 }
1559
@@ -21,7 +65,101 @@ impl Platform for TestPlatform { · 5 unmodified lines
2165 todo!()
2266 }
2367
24− fn dispatcher(&self) -> std::sync::Arc<dyn crate::PlatformDispatcher> {
68+ fn open_url(&self, url: &str) {
69+ todo!()
70+ }
71+
72+ fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
73+ todo!()
74+ }
75+
76+ fn prompt_for_paths(
77+ &self,
78+ options: crate::PathPromptOptions,
79+ ) -> futures::channel::oneshot::Receiver<Option<Vec<std::path::PathBuf>>> {
80+ todo!()
81+ }
82+
83+ fn prompt_for_new_path(
84+ &self,
85+ directory: &std::path::Path,
86+ ) -> futures::channel::oneshot::Receiver<Option<std::path::PathBuf>> {
87+ todo!()
88+ }
89+
90+ fn reveal_path(&self, path: &std::path::Path) {
91+ todo!()
92+ }
93+
94+ fn on_become_active(&self, callback: Box<dyn FnMut()>) {
95+ todo!()
96+ }
97+
98+ fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
99+ todo!()
100+ }
101+
102+ fn on_quit(&self, callback: Box<dyn FnMut()>) {
103+ todo!()
104+ }
105+
106+ fn on_reopen(&self, callback: Box<dyn FnMut()>) {
107+ todo!()
108+ }
109+
110+ fn on_event(&self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
111+ todo!()
112+ }
113+
114+ fn os_name(&self) -> &'static str {
115+ todo!()
116+ }
117+
118+ fn os_version(&self) -> anyhow::Result<crate::SemanticVersion> {
119+ todo!()
120+ }
121+
122+ fn app_version(&self) -> anyhow::Result<crate::SemanticVersion> {
123+ todo!()
124+ }
125+
126+ fn app_path(&self) -> anyhow::Result<std::path::PathBuf> {
127+ todo!()
128+ }
129+
130+ fn local_timezone(&self) -> time::UtcOffset {
131+ todo!()
132+ }
133+
134+ fn path_for_auxiliary_executable(&self, name: &str) -> anyhow::Result<std::path::PathBuf> {
135+ todo!()
136+ }
137+
138+ fn set_cursor_style(&self, style: crate::CursorStyle) {
139+ todo!()
140+ }
141+
142+ fn should_auto_hide_scrollbars(&self) -> bool {
143+ todo!()
144+ }
145+
146+ fn write_to_clipboard(&self, item: crate::ClipboardItem) {
147+ todo!()
148+ }
149+
150+ fn read_from_clipboard(&self) -> Option<crate::ClipboardItem> {
151+ todo!()
152+ }
153+
154+ fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> anyhow::Result<()> {
155+ todo!()
156+ }
157+
158+ fn read_credentials(&self, url: &str) -> anyhow::Result<Option<(String, Vec<u8>)>> {
159+ todo!()
160+ }
161+
162+ fn delete_credentials(&self, url: &str) -> anyhow::Result<()> {
25163 todo!()
26164 }
27165 }
crates/gpui3/src/scene.rs+16−16
@@ -1,3 +1,5 @@
1+use crate::{text::GlyphId, FontId};
2+
13 use super::{Bounds, Hsla, Pixels, Point};
24 use bytemuck::{Pod, Zeroable};
35 use plane_split::BspSplitter;
@@ -39,7 +41,7 @@ impl Scene { · 35 unmodified lines
3941 #[derive(Clone, Debug)]
4042 pub enum Primitive {
4143 Quad(Quad),
42− Glyph(Glyph),
44+ Glyph(RenderedGlyph),
4345 Underline(Underline),
4446 }
4547
@@ -58,7 +60,7 @@ impl Primitive { · 12 unmodified lines
5860 #[derive(Default)]
5961 pub struct PrimitiveBatch {
6062 pub quads: Vec<Quad>,
61− pub glyphs: Vec<Glyph>,
63+ pub glyphs: Vec<RenderedGlyph>,
6264 pub underlines: Vec<Underline>,
6365 }
6466
@@ -96,26 +98,24 @@ unsafe impl Zeroable for Quad {} · 31 unmodified lines
9698
9799 unsafe impl Pod for Quad {}
98100
99−#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
100−pub struct GlyphId(u32);
101−
102−#[derive(Clone, Debug)]
103−pub struct Glyph {
104− pub id: GlyphId,
105− pub position: Point<Pixels>,
106− pub color: Hsla,
107− pub index: usize,
108− pub is_emoji: bool,
109−}
110−
111101 impl From<Quad> for Primitive {
112102 fn from(quad: Quad) -> Self {
113103 Primitive::Quad(quad)
114104 }
115105 }
116106
117−impl From<Glyph> for Primitive {
118− fn from(glyph: Glyph) -> Self {
107+#[derive(Debug, Clone, Copy)]
108+#[repr(C)]
109+pub struct RenderedGlyph {
110+ pub font_id: FontId,
111+ pub font_size: f32,
112+ pub id: GlyphId,
113+ pub origin: Point<Pixels>,
114+ pub color: Hsla,
115+}
116+
117+impl From<RenderedGlyph> for Primitive {
118+ fn from(glyph: RenderedGlyph) -> Self {
119119 Primitive::Glyph(glyph)
120120 }
121121 }
crates/gpui3/src/text.rs+32−4
@@ -1,8 +1,7 @@
11 use crate::{black, px};
22
33 use super::{
4− point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle,
5− WindowContext,
4+ point, Bounds, FontId, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle, WindowContext,
65 };
76 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
87 use smallvec::SmallVec;
@@ -27,6 +26,35 @@ pub struct RunStyle { · 18 unmodified lines
2726 pub underline: Option<UnderlineStyle>,
2827 }
2928
29+#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
30+pub struct GlyphId(u32);
31+
32+impl From<GlyphId> for u32 {
33+ fn from(value: GlyphId) -> Self {
34+ value.0
35+ }
36+}
37+
38+impl From<u16> for GlyphId {
39+ fn from(num: u16) -> Self {
40+ GlyphId(num as u32)
41+ }
42+}
43+
44+impl From<u32> for GlyphId {
45+ fn from(num: u32) -> Self {
46+ GlyphId(num)
47+ }
48+}
49+
50+#[derive(Clone, Debug)]
51+pub struct Glyph {
52+ pub id: GlyphId,
53+ pub position: Point<Pixels>,
54+ pub index: usize,
55+ pub is_emoji: bool,
56+}
57+
3058 impl TextLayoutCache {
3159 pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
3260 Self {
@@ -726,7 +754,7 @@ mod tests { · 693 unmodified lines
726754 let cx = AppContext::test();
727755
728756 let font_cache = cx.font_cache().clone();
729− let font_system = cx.platform().font_system();
757+ let font_system = cx.platform().text_system();
730758 let family = font_cache
731759 .load_family(&["Courier"], &Default::default())
732760 .unwrap();
@@ -793,7 +821,7 @@ mod tests { · 60 unmodified lines
793821 fn test_wrap_shaped_line() {
794822 let cx = AppContext::test();
795823 let font_cache = cx.font_cache().clone();
796− let font_system = cx.platform().font_system();
824+ let font_system = cx.platform().text_system();
797825 let text_layout_cache = TextLayoutCache::new(font_system.clone());
798826
799827 let family = font_cache
crates/storybook/Cargo.toml+0−6
@@ -13,22 +13,16 @@ test-support = [] · 12 unmodified lines
1313
1414 [dependencies]
1515 anyhow.workspace = true
16−bytemuck = "1.14.0"
1716 derive_more.workspace = true
1817 gpui2 = { path = "../gpui2" }
19−itertools = "0.11.0"
2018 log.workspace = true
21−plane-split = "0.18.0"
22−raw-window-handle = "0.5.2"
2319 refineable = { path = "../refineable" }
2420 rust-embed.workspace = true
2521 serde.workspace = true
2622 settings = { path = "../settings" }
2723 simplelog = "0.9"
28−slotmap = "1.0.6"
2924 theme = { path = "../theme" }
3025 util = { path = "../util" }
31−wgpu = "0.17.0"
3226
3327 [dev-dependencies]
3428 gpui2 = { path = "../gpui2", features = ["test-support"] }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t