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
983 "collections",983 "collections",
984 "editor",984 "editor",
985 "gpui",985 "gpui",
986 "itertools 0.10.5",986 "itertools",
987 "language",987 "language",
988 "outline",988 "outline",
989 "project",989 "project",
@@ -1960,7 +1960,7 @@ dependencies = [ · 970 unmodified lines
1960 "cranelift-codegen",1960 "cranelift-codegen",
1961 "cranelift-entity",1961 "cranelift-entity",
1962 "cranelift-frontend",1962 "cranelift-frontend",
1963 "itertools 0.10.5",1963 "itertools",
1964 "log",1964 "log",
1965 "smallvec",1965 "smallvec",
1966 "wasmparser",1966 "wasmparser",
@@ -2392,7 +2392,7 @@ dependencies = [ · 425 unmodified lines
2392 "git",2392 "git",
2393 "gpui",2393 "gpui",
2394 "indoc",2394 "indoc",
2395 "itertools 0.10.5",2395 "itertools",
2396 "language",2396 "language",
2397 "lazy_static",2397 "lazy_static",
2398 "log",2398 "log",
@@ -3273,7 +3273,7 @@ dependencies = [ · 874 unmodified lines
3273 "futures 0.3.28",3273 "futures 0.3.28",
3274 "gpui_macros",3274 "gpui_macros",
3275 "image",3275 "image",
3276 "itertools 0.10.5",3276 "itertools",
3277 "lazy_static",3277 "lazy_static",
3278 "log",3278 "log",
3279 "media",3279 "media",
@@ -3365,7 +3365,7 @@ dependencies = [ · 85 unmodified lines
3365 "futures 0.3.28",3365 "futures 0.3.28",
3366 "gpui_macros",3366 "gpui_macros",
3367 "image",3367 "image",
3368 "itertools 0.10.5",3368 "itertools",
3369 "lazy_static",3369 "lazy_static",
3370 "log",3370 "log",
3371 "media",3371 "media",
@@ -3374,6 +3374,7 @@ dependencies = [ · 2 unmodified lines
3374 "ordered-float",3374 "ordered-float",
3375 "parking",3375 "parking",
3376 "parking_lot 0.11.2",3376 "parking_lot 0.11.2",
3377 "pathfinder_geometry",
3377 "plane-split",3378 "plane-split",
3378 "png",3379 "png",
3379 "postage",3380 "postage",
@@ -3971,15 +3972,6 @@ dependencies = [ · 591 unmodified lines
3971 "either",3972 "either",
3972]3973]
3973 3974
3974[[package]]
3975name = "itertools"
3976version = "0.11.0"
3977source = "registry+https://github.com/rust-lang/crates.io-index"
3978checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
3979dependencies = [
3980 "either",
3981]
3982
3983[[package]]3975[[package]]
3984name = "itoa"3976name = "itoa"
3985version = "1.0.9"3977version = "1.0.9"
@@ -5772,7 +5764,7 @@ dependencies = [ · 1786 unmodified lines
5772 "globset",5764 "globset",
5773 "gpui",5765 "gpui",
5774 "ignore",5766 "ignore",
5775 "itertools 0.10.5",5767 "itertools",
5776 "language",5768 "language",
5777 "lazy_static",5769 "lazy_static",
5778 "log",5770 "log",
@@ -5896,7 +5888,7 @@ checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" · 117 unmodified lines
5896dependencies = [5888dependencies = [
5897 "bytes 1.4.0",5889 "bytes 1.4.0",
5898 "heck 0.3.3",5890 "heck 0.3.3",
5899 "itertools 0.10.5",5891 "itertools",
5900 "lazy_static",5892 "lazy_static",
5901 "log",5893 "log",
5902 "multimap",5894 "multimap",
@@ -5915,7 +5907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 12 unmodified lines
5915checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba"5907checksum = "600d2f334aa05acb02a755e217ef1ab6dea4d51b58b7846588b747edec04efba"
5916dependencies = [5908dependencies = [
5917 "anyhow",5909 "anyhow",
5918 "itertools 0.10.5",5910 "itertools",
5919 "proc-macro2",5911 "proc-macro2",
5920 "quote",5912 "quote",
5921 "syn 1.0.109",5913 "syn 1.0.109",
@@ -5928,7 +5920,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" · 6 unmodified lines
5928checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"5920checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe"
5929dependencies = [5921dependencies = [
5930 "anyhow",5922 "anyhow",
5931 "itertools 0.10.5",5923 "itertools",
5932 "proc-macro2",5924 "proc-macro2",
5933 "quote",5925 "quote",
5934 "syn 1.0.109",5926 "syn 1.0.109",
@@ -7568,7 +7560,7 @@ version = "0.2.1" · 1633 unmodified lines
7568source = "registry+https://github.com/rust-lang/crates.io-index"7560source = "registry+https://github.com/rust-lang/crates.io-index"
7569checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"7561checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e"
7570dependencies = [7562dependencies = [
7571 "itertools 0.10.5",7563 "itertools",
7572 "nom",7564 "nom",
7573 "unicode_categories",7565 "unicode_categories",
7574]7566]
@@ -7692,22 +7684,16 @@ name = "storybook" · 117 unmodified lines
7692version = "0.1.0"7684version = "0.1.0"
7693dependencies = [7685dependencies = [
7694 "anyhow",7686 "anyhow",
7695 "bytemuck",
7696 "derive_more",7687 "derive_more",
7697 "gpui2",7688 "gpui2",
7698 "itertools 0.11.0",
7699 "log",7689 "log",
7700 "plane-split",
7701 "raw-window-handle",
7702 "refineable",7690 "refineable",
7703 "rust-embed",7691 "rust-embed",
7704 "serde",7692 "serde",
7705 "settings",7693 "settings",
7706 "simplelog",7694 "simplelog",
7707 "slotmap",
7708 "theme",7695 "theme",
7709 "util",7696 "util",
7710 "wgpu",
7711]7697]
7712 7698
7713[[package]]7699[[package]]
@@ -7977,7 +7963,7 @@ dependencies = [ · 263 unmodified lines
7977 "dirs 4.0.0",7963 "dirs 4.0.0",
7978 "futures 0.3.28",7964 "futures 0.3.28",
7979 "gpui",7965 "gpui",
7980 "itertools 0.10.5",7966 "itertools",
7981 "lazy_static",7967 "lazy_static",
7982 "libc",7968 "libc",
7983 "mio-extras",7969 "mio-extras",
@@ -8008,7 +7994,7 @@ dependencies = [ · 24 unmodified lines
8008 "editor",7994 "editor",
8009 "futures 0.3.28",7995 "futures 0.3.28",
8010 "gpui",7996 "gpui",
8011 "itertools 0.10.5",7997 "itertools",
8012 "language",7998 "language",
8013 "lazy_static",7999 "lazy_static",
8014 "libc",8000 "libc",
@@ -9190,7 +9176,7 @@ dependencies = [ · 1175 unmodified lines
9190 "futures 0.3.28",9176 "futures 0.3.28",
9191 "gpui",9177 "gpui",
9192 "indoc",9178 "indoc",
9193 "itertools 0.10.5",9179 "itertools",
9194 "language",9180 "language",
9195 "language_selector",9181 "language_selector",
9196 "log",9182 "log",
@@ -10137,7 +10123,7 @@ dependencies = [ · 940 unmodified lines
10137 "gpui",10123 "gpui",
10138 "indoc",10124 "indoc",
10139 "install_cli",10125 "install_cli",
10140 "itertools 0.10.5",10126 "itertools",
10141 "language",10127 "language",
10142 "lazy_static",10128 "lazy_static",
10143 "log",10129 "log",
crates/gpui/src/platform/mac/platform.rs+3−3
@@ -1,6 +1,6 @@
1use super::{1use super::{
2 event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,2 event::key_to_native, screen::Screen, status_item::StatusItem, BoolExt as _, Dispatcher,
3 FontSystem, MacWindow,3 MacWindow, TextSystem,
4};4};
5use crate::{5use crate::{
6 executor,6 executor,
@@ -488,7 +488,7 @@ impl platform::ForegroundPlatform for MacForegroundPlatform { · 481 unmodified lines
488 488
489pub struct MacPlatform {489pub struct MacPlatform {
490 dispatcher: Arc<Dispatcher>,490 dispatcher: Arc<Dispatcher>,
491 fonts: Arc<FontSystem>,491 fonts: Arc<TextSystem>,
492 pasteboard: id,492 pasteboard: id,
493 text_hash_pasteboard_type: id,493 text_hash_pasteboard_type: id,
494 metadata_pasteboard_type: id,494 metadata_pasteboard_type: id,
@@ -498,7 +498,7 @@ impl MacPlatform { · 3 unmodified lines
498 pub fn new() -> Self {498 pub fn new() -> Self {
499 Self {499 Self {
500 dispatcher: Arc::new(Dispatcher),500 dispatcher: Arc::new(Dispatcher),
501 fonts: Arc::new(FontSystem::new()),501 fonts: Arc::new(TextSystem::new()),
502 pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },502 pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
503 text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },503 text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
504 metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },504 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
35ordered-float.workspace = true35ordered-float.workspace = true
36parking = "2.0.0"36parking = "2.0.0"
37parking_lot.workspace = true37parking_lot.workspace = true
38pathfinder_geometry = "0.5"
38postage.workspace = true39postage.workspace = true
39rand.workspace = true40rand.workspace = true
40refineable.workspace = true41refineable.workspace = true
crates/gpui3/src/app.rs+13−8
@@ -1,14 +1,19 @@
1use crate::{
2 Context, FontCache, LayoutId, Platform, Reference, View, Window, WindowContext, WindowHandle,
3 WindowId,
4};
1use anyhow::{anyhow, Result};5use anyhow::{anyhow, Result};
2use slotmap::SlotMap;6use slotmap::SlotMap;
3use std::{any::Any, marker::PhantomData, rc::Rc, sync::Arc};7use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc};
4 8
5use crate::FontCache;9#[derive(Clone)]
10pub struct App(Rc<RefCell<AppContext>>);
6 11
7use super::{12impl App {
8 platform::Platform,13 pub fn new(platform: Rc<dyn Platform>) -> Self {
9 window::{Window, WindowHandle, WindowId},14 Self(Rc::new(RefCell::new(AppContext::new(platform))))
10 Context, LayoutId, Reference, View, WindowContext,15 }
11};16}
12 17
13pub struct AppContext {18pub struct AppContext {
14 platform: Rc<dyn Platform>,19 platform: Rc<dyn Platform>,
@@ -21,7 +26,7 @@ pub struct AppContext { · 6 unmodified lines
21 26
22impl AppContext {27impl AppContext {
23 pub fn new(platform: Rc<dyn Platform>) -> Self {28 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()));
25 AppContext {30 AppContext {
26 platform,31 platform,
27 font_cache,32 font_cache,
crates/gpui3/src/executor.rs+1−1
@@ -619,7 +619,7 @@ impl ExecutorEvent { · 618 unmodified lines
619}619}
620 620
621impl ForegroundExecutor {621impl ForegroundExecutor {
622 pub fn platform(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {622 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Result<Self> {
623 if dispatcher.is_main_thread() {623 if dispatcher.is_main_thread() {
624 Ok(Self::Platform {624 Ok(Self::Platform {
625 dispatcher,625 dispatcher,
crates/gpui3/src/fonts.rs+3−3
@@ -245,7 +245,7 @@ impl FontCache { · 244 unmodified lines
245 .typographic_bounds(font_id, glyph_id)245 .typographic_bounds(font_id, glyph_id)
246 .unwrap();246 .unwrap();
247 }247 }
248 bounds.size.width * self.em_size(font_id, font_size)248 self.em_size(font_id, font_size) * bounds.size.width
249 }249 }
250 250
251 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {251 pub fn em_advance(&self, font_id: FontId, font_size: Pixels) -> Pixels {
@@ -256,7 +256,7 @@ impl FontCache { · 4 unmodified lines
256 glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();256 glyph_id = state.font_system.glyph_for_char(font_id, 'm').unwrap();
257 advance = state.font_system.advance(font_id, glyph_id).unwrap();257 advance = state.font_system.advance(font_id, glyph_id).unwrap();
258 }258 }
259 advance.x * self.em_size(font_id, font_size)259 self.em_size(font_id, font_size) * advance.width
260 }260 }
261 261
262 pub fn line_height(&self, font_size: Pixels) -> Pixels {262 pub fn line_height(&self, font_size: Pixels) -> Pixels {
@@ -356,7 +356,7 @@ mod tests { · 93 unmodified lines
356 #[test]356 #[test]
357 fn test_select_font() {357 fn test_select_font() {
358 let platform = TestPlatform::new();358 let platform = TestPlatform::new();
359 let fonts = FontCache::new(platform.font_system());359 let fonts = FontCache::new(platform.text_system());
360 let arial = fonts360 let arial = fonts
361 .load_family(361 .load_family(
362 &["Arial"],362 &["Arial"],
crates/gpui3/src/geometry.rs+6−0
@@ -246,6 +246,12 @@ impl std::hash::Hash for Pixels { · 245 unmodified lines
246 }246 }
247}247}
248 248
249impl From<f64> for Pixels {
250 fn from(val: f64) -> Self {
251 Pixels(val as f32)
252 }
253}
254
249unsafe impl bytemuck::Pod for Pixels {}255unsafe impl bytemuck::Pod for Pixels {}
250unsafe impl bytemuck::Zeroable for Pixels {}256unsafe impl bytemuck::Zeroable for Pixels {}
251 257
crates/gpui3/src/gpui3.rs+1−0
@@ -34,6 +34,7 @@ pub use styled::*; · 33 unmodified lines
34pub use taffy::LayoutId;34pub use taffy::LayoutId;
35use taffy::TaffyLayoutEngine;35use taffy::TaffyLayoutEngine;
36use text::*;36use text::*;
37pub use text::{Glyph, GlyphId};
37pub use util::arc_cow::ArcCow;38pub use util::arc_cow::ArcCow;
38pub use window::*;39pub use window::*;
39 40
crates/gpui3/src/platform.rs+176−11
@@ -6,13 +6,26 @@ mod mac; · 5 unmodified lines
6mod test;6mod test;
7 7
8use crate::{8use crate::{
9 AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, GlyphId,9 AnyWindowHandle, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight,
10 LineLayout, Pixels, Point, RunStyle, SharedString, Size,10 ForegroundExecutor, GlyphId, LineLayout, Pixels, Point, Result, RunStyle, SharedString, Size,
11};11};
12use anyhow::anyhow;
12use async_task::Runnable;13use async_task::Runnable;
13use futures::channel::oneshot;14use futures::channel::oneshot;
14use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};15use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
15use std::{any::Any, fmt::Debug, ops::Range, rc::Rc, sync::Arc};16use seahash::SeaHasher;
17use serde::{Deserialize, Serialize};
18use std::hash::{Hash, Hasher};
19use 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};
28pub use time::UtcOffset;
16use uuid::Uuid;29use uuid::Uuid;
17 30
18pub use events::*;31pub use events::*;
@@ -22,15 +35,64 @@ pub use mac::*; · 3 unmodified lines
22#[cfg(any(test, feature = "test"))]35#[cfg(any(test, feature = "test"))]
23pub use test::*;36pub use test::*;
24 37
38// #[cfg(target_os = "macos")]
39// pub fn current() -> Rc<dyn Platform> {
40// MacPlatform
41// }
42
25pub trait Platform {43pub trait Platform {
26 fn dispatcher(&self) -> Arc<dyn PlatformDispatcher>;44 fn executor(&self) -> Rc<ForegroundExecutor>;
27 fn font_system(&self) -> Arc<dyn PlatformTextSystem>;45 fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
28 46
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>;
29 fn open_window(58 fn open_window(
30 &self,59 &self,
31 handle: AnyWindowHandle,60 handle: AnyWindowHandle,
32 options: WindowOptions,61 options: WindowOptions,
33 ) -> Box<dyn PlatformWindow>;62 ) -> 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<()>;
34}96}
35 97
36pub trait PlatformScreen: Debug {98pub trait PlatformScreen: Debug {
@@ -90,12 +152,9 @@ pub trait PlatformTextSystem: Send + Sync { · 53 unmodified lines
90 style: FontStyle,152 style: FontStyle,
91 ) -> anyhow::Result<FontId>;153 ) -> anyhow::Result<FontId>;
92 fn font_metrics(&self, font_id: FontId) -> FontMetrics;154 fn font_metrics(&self, font_id: FontId) -> FontMetrics;
93 fn typographic_bounds(155 fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId)
94 &self,156 -> anyhow::Result<Bounds<f32>>;
95 font_id: FontId,157 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Size<f32>>;
96 glyph_id: GlyphId,
97 ) -> anyhow::Result<Bounds<Pixels>>;
98 fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result<Point<Pixels>>;
99 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;158 fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
100 fn rasterize_glyph(159 fn rasterize_glyph(
101 &self,160 &self,
@@ -224,3 +283,109 @@ pub enum WindowPromptLevel { · 122 unmodified lines
224 Warning,283 Warning,
225 Critical,284 Critical,
226}285}
286
287#[derive(Copy, Clone, Debug)]
288pub struct PathPromptOptions {
289 pub files: bool,
290 pub directories: bool,
291 pub multiple: bool,
292}
293
294#[derive(Copy, Clone, Debug)]
295pub enum PromptLevel {
296 Info,
297 Warning,
298 Critical,
299}
300
301#[derive(Copy, Clone, Debug)]
302pub enum CursorStyle {
303 Arrow,
304 ResizeLeftRight,
305 ResizeUpDown,
306 PointingHand,
307 IBeam,
308}
309
310impl Default for CursorStyle {
311 fn default() -> Self {
312 Self::Arrow
313 }
314}
315
316#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
317pub struct SemanticVersion {
318 major: usize,
319 minor: usize,
320 patch: usize,
321}
322
323impl 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
348impl 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)]
355pub struct ClipboardItem {
356 pub(crate) text: String,
357 pub(crate) metadata: Option<String>,
358}
359
360impl 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
2///! an origin at the bottom left of the main display.2///! an origin at the bottom left of the main display.
3mod dispatcher;3mod dispatcher;
4mod events;4mod events;
5mod open_type;
5mod platform;6mod platform;
6mod screen;7mod screen;
8mod text_system;
7mod window;9mod window;
8mod window_appearence;10mod window_appearence;
9 11
10use std::{
11 ffi::{c_char, CStr, OsStr},
12 ops::Range,
13 os::unix::prelude::OsStrExt,
14 path::PathBuf,
15};
16
17use crate::{px, size, Pixels, Size};12use crate::{px, size, Pixels, Size};
18use anyhow::anyhow;13use anyhow::anyhow;
19use cocoa::{14use cocoa::{
@@ -25,8 +20,17 @@ use objc::{ · 5 unmodified lines
25 runtime::{BOOL, NO, YES},20 runtime::{BOOL, NO, YES},
26 sel, sel_impl,21 sel, sel_impl,
27};22};
23use std::{
24 ffi::{c_char, CStr, OsStr},
25 ops::Range,
26 os::unix::prelude::OsStrExt,
27 path::PathBuf,
28};
29
30pub use dispatcher::*;
28pub use platform::*;31pub use platform::*;
29pub use screen::*;32pub use screen::*;
33pub use text_system::*;
30pub use window::*;34pub use window::*;
31 35
32trait BoolExt {36trait BoolExt {
crates/gpui3/src/platform/mac/open_type.rs+395−0
@@ -0,0 +1,395 @@
1#![allow(unused, non_upper_case_globals)]
2
3use std::ptr;
4
5use crate::FontFeatures;
6use cocoa::appkit::CGFloat;
7use core_foundation::{base::TCFType, number::CFNumber};
8use core_graphics::geometry::CGAffineTransform;
9use core_text::{
10 font::{CTFont, CTFontRef},
11 font_descriptor::{
12 CTFontDescriptor, CTFontDescriptorCreateCopyWithFeature, CTFontDescriptorRef,
13 },
14};
15use font_kit::font::Font;
16
17const kCaseSensitiveLayoutOffSelector: i32 = 1;
18const kCaseSensitiveLayoutOnSelector: i32 = 0;
19const kCaseSensitiveLayoutType: i32 = 33;
20const kCaseSensitiveSpacingOffSelector: i32 = 3;
21const kCaseSensitiveSpacingOnSelector: i32 = 2;
22const kCharacterAlternativesType: i32 = 17;
23const kCommonLigaturesOffSelector: i32 = 3;
24const kCommonLigaturesOnSelector: i32 = 2;
25const kContextualAlternatesOffSelector: i32 = 1;
26const kContextualAlternatesOnSelector: i32 = 0;
27const kContextualAlternatesType: i32 = 36;
28const kContextualLigaturesOffSelector: i32 = 19;
29const kContextualLigaturesOnSelector: i32 = 18;
30const kContextualSwashAlternatesOffSelector: i32 = 5;
31const kContextualSwashAlternatesOnSelector: i32 = 4;
32const kDefaultLowerCaseSelector: i32 = 0;
33const kDefaultUpperCaseSelector: i32 = 0;
34const kDiagonalFractionsSelector: i32 = 2;
35const kFractionsType: i32 = 11;
36const kHistoricalLigaturesOffSelector: i32 = 21;
37const kHistoricalLigaturesOnSelector: i32 = 20;
38const kHojoCharactersSelector: i32 = 12;
39const kInferiorsSelector: i32 = 2;
40const kJIS2004CharactersSelector: i32 = 11;
41const kLigaturesType: i32 = 1;
42const kLowerCasePetiteCapsSelector: i32 = 2;
43const kLowerCaseSmallCapsSelector: i32 = 1;
44const kLowerCaseType: i32 = 37;
45const kLowerCaseNumbersSelector: i32 = 0;
46const kMathematicalGreekOffSelector: i32 = 11;
47const kMathematicalGreekOnSelector: i32 = 10;
48const kMonospacedNumbersSelector: i32 = 0;
49const kNLCCharactersSelector: i32 = 13;
50const kNoFractionsSelector: i32 = 0;
51const kNormalPositionSelector: i32 = 0;
52const kNoStyleOptionsSelector: i32 = 0;
53const kNumberCaseType: i32 = 21;
54const kNumberSpacingType: i32 = 6;
55const kOrdinalsSelector: i32 = 3;
56const kProportionalNumbersSelector: i32 = 1;
57const kQuarterWidthTextSelector: i32 = 4;
58const kScientificInferiorsSelector: i32 = 4;
59const kSlashedZeroOffSelector: i32 = 5;
60const kSlashedZeroOnSelector: i32 = 4;
61const kStyleOptionsType: i32 = 19;
62const kStylisticAltEighteenOffSelector: i32 = 37;
63const kStylisticAltEighteenOnSelector: i32 = 36;
64const kStylisticAltEightOffSelector: i32 = 17;
65const kStylisticAltEightOnSelector: i32 = 16;
66const kStylisticAltElevenOffSelector: i32 = 23;
67const kStylisticAltElevenOnSelector: i32 = 22;
68const kStylisticAlternativesType: i32 = 35;
69const kStylisticAltFifteenOffSelector: i32 = 31;
70const kStylisticAltFifteenOnSelector: i32 = 30;
71const kStylisticAltFiveOffSelector: i32 = 11;
72const kStylisticAltFiveOnSelector: i32 = 10;
73const kStylisticAltFourOffSelector: i32 = 9;
74const kStylisticAltFourOnSelector: i32 = 8;
75const kStylisticAltFourteenOffSelector: i32 = 29;
76const kStylisticAltFourteenOnSelector: i32 = 28;
77const kStylisticAltNineOffSelector: i32 = 19;
78const kStylisticAltNineOnSelector: i32 = 18;
79const kStylisticAltNineteenOffSelector: i32 = 39;
80const kStylisticAltNineteenOnSelector: i32 = 38;
81const kStylisticAltOneOffSelector: i32 = 3;
82const kStylisticAltOneOnSelector: i32 = 2;
83const kStylisticAltSevenOffSelector: i32 = 15;
84const kStylisticAltSevenOnSelector: i32 = 14;
85const kStylisticAltSeventeenOffSelector: i32 = 35;
86const kStylisticAltSeventeenOnSelector: i32 = 34;
87const kStylisticAltSixOffSelector: i32 = 13;
88const kStylisticAltSixOnSelector: i32 = 12;
89const kStylisticAltSixteenOffSelector: i32 = 33;
90const kStylisticAltSixteenOnSelector: i32 = 32;
91const kStylisticAltTenOffSelector: i32 = 21;
92const kStylisticAltTenOnSelector: i32 = 20;
93const kStylisticAltThirteenOffSelector: i32 = 27;
94const kStylisticAltThirteenOnSelector: i32 = 26;
95const kStylisticAltThreeOffSelector: i32 = 7;
96const kStylisticAltThreeOnSelector: i32 = 6;
97const kStylisticAltTwelveOffSelector: i32 = 25;
98const kStylisticAltTwelveOnSelector: i32 = 24;
99const kStylisticAltTwentyOffSelector: i32 = 41;
100const kStylisticAltTwentyOnSelector: i32 = 40;
101const kStylisticAltTwoOffSelector: i32 = 5;
102const kStylisticAltTwoOnSelector: i32 = 4;
103const kSuperiorsSelector: i32 = 1;
104const kSwashAlternatesOffSelector: i32 = 3;
105const kSwashAlternatesOnSelector: i32 = 2;
106const kTitlingCapsSelector: i32 = 4;
107const kTypographicExtrasType: i32 = 14;
108const kVerticalFractionsSelector: i32 = 1;
109const kVerticalPositionType: i32 = 10;
110
111pub 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
354fn 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")]
388extern "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};1use super::BoolExt;
2// use block::ConcreteBlock;2use crate::{
3// use cocoa::{3 AnyWindowHandle, ClipboardItem, CursorStyle, Event, ForegroundExecutor, MacDispatcher,
4// appkit::{4 MacScreen, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen,
5// NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,5 PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
6// NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,6};
7// NSSavePanel, NSWindow,7use anyhow::anyhow;
8// },8use block::ConcreteBlock;
9// base::{id, nil, selector, BOOL, YES},9use cocoa::{
10// foundation::{NSArray, NSAutoreleasePool, NSData, NSInteger, NSString, NSUInteger, NSURL},10 appkit::{
11// };11 NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
12// use core_foundation::{12 NSModalResponse, NSOpenPanel, NSPasteboard, NSPasteboardTypeString, NSSavePanel, NSWindow,
13// base::{CFTypeRef, OSStatus},13 },
14// dictionary::CFDictionaryRef,14 base::{id, nil, BOOL, YES},
15// string::CFStringRef,15 foundation::{
16// };16 NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSString,
17// use ctor::ctor;17 NSUInteger, NSURL,
18// use objc::{18 },
19// class,19};
20// declare::ClassDecl,20use core_foundation::{
21// msg_send,21 base::{CFType, CFTypeRef, OSStatus, TCFType as _},
22// runtime::{Class, Object, Sel},22 boolean::CFBoolean,
23// sel, sel_impl,23 data::CFData,
24// };24 dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},
25 25 string::{CFString, CFStringRef},
26// use postage::oneshot;26};
27// use ptr::null_mut;27use ctor::ctor;
28// use std::{28use futures::channel::oneshot;
29// cell::{Cell, RefCell},29use objc::{
30// convert::TryInto,30 class,
31// ffi::{c_void, CStr, OsStr},31 declare::ClassDecl,
32// os::{raw::c_char, unix::ffi::OsStrExt},32 msg_send,
33// path::{Path, PathBuf},33 runtime::{Class, Object, Sel},
34// process::Command,34 sel, sel_impl,
35// ptr,35};
36// rc::Rc,36use ptr::null_mut;
37// slice, str,37use std::{
38// sync::Arc,38 cell::{Cell, RefCell},
39// };39 convert::TryInto,
40 40 ffi::{c_void, CStr, OsStr},
41// use crate::Event;41 os::{raw::c_char, unix::ffi::OsStrExt},
42 42 path::{Path, PathBuf},
43// #[allow(non_upper_case_globals)]43 process::Command,
44// const NSUTF8StringEncoding: NSUInteger = 4;44 ptr,
45 45 rc::Rc,
46// const MAC_PLATFORM_IVAR: &str = "platform";46 slice, str,
47// static mut APP_CLASS: *const Class = ptr::null();47 sync::Arc,
48// static mut APP_DELEGATE_CLASS: *const Class = ptr::null();48};
49 49use time::UtcOffset;
50// #[ctor]50
51// unsafe fn build_classes() {51#[allow(non_upper_case_globals)]
52// APP_CLASS = {52const NSUTF8StringEncoding: NSUInteger = 4;
53// let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();53
54// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);54#[allow(non_upper_case_globals)]
55// decl.add_method(55pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
56// sel!(sendEvent:),56
57// send_event as extern "C" fn(&mut Object, Sel, id),57const MAC_PLATFORM_IVAR: &str = "platform";
58// );58static mut APP_CLASS: *const Class = ptr::null();
59// decl.register()59static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
60// };60
61 61#[ctor]
62// APP_DELEGATE_CLASS = {62unsafe fn build_classes() {
63// let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();63 APP_CLASS = {
64// decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);64 let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
65// decl.add_method(65 decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
66// sel!(applicationDidFinishLaunching:),66 decl.add_method(
67// did_finish_launching as extern "C" fn(&mut Object, Sel, id),67 sel!(sendEvent:),
68// );68 send_event as extern "C" fn(&mut Object, Sel, id),
69// decl.add_method(69 );
70// sel!(applicationShouldHandleReopen:hasVisibleWindows:),70 decl.register()
71// should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),71 };
72// );72
73// decl.add_method(73 APP_DELEGATE_CLASS = {
74// sel!(applicationDidBecomeActive:),74 let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
75// did_become_active as extern "C" fn(&mut Object, Sel, id),75 decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
76// );76 decl.add_method(
77// decl.add_method(77 sel!(applicationDidFinishLaunching:),
78// sel!(applicationDidResignActive:),78 did_finish_launching as extern "C" fn(&mut Object, Sel, id),
79// did_resign_active as extern "C" fn(&mut Object, Sel, id),79 );
80// );80 decl.add_method(
81// decl.add_method(81 sel!(applicationShouldHandleReopen:hasVisibleWindows:),
82// sel!(applicationWillTerminate:),82 should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),
83// will_terminate as extern "C" fn(&mut Object, Sel, id),83 );
84// );84 decl.add_method(
85// decl.add_method(85 sel!(applicationDidBecomeActive:),
86// sel!(handleGPUIMenuItem:),86 did_become_active as extern "C" fn(&mut Object, Sel, id),
87// handle_menu_item as extern "C" fn(&mut Object, Sel, id),87 );
88// );88 decl.add_method(
89// // Add menu item handlers so that OS save panels have the correct key commands89 sel!(applicationDidResignActive:),
90// decl.add_method(90 did_resign_active as extern "C" fn(&mut Object, Sel, id),
91// sel!(cut:),91 );
92// handle_menu_item as extern "C" fn(&mut Object, Sel, id),92 decl.add_method(
93// );93 sel!(applicationWillTerminate:),
94// decl.add_method(94 will_terminate as extern "C" fn(&mut Object, Sel, id),
95// sel!(copy:),95 );
96// handle_menu_item as extern "C" fn(&mut Object, Sel, id),96 decl.add_method(
97// );97 sel!(handleGPUIMenuItem:),
98// decl.add_method(98 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
99// sel!(paste:),99 );
100// handle_menu_item as extern "C" fn(&mut Object, Sel, id),100 // Add menu item handlers so that OS save panels have the correct key commands
101// );101 decl.add_method(
102// decl.add_method(102 sel!(cut:),
103// sel!(selectAll:),103 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
104// handle_menu_item as extern "C" fn(&mut Object, Sel, id),104 );
105// );105 decl.add_method(
106// decl.add_method(106 sel!(copy:),
107// sel!(undo:),107 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
108// handle_menu_item as extern "C" fn(&mut Object, Sel, id),108 );
109// );109 decl.add_method(
110// decl.add_method(110 sel!(paste:),
111// sel!(redo:),111 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
112// handle_menu_item as extern "C" fn(&mut Object, Sel, id),112 );
113// );113 decl.add_method(
114// decl.add_method(114 sel!(selectAll:),
115// sel!(validateMenuItem:),115 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
116// validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,116 );
117// );117 decl.add_method(
118// decl.add_method(118 sel!(undo:),
119// sel!(menuWillOpen:),119 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
120// menu_will_open as extern "C" fn(&mut Object, Sel, id),120 );
121// );121 decl.add_method(
122// decl.add_method(122 sel!(redo:),
123// sel!(application:openURLs:),123 handle_menu_item as extern "C" fn(&mut Object, Sel, id),
124// open_urls as extern "C" fn(&mut Object, Sel, id, id),124 );
125// );125 decl.add_method(
126// decl.register()126 sel!(validateMenuItem:),
127// }127 validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,
128// }128 );
129 129 decl.add_method(
130// pub struct MacForegroundPlatformState {130 sel!(menuWillOpen:),
131// dispatcher: Arc<MacDispatcher>,131 menu_will_open as extern "C" fn(&mut Object, Sel, id),
132// fonts: Arc<MacFontSystem>,132 );
133// pasteboard: id,133 decl.add_method(
134// text_hash_pasteboard_type: id,134 sel!(application:openURLs:),
135// metadata_pasteboard_type: id,135 open_urls as extern "C" fn(&mut Object, Sel, id, id),
136// become_active: Option<Box<dyn FnMut()>>,136 );
137// resign_active: Option<Box<dyn FnMut()>>,137 decl.register()
138// reopen: Option<Box<dyn FnMut()>>,138 }
139// quit: Option<Box<dyn FnMut()>>,139}
140// event: Option<Box<dyn FnMut(Event) -> bool>>,140
141// // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,141pub struct MacPlatform(RefCell<MacPlatformState>);
142// validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,142
143// will_open_menu: Option<Box<dyn FnMut()>>,143pub struct MacPlatformState {
144// open_urls: Option<Box<dyn FnMut(Vec<String>)>>,144 executor: Rc<ForegroundExecutor>,
145// finish_launching: Option<Box<dyn FnOnce()>>,145 text_system: Arc<MacTextSystem>,
146// // menu_actions: Vec<Box<dyn Action>>,146 pasteboard: id,
147// // foreground: Rc<executor::Foreground>,147 text_hash_pasteboard_type: id,
148// }148 metadata_pasteboard_type: id,
149 149 become_active: Option<Box<dyn FnMut()>>,
150// impl MacForegroundPlatform {150 resign_active: Option<Box<dyn FnMut()>>,
151// pub fn new(foreground: Rc<executor::Foreground>) -> Self {151 reopen: Option<Box<dyn FnMut()>>,
152// Self(RefCell::new(MacForegroundPlatformState {152 quit: Option<Box<dyn FnMut()>>,
153// become_active: None,153 event: Option<Box<dyn FnMut(Event) -> bool>>,
154// resign_active: None,154 // menu_command: Option<Box<dyn FnMut(&dyn Action)>>,
155// reopen: None,155 // validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
156// quit: None,156 will_open_menu: Option<Box<dyn FnMut()>>,
157// event: None,157 open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
158// // menu_command: None,158 finish_launching: Option<Box<dyn FnOnce()>>,
159// validate_menu_command: None,159 // menu_actions: Vec<Box<dyn Action>>,
160// will_open_menu: None,160}
161// open_urls: None,161
162// finish_launching: None,162impl MacPlatform {
163// menu_actions: Default::default(),163 pub fn new() -> Self {
164// foreground,164 Self(RefCell::new(MacPlatformState {
165// }))165 executor: Rc::new(ForegroundExecutor::new(Arc::new(MacDispatcher)).unwrap()),
166// }166 text_system: Arc::new(MacTextSystem::new()),
167 167 pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },
168// unsafe fn create_menu_bar(168 text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },
169// &self,169 metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },
170// menus: Vec<Menu>,170 become_active: None,
171// delegate: id,171 resign_active: None,
172// actions: &mut Vec<Box<dyn Action>>,172 reopen: None,
173// keystroke_matcher: &KeymapMatcher,173 quit: None,
174// ) -> id {174 event: None,
175// let application_menu = NSMenu::new(nil).autorelease();175 will_open_menu: None,
176// application_menu.setDelegate_(delegate);176 open_urls: None,
177 177 finish_launching: None,
178// for menu_config in menus {178 // menu_command: None,
179// let menu = NSMenu::new(nil).autorelease();179 // validate_menu_command: None,
180// menu.setTitle_(ns_string(menu_config.name));180 // menu_actions: Default::default(),
181// menu.setDelegate_(delegate);181 }))
182 182 }
183// for item_config in menu_config.items {183
184// menu.addItem_(self.create_menu_item(184 unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {
185// item_config,185 let pasteboard = self.0.borrow().pasteboard;
186// delegate,186 let data = pasteboard.dataForType(kind);
187// actions,187 if data == nil {
188// keystroke_matcher,188 None
189// ));189 } else {
190// }190 Some(slice::from_raw_parts(
191 191 data.bytes() as *mut u8,
192// let menu_item = NSMenuItem::new(nil).autorelease();192 data.length() as usize,
193// menu_item.setSubmenu_(menu);193 ))
194// application_menu.addItem_(menu_item);194 }
195 195 }
196// if menu_config.name == "Window" {196
197// let app: id = msg_send![APP_CLASS, sharedApplication];197 // unsafe fn create_menu_bar(
198// app.setWindowsMenu_(menu);198 // &self,
199// }199 // menus: Vec<Menu>,
200// }200 // delegate: id,
201 201 // actions: &mut Vec<Box<dyn Action>>,
202// application_menu202 // keystroke_matcher: &KeymapMatcher,
203// }203 // ) -> id {
204 204 // let application_menu = NSMenu::new(nil).autorelease();
205// // unsafe fn create_menu_item(205 // application_menu.setDelegate_(delegate);
206// // &self,206
207// // item: MenuItem,207 // for menu_config in menus {
208// // delegate: id,208 // let menu = NSMenu::new(nil).autorelease();
209// // actions: &mut Vec<Box<dyn Action>>,209 // menu.setTitle_(ns_string(menu_config.name));
210// // keystroke_matcher: &KeymapMatcher,210 // menu.setDelegate_(delegate);
211// // ) -> id {211
212// // match item {212 // for item_config in menu_config.items {
213// // MenuItem::Separator => NSMenuItem::separatorItem(nil),213 // menu.addItem_(self.create_menu_item(
214// // MenuItem::Action {214 // item_config,
215// // name,215 // delegate,
216// // action,216 // actions,
217// // os_action,217 // keystroke_matcher,
218// // } => {218 // ));
219// // // TODO219 // }
220// // let keystrokes = keystroke_matcher220
221// // .bindings_for_action(action.id())221 // let menu_item = NSMenuItem::new(nil).autorelease();
222// // .find(|binding| binding.action().eq(action.as_ref()))222 // menu_item.setSubmenu_(menu);
223// // .map(|binding| binding.keystrokes());223 // application_menu.addItem_(menu_item);
224// // let selector = match os_action {224
225// // Some(crate::OsAction::Cut) => selector("cut:"),225 // if menu_config.name == "Window" {
226// // Some(crate::OsAction::Copy) => selector("copy:"),226 // let app: id = msg_send![APP_CLASS, sharedApplication];
227// // Some(crate::OsAction::Paste) => selector("paste:"),227 // app.setWindowsMenu_(menu);
228// // Some(crate::OsAction::SelectAll) => selector("selectAll:"),228 // }
229// // Some(crate::OsAction::Undo) => selector("undo:"),229 // }
230// // Some(crate::OsAction::Redo) => selector("redo:"),230
231// // None => selector("handleGPUIMenuItem:"),231 // application_menu
232// // };232 // }
233 233
234// // let item;234 // unsafe fn create_menu_item(
235// // if let Some(keystrokes) = keystrokes {235 // &self,
236// // if keystrokes.len() == 1 {236 // item: MenuItem,
237// // let keystroke = &keystrokes[0];237 // delegate: id,
238// // let mut mask = NSEventModifierFlags::empty();238 // actions: &mut Vec<Box<dyn Action>>,
239// // for (modifier, flag) in &[239 // keystroke_matcher: &KeymapMatcher,
240// // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),240 // ) -> id {
241// // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),241 // match item {
242// // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),242 // MenuItem::Separator => NSMenuItem::separatorItem(nil),
243// // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),243 // MenuItem::Action {
244// // ] {244 // name,
245// // if *modifier {245 // action,
246// // mask |= *flag;246 // os_action,
247// // }247 // } => {
248// // }248 // // TODO
249 249 // let keystrokes = keystroke_matcher
250// // item = NSMenuItem::alloc(nil)250 // .bindings_for_action(action.id())
251// // .initWithTitle_action_keyEquivalent_(251 // .find(|binding| binding.action().eq(action.as_ref()))
252// // ns_string(name),252 // .map(|binding| binding.keystrokes());
253// // selector,253 // let selector = match os_action {
254// // ns_string(key_to_native(&keystroke.key).as_ref()),254 // Some(crate::OsAction::Cut) => selector("cut:"),
255// // )255 // Some(crate::OsAction::Copy) => selector("copy:"),
256// // .autorelease();256 // Some(crate::OsAction::Paste) => selector("paste:"),
257// // item.setKeyEquivalentModifierMask_(mask);257 // Some(crate::OsAction::SelectAll) => selector("selectAll:"),
258// // }258 // Some(crate::OsAction::Undo) => selector("undo:"),
259// // // For multi-keystroke bindings, render the keystroke as part of the title.259 // Some(crate::OsAction::Redo) => selector("redo:"),
260// // else {260 // None => selector("handleGPUIMenuItem:"),
261// // use std::fmt::Write;261 // };
262 262
263// // let mut name = format!("{name} [");263 // let item;
264// // for (i, keystroke) in keystrokes.iter().enumerate() {264 // if let Some(keystrokes) = keystrokes {
265// // if i > 0 {265 // if keystrokes.len() == 1 {
266// // name.push(' ');266 // let keystroke = &keystrokes[0];
267// // }267 // let mut mask = NSEventModifierFlags::empty();
268// // write!(&mut name, "{}", keystroke).unwrap();268 // for (modifier, flag) in &[
269// // }269 // (keystroke.cmd, NSEventModifierFlags::NSCommandKeyMask),
270// // name.push(']');270 // (keystroke.ctrl, NSEventModifierFlags::NSControlKeyMask),
271 271 // (keystroke.alt, NSEventModifierFlags::NSAlternateKeyMask),
272// // item = NSMenuItem::alloc(nil)272 // (keystroke.shift, NSEventModifierFlags::NSShiftKeyMask),
273// // .initWithTitle_action_keyEquivalent_(273 // ] {
274// // ns_string(&name),274 // if *modifier {
275// // selector,275 // mask |= *flag;
276// // ns_string(""),276 // }
277// // )277 // }
278// // .autorelease();278
279// // }279 // item = NSMenuItem::alloc(nil)
280// // } else {280 // .initWithTitle_action_keyEquivalent_(
281// // item = NSMenuItem::alloc(nil)281 // ns_string(name),
282// // .initWithTitle_action_keyEquivalent_(282 // selector,
283// // ns_string(name),283 // ns_string(key_to_native(&keystroke.key).as_ref()),
284// // selector,284 // )
285// // ns_string(""),285 // .autorelease();
286// // )286 // item.setKeyEquivalentModifierMask_(mask);
287// // .autorelease();287 // }
288// // }288 // // For multi-keystroke bindings, render the keystroke as part of the title.
289 289 // else {
290// // let tag = actions.len() as NSInteger;290 // use std::fmt::Write;
291// // let _: () = msg_send![item, setTag: tag];291
292// // actions.push(action);292 // let mut name = format!("{name} [");
293// // item293 // for (i, keystroke) in keystrokes.iter().enumerate() {
294// // }294 // if i > 0 {
295// // MenuItem::Submenu(Menu { name, items }) => {295 // name.push(' ');
296// // let item = NSMenuItem::new(nil).autorelease();296 // }
297// // let submenu = NSMenu::new(nil).autorelease();297 // write!(&mut name, "{}", keystroke).unwrap();
298// // submenu.setDelegate_(delegate);298 // }
299// // for item in items {299 // name.push(']');
300// // submenu.addItem_(self.create_menu_item(300
301// // item,301 // item = NSMenuItem::alloc(nil)
302// // delegate,302 // .initWithTitle_action_keyEquivalent_(
303// // actions,303 // ns_string(&name),
304// // keystroke_matcher,304 // selector,
305// // ));305 // ns_string(""),
306// // }306 // )
307// // item.setSubmenu_(submenu);307 // .autorelease();
308// // item.setTitle_(ns_string(name));308 // }
309// // item309 // } else {
310// // }310 // item = NSMenuItem::alloc(nil)
311// // }311 // .initWithTitle_action_keyEquivalent_(
312// // }312 // ns_string(name),
313 313 // selector,
314// unsafe fn read_from_pasteboard(&self, kind: id) -> Option<&[u8]> {314 // ns_string(""),
315// let data = self.pasteboard.dataForType(kind);315 // )
316// if data == nil {316 // .autorelease();
317// None317 // }
318// } else {318
319// Some(slice::from_raw_parts(319 // let tag = actions.len() as NSInteger;
320// data.bytes() as *mut u8,320 // let _: () = msg_send![item, setTag: tag];
321// data.length() as usize,321 // actions.push(action);
322// ))322 // item
323// }323 // }
324// }324 // MenuItem::Submenu(Menu { name, items }) => {
325// }325 // let item = NSMenuItem::new(nil).autorelease();
326 326 // let submenu = NSMenu::new(nil).autorelease();
327// // impl platform::ForegroundPlatform for MacForegroundPlatform {327 // submenu.setDelegate_(delegate);
328// // fn on_become_active(&self, callback: Box<dyn FnMut()>) {328 // for item in items {
329// // self.0.borrow_mut().become_active = Some(callback);329 // submenu.addItem_(self.create_menu_item(
330// // }330 // item,
331 331 // delegate,
332// // fn on_resign_active(&self, callback: Box<dyn FnMut()>) {332 // actions,
333// // self.0.borrow_mut().resign_active = Some(callback);333 // keystroke_matcher,
334// // }334 // ));
335 335 // }
336// // fn on_quit(&self, callback: Box<dyn FnMut()>) {336 // item.setSubmenu_(submenu);
337// // self.0.borrow_mut().quit = Some(callback);337 // item.setTitle_(ns_string(name));
338// // }338 // item
339 339 // }
340// // fn on_reopen(&self, callback: Box<dyn FnMut()>) {340 // }
341// // self.0.borrow_mut().reopen = Some(callback);341 // }
342// // }342}
343 343
344// // fn on_event(&self, callback: Box<dyn FnMut(platform::Event) -> bool>) {344impl Platform for MacPlatform {
345// // self.0.borrow_mut().event = Some(callback);345 fn executor(&self) -> Rc<ForegroundExecutor> {
346// // }346 self.0.borrow().executor.clone()
347 347 }
348// // fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {348
349// // self.0.borrow_mut().open_urls = Some(callback);349 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
350// // }350 self.0.borrow().text_system.clone()
351 351 }
352// // fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {352
353// // self.0.borrow_mut().finish_launching = Some(on_finish_launching);353 fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
354 354 self.0.borrow_mut().finish_launching = Some(on_finish_launching);
355// // unsafe {355
356// // let app: id = msg_send![APP_CLASS, sharedApplication];356 unsafe {
357// // let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];357 let app: id = msg_send![APP_CLASS, sharedApplication];
358// // app.setDelegate_(app_delegate);358 let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
359 359 app.setDelegate_(app_delegate);
360// // let self_ptr = self as *const Self as *const c_void;360
361// // (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);361 let self_ptr = self as *const Self as *const c_void;
362// // (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);362 (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
363 363 (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);
364// // let pool = NSAutoreleasePool::new(nil);364
365// // app.run();365 let pool = NSAutoreleasePool::new(nil);
366// // pool.drain();366 app.run();
367 367 pool.drain();
368// // (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());368
369// // (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());369 (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
370// // }370 (*app.delegate()).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());
371// // }371 }
372 372 }
373// // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {373
374// // self.0.borrow_mut().menu_command = Some(callback);374 fn quit(&self) {
375// // }375 // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks
376 376 // synchronously before this method terminates. If we call `Platform::quit` while holding a
377// // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {377 // borrow of the app state (which most of the time we will do), we will end up
378// // self.0.borrow_mut().will_open_menu = Some(callback);378 // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve
379// // }379 // this, we make quitting the application asynchronous so that we aren't holding borrows to
380 380 // the app state on the stack when we actually terminate the app.
381// // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {381
382// // self.0.borrow_mut().validate_menu_command = Some(callback);382 use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};
383// // }383
384 384 unsafe {
385// // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {385 dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));
386// // unsafe {386 }
387// // let app: id = msg_send![APP_CLASS, sharedApplication];387
388// // let mut state = self.0.borrow_mut();388 unsafe extern "C" fn quit(_: *mut c_void) {
389// // let actions = &mut state.menu_actions;389 let app = NSApplication::sharedApplication(nil);
390// // app.setMainMenu_(self.create_menu_bar(390 let _: () = msg_send![app, terminate: nil];
391// // menus,391 }
392// // app.delegate(),392 }
393// // actions,393
394// // keystroke_matcher,394 fn restart(&self) {
395// // ));395 use std::os::unix::process::CommandExt as _;
396// // }396
397// // }397 let app_pid = std::process::id().to_string();
398 398 let app_path = self
399// // fn prompt_for_paths(399 .app_path()
400// // &self,400 .ok()
401// // options: platform::PathPromptOptions,401 // When the app is not bundled, `app_path` returns the
402// // ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {402 // directory containing the executable. Disregard this
403// // unsafe {403 // and get the path to the executable itself.
404// // let panel = NSOpenPanel::openPanel(nil);404 .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))
405// // panel.setCanChooseDirectories_(options.directories.to_objc());405 .unwrap_or_else(|| std::env::current_exe().unwrap());
406// // panel.setCanChooseFiles_(options.files.to_objc());406
407// // panel.setAllowsMultipleSelection_(options.multiple.to_objc());407 // Wait until this process has exited and then re-open this path.
408// // panel.setResolvesAliases_(false.to_objc());408 let script = r#"
409// // let (done_tx, done_rx) = oneshot::channel();409 while kill -0 $0 2> /dev/null; do
410// // let done_tx = Cell::new(Some(done_tx));410 sleep 0.1
411// // let block = ConcreteBlock::new(move |response: NSModalResponse| {411 done
412// // let result = if response == NSModalResponse::NSModalResponseOk {412 open "$1"
413// // let mut result = Vec::new();413 "#;
414// // let urls = panel.URLs();414
415// // for i in 0..urls.count() {415 let restart_process = Command::new("/bin/bash")
416// // let url = urls.objectAtIndex(i);416 .arg("-c")
417// // if url.isFileURL() == YES {417 .arg(script)
418// // if let Ok(path) = ns_url_to_path(url) {418 .arg(app_pid)
419// // result.push(path)419 .arg(app_path)
420// // }420 .process_group(0)
421// // }421 .spawn();
422// // }422
423// // Some(result)423 match restart_process {
424// // } else {424 Ok(_) => self.quit(),
425// // None425 Err(e) => log::error!("failed to spawn restart script: {:?}", e),
426// // };426 }
427 427 }
428// // if let Some(mut done_tx) = done_tx.take() {428
429// // let _ = postage::sink::Sink::try_send(&mut done_tx, result);429 fn activate(&self, ignoring_other_apps: bool) {
430// // }430 unsafe {
431// // });431 let app = NSApplication::sharedApplication(nil);
432// // let block = block.copy();432 app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());
433// // let _: () = msg_send![panel, beginWithCompletionHandler: block];433 }
434// // done_rx434 }
435// // }435
436// // }436 fn hide(&self) {
437 437 unsafe {
438// // fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {438 let app = NSApplication::sharedApplication(nil);
439// // unsafe {439 let _: () = msg_send![app, hide: nil];
440// // let panel = NSSavePanel::savePanel(nil);440 }
441// // let path = ns_string(directory.to_string_lossy().as_ref());441 }
442// // let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());442
443// // panel.setDirectoryURL(url);443 fn hide_other_apps(&self) {
444 444 unsafe {
445// // let (done_tx, done_rx) = oneshot::channel();445 let app = NSApplication::sharedApplication(nil);
446// // let done_tx = Cell::new(Some(done_tx));446 let _: () = msg_send![app, hideOtherApplications: nil];
447// // let block = ConcreteBlock::new(move |response: NSModalResponse| {447 }
448// // let mut result = None;448 }
449// // if response == NSModalResponse::NSModalResponseOk {449
450// // let url = panel.URL();450 fn unhide_other_apps(&self) {
451// // if url.isFileURL() == YES {451 unsafe {
452// // result = ns_url_to_path(panel.URL()).ok()452 let app = NSApplication::sharedApplication(nil);
453// // }453 let _: () = msg_send![app, unhideAllApplications: nil];
454// // }454 }
455 455 }
456// // if let Some(mut done_tx) = done_tx.take() {456
457// // let _ = postage::sink::Sink::try_send(&mut done_tx, result);457 fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
458// // }458 MacScreen::all()
459// // });459 .into_iter()
460// // let block = block.copy();460 .map(|screen| Rc::new(screen) as Rc<_>)
461// // let _: () = msg_send![panel, beginWithCompletionHandler: block];461 .collect()
462// // done_rx462 }
463// // }463
464// // }464 fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn PlatformScreen>> {
465 465 MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
466// // fn reveal_path(&self, path: &Path) {466 }
467// // unsafe {467
468// // let path = path.to_path_buf();468 fn main_window(&self) -> Option<AnyWindowHandle> {
469// // self.0469 MacWindow::main_window()
470// // .borrow()470 }
471// // .foreground471
472// // .spawn(async move {472 // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
473// // let full_path = ns_string(path.to_str().unwrap_or(""));473 // Box::new(StatusItem::add(self.fonts()))
474// // let root_full_path = ns_string("");474 // }
475// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];475
476// // let _: BOOL = msg_send![476 fn open_window(
477// // workspace,477 &self,
478// // selectFile: full_path478 handle: AnyWindowHandle,
479// // inFileViewerRootedAtPath: root_full_path479 options: WindowOptions,
480// // ];480 ) -> Box<dyn PlatformWindow> {
481// // })481 Box::new(MacWindow::open(
482// // .detach();482 handle,
483// // }483 options,
484// // }484 self.executor(),
485// // }485 self.text_system(),
486 486 ))
487// // impl Platform for MacPlatform {487 }
488// // fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {488
489// // self.dispatcher.clone()489 fn open_url(&self, url: &str) {
490// // }490 unsafe {
491 491 let url = NSURL::alloc(nil)
492// // fn fonts(&self) -> Arc<dyn platform::FontSystem> {492 .initWithString_(ns_string(url))
493// // self.fonts.clone()493 .autorelease();
494// // }494 let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
495 495 msg_send![workspace, openURL: url]
496// // fn activate(&self, ignoring_other_apps: bool) {496 }
497// // unsafe {497 }
498// // let app = NSApplication::sharedApplication(nil);498
499// // app.activateIgnoringOtherApps_(ignoring_other_apps.to_objc());499 fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
500// // }500 self.0.borrow_mut().open_urls = Some(callback);
501// // }501 }
502 502
503// // fn hide(&self) {503 fn prompt_for_paths(
504// // unsafe {504 &self,
505// // let app = NSApplication::sharedApplication(nil);505 options: PathPromptOptions,
506// // let _: () = msg_send![app, hide: nil];506 ) -> oneshot::Receiver<Option<Vec<PathBuf>>> {
507// // }507 unsafe {
508// // }508 let panel = NSOpenPanel::openPanel(nil);
509 509 panel.setCanChooseDirectories_(options.directories.to_objc());
510// // fn hide_other_apps(&self) {510 panel.setCanChooseFiles_(options.files.to_objc());
511// // unsafe {511 panel.setAllowsMultipleSelection_(options.multiple.to_objc());
512// // let app = NSApplication::sharedApplication(nil);512 panel.setResolvesAliases_(false.to_objc());
513// // let _: () = msg_send![app, hideOtherApplications: nil];513 let (done_tx, done_rx) = oneshot::channel();
514// // }514 let done_tx = Cell::new(Some(done_tx));
515// // }515 let block = ConcreteBlock::new(move |response: NSModalResponse| {
516 516 let result = if response == NSModalResponse::NSModalResponseOk {
517// // fn unhide_other_apps(&self) {517 let mut result = Vec::new();
518// // unsafe {518 let urls = panel.URLs();
519// // let app = NSApplication::sharedApplication(nil);519 for i in 0..urls.count() {
520// // let _: () = msg_send![app, unhideAllApplications: nil];520 let url = urls.objectAtIndex(i);
521// // }521 if url.isFileURL() == YES {
522// // }522 if let Ok(path) = ns_url_to_path(url) {
523 523 result.push(path)
524// // fn quit(&self) {524 }
525// // // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks525 }
526// // // synchronously before this method terminates. If we call `Platform::quit` while holding a526 }
527// // // borrow of the app state (which most of the time we will do), we will end up527 Some(result)
528// // // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve528 } else {
529// // // this, we make quitting the application asynchronous so that we aren't holding borrows to529 None
530// // // the app state on the stack when we actually terminate the app.530 };
531 531
532// // use super::dispatcher::{dispatch_async_f, dispatch_get_main_queue};532 if let Some(mut done_tx) = done_tx.take() {
533 533 let _ = done_tx.send(result);
534// // unsafe {534 }
535// // dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));535 });
536// // }536 let block = block.copy();
537 537 let _: () = msg_send![panel, beginWithCompletionHandler: block];
538// // unsafe extern "C" fn quit(_: *mut c_void) {538 done_rx
539// // let app = NSApplication::sharedApplication(nil);539 }
540// // let _: () = msg_send![app, terminate: nil];540 }
541// // }541
542// // }542 fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> {
543 543 unsafe {
544// // fn screen_by_id(&self, id: uuid::Uuid) -> Option<Rc<dyn platform::Screen>> {544 let panel = NSSavePanel::savePanel(nil);
545// // Screen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)545 let path = ns_string(directory.to_string_lossy().as_ref());
546// // }546 let url = NSURL::fileURLWithPath_isDirectory_(nil, path, true.to_objc());
547 547 panel.setDirectoryURL(url);
548// // fn screens(&self) -> Vec<Rc<dyn platform::Screen>> {548
549// // Screen::all()549 let (done_tx, done_rx) = oneshot::channel();
550// // .into_iter()550 let done_tx = Cell::new(Some(done_tx));
551// // .map(|screen| Rc::new(screen) as Rc<_>)551 let block = ConcreteBlock::new(move |response: NSModalResponse| {
552// // .collect()552 let mut result = None;
553// // }553 if response == NSModalResponse::NSModalResponseOk {
554 554 let url = panel.URL();
555// // fn open_window(555 if url.isFileURL() == YES {
556// // &self,556 result = ns_url_to_path(panel.URL()).ok()
557// // handle: AnyWindowHandle,557 }
558// // options: platform::WindowOptions,558 }
559// // executor: Rc<executor::Foreground>,559
560// // ) -> Box<dyn platform::Window> {560 if let Some(mut done_tx) = done_tx.take() {
561// // Box::new(MacWindow::open(handle, options, executor, self.fonts()))561 let _ = done_tx.send(result);
562// // }562 }
563 563 });
564// // fn main_window(&self) -> Option<AnyWindowHandle> {564 let block = block.copy();
565// // MacWindow::main_window()565 let _: () = msg_send![panel, beginWithCompletionHandler: block];
566// // }566 done_rx
567 567 }
568// // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {568 }
569// // Box::new(StatusItem::add(self.fonts()))569
570// // }570 fn reveal_path(&self, path: &Path) {
571 571 unsafe {
572// // fn write_to_clipboard(&self, item: ClipboardItem) {572 let path = path.to_path_buf();
573// // unsafe {573 self.0
574// // self.pasteboard.clearContents();574 .borrow()
575 575 .executor
576// // let text_bytes = NSData::dataWithBytes_length_(576 .spawn(async move {
577// // nil,577 let full_path = ns_string(path.to_str().unwrap_or(""));
578// // item.text.as_ptr() as *const c_void,578 let root_full_path = ns_string("");
579// // item.text.len() as u64,579 let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];
580// // );580 let _: BOOL = msg_send![
581// // self.pasteboard581 workspace,
582// // .setData_forType(text_bytes, NSPasteboardTypeString);582 selectFile: full_path
583 583 inFileViewerRootedAtPath: root_full_path
584// // if let Some(metadata) = item.metadata.as_ref() {584 ];
585// // let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();585 })
586// // let hash_bytes = NSData::dataWithBytes_length_(586 .detach();
587// // nil,587 }
588// // hash_bytes.as_ptr() as *const c_void,588 }
589// // hash_bytes.len() as u64,589
590// // );590 fn on_become_active(&self, callback: Box<dyn FnMut()>) {
591// // self.pasteboard591 self.0.borrow_mut().become_active = Some(callback);
592// // .setData_forType(hash_bytes, self.text_hash_pasteboard_type);592 }
593 593
594// // let metadata_bytes = NSData::dataWithBytes_length_(594 fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
595// // nil,595 self.0.borrow_mut().resign_active = Some(callback);
596// // metadata.as_ptr() as *const c_void,596 }
597// // metadata.len() as u64,597
598// // );598 fn on_quit(&self, callback: Box<dyn FnMut()>) {
599// // self.pasteboard599 self.0.borrow_mut().quit = Some(callback);
600// // .setData_forType(metadata_bytes, self.metadata_pasteboard_type);600 }
601// // }601
602// // }602 fn on_reopen(&self, callback: Box<dyn FnMut()>) {
603// // }603 self.0.borrow_mut().reopen = Some(callback);
604 604 }
605// // fn read_from_clipboard(&self) -> Option<ClipboardItem> {605
606// // unsafe {606 fn on_event(&self, callback: Box<dyn FnMut(Event) -> bool>) {
607// // if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {607 self.0.borrow_mut().event = Some(callback);
608// // let text = String::from_utf8_lossy(text_bytes).to_string();608 }
609// // let hash_bytes = self609
610// // .read_from_pasteboard(self.text_hash_pasteboard_type)610 fn os_name(&self) -> &'static str {
611// // .and_then(|bytes| bytes.try_into().ok())611 "macOS"
612// // .map(u64::from_be_bytes);612 }
613// // let metadata_bytes = self613
614// // .read_from_pasteboard(self.metadata_pasteboard_type)614 fn os_version(&self) -> Result<SemanticVersion> {
615// // .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());615 unsafe {
616 616 let process_info = NSProcessInfo::processInfo(nil);
617// // if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {617 let version = process_info.operatingSystemVersion();
618// // if hash == ClipboardItem::text_hash(&text) {618 Ok(SemanticVersion {
619// // Some(ClipboardItem {619 major: version.majorVersion as usize,
620// // text,620 minor: version.minorVersion as usize,
621// // metadata: Some(metadata),621 patch: version.patchVersion as usize,
622// // })622 })
623// // } else {623 }
624// // Some(ClipboardItem {624 }
625// // text,625
626// // metadata: None,626 fn app_version(&self) -> Result<SemanticVersion> {
627// // })627 unsafe {
628// // }628 let bundle: id = NSBundle::mainBundle();
629// // } else {629 if bundle.is_null() {
630// // Some(ClipboardItem {630 Err(anyhow!("app is not running inside a bundle"))
631// // text,631 } else {
632// // metadata: None,632 let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];
633// // })633 let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
634// // }634 let bytes = version.UTF8String() as *const u8;
635// // } else {635 let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
636// // None636 version.parse()
637// // }637 }
638// // }638 }
639// // }639 }
640 640
641// // fn open_url(&self, url: &str) {641 fn app_path(&self) -> Result<PathBuf> {
642// // unsafe {642 unsafe {
643// // let url = NSURL::alloc(nil)643 let bundle: id = NSBundle::mainBundle();
644// // .initWithString_(ns_string(url))644 if bundle.is_null() {
645// // .autorelease();645 Err(anyhow!("app is not running inside a bundle"))
646// // let workspace: id = msg_send![class!(NSWorkspace), sharedWorkspace];646 } else {
647// // msg_send![workspace, openURL: url]647 Ok(path_from_objc(msg_send![bundle, bundlePath]))
648// // }648 }
649// // }649 }
650 650 }
651// // fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {651
652// // let url = CFString::from(url);652 fn local_timezone(&self) -> UtcOffset {
653// // let username = CFString::from(username);653 unsafe {
654// // let password = CFData::from_buffer(password);654 let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];
655 655 let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];
656// // unsafe {656 UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()
657// // use security::*;657 }
658 658 }
659// // // First, check if there are already credentials for the given server. If so, then659
660// // // update the username and password.660 fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {
661// // let mut verb = "updating";661 unsafe {
662// // let mut query_attrs = CFMutableDictionary::with_capacity(2);662 let bundle: id = NSBundle::mainBundle();
663// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);663 if bundle.is_null() {
664// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());664 Err(anyhow!("app is not running inside a bundle"))
665 665 } else {
666// // let mut attrs = CFMutableDictionary::with_capacity(4);666 let name = ns_string(name);
667// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);667 let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];
668// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());668 if url.is_null() {
669// // attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());669 Err(anyhow!("resource not found"))
670// // attrs.set(kSecValueData as *const _, password.as_CFTypeRef());670 } else {
671 671 ns_url_to_path(url)
672// // let mut status = SecItemUpdate(672 }
673// // query_attrs.as_concrete_TypeRef(),673 }
674// // attrs.as_concrete_TypeRef(),674 }
675// // );675 }
676 676
677// // // If there were no existing credentials for the given server, then create them.677 fn set_cursor_style(&self, style: CursorStyle) {
678// // if status == errSecItemNotFound {678 unsafe {
679// // verb = "creating";679 let new_cursor: id = match style {
680// // status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());680 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],
681// // }681 CursorStyle::ResizeLeftRight => {
682 682 msg_send![class!(NSCursor), resizeLeftRightCursor]
683// // if status != errSecSuccess {683 }
684// // return Err(anyhow!("{} password failed: {}", verb, status));684 CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
685// // }685 CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
686// // }686 CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],
687// // Ok(())687 };
688// // }688
689 689 let old_cursor: id = msg_send![class!(NSCursor), currentCursor];
690// // fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {690 if new_cursor != old_cursor {
691// // let url = CFString::from(url);691 let _: () = msg_send![new_cursor, set];
692// // let cf_true = CFBoolean::true_value().as_CFTypeRef();692 }
693 693 }
694// // unsafe {694 }
695// // use security::*;695
696 696 fn should_auto_hide_scrollbars(&self) -> bool {
697// // // Find any credentials for the given server URL.697 #[allow(non_upper_case_globals)]
698// // let mut attrs = CFMutableDictionary::with_capacity(5);698 const NSScrollerStyleOverlay: NSInteger = 1;
699// // attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);699
700// // attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());700 unsafe {
701// // attrs.set(kSecReturnAttributes as *const _, cf_true);701 let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];
702// // attrs.set(kSecReturnData as *const _, cf_true);702 style == NSScrollerStyleOverlay
703 703 }
704// // let mut result = CFTypeRef::from(ptr::null());704 }
705// // let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);705
706// // match status {706 fn write_to_clipboard(&self, item: ClipboardItem) {
707// // security::errSecSuccess => {}707 let state = self.0.borrow();
708// // security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),708 unsafe {
709// // _ => return Err(anyhow!("reading password failed: {}", status)),709 state.pasteboard.clearContents();
710// // }710
711 711 let text_bytes = NSData::dataWithBytes_length_(
712// // let result = CFType::wrap_under_create_rule(result)712 nil,
713// // .downcast::<CFDictionary>()713 item.text.as_ptr() as *const c_void,
714// // .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;714 item.text.len() as u64,
715// // let username = result715 );
716// // .find(kSecAttrAccount as *const _)716 state
717// // .ok_or_else(|| anyhow!("account was missing from keychain item"))?;717 .pasteboard
718// // let username = CFType::wrap_under_get_rule(*username)718 .setData_forType(text_bytes, NSPasteboardTypeString);
719// // .downcast::<CFString>()719
720// // .ok_or_else(|| anyhow!("account was not a string"))?;720 if let Some(metadata) = item.metadata.as_ref() {
721// // let password = result721 let hash_bytes = ClipboardItem::text_hash(&item.text).to_be_bytes();
722// // .find(kSecValueData as *const _)722 let hash_bytes = NSData::dataWithBytes_length_(
723// // .ok_or_else(|| anyhow!("password was missing from keychain item"))?;723 nil,
724// // let password = CFType::wrap_under_get_rule(*password)724 hash_bytes.as_ptr() as *const c_void,
725// // .downcast::<CFData>()725 hash_bytes.len() as u64,
726// // .ok_or_else(|| anyhow!("password was not a string"))?;726 );
727 727 state
728// // Ok(Some((username.to_string(), password.bytes().to_vec())))728 .pasteboard
729// // }729 .setData_forType(hash_bytes, state.text_hash_pasteboard_type);
730// // }730
731 731 let metadata_bytes = NSData::dataWithBytes_length_(
732// // fn delete_credentials(&self, url: &str) -> Result<()> {732 nil,
733// // let url = CFString::from(url);733 metadata.as_ptr() as *const c_void,
734 734 metadata.len() as u64,
735// // unsafe {735 );
736// // use security::*;736 state
737 737 .pasteboard
738// // let mut query_attrs = CFMutableDictionary::with_capacity(2);738 .setData_forType(metadata_bytes, state.metadata_pasteboard_type);
739// // query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);739 }
740// // query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());740 }
741 741 }
742// // let status = SecItemDelete(query_attrs.as_concrete_TypeRef());742
743 743 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
744// // if status != errSecSuccess {744 let state = self.0.borrow();
745// // return Err(anyhow!("delete password failed: {}", status));745 unsafe {
746// // }746 if let Some(text_bytes) = self.read_from_pasteboard(NSPasteboardTypeString) {
747// // }747 let text = String::from_utf8_lossy(text_bytes).to_string();
748// // Ok(())748 let hash_bytes = self
749// // }749 .read_from_pasteboard(state.text_hash_pasteboard_type)
750 750 .and_then(|bytes| bytes.try_into().ok())
751// // fn set_cursor_style(&self, style: CursorStyle) {751 .map(u64::from_be_bytes);
752// // unsafe {752 let metadata_bytes = self
753// // let new_cursor: id = match style {753 .read_from_pasteboard(state.metadata_pasteboard_type)
754// // CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],754 .and_then(|bytes| String::from_utf8(bytes.to_vec()).ok());
755// // CursorStyle::ResizeLeftRight => {755
756// // msg_send![class!(NSCursor), resizeLeftRightCursor]756 if let Some((hash, metadata)) = hash_bytes.zip(metadata_bytes) {
757// // }757 if hash == ClipboardItem::text_hash(&text) {
758// // CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],758 Some(ClipboardItem {
759// // CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],759 text,
760// // CursorStyle::IBeam => msg_send![class!(NSCursor), IBeamCursor],760 metadata: Some(metadata),
761// // };761 })
762 762 } else {
763// // let old_cursor: id = msg_send![class!(NSCursor), currentCursor];763 Some(ClipboardItem {
764// // if new_cursor != old_cursor {764 text,
765// // let _: () = msg_send![new_cursor, set];765 metadata: None,
766// // }766 })
767// // }767 }
768// // }768 } else {
769 769 Some(ClipboardItem {
770// // fn should_auto_hide_scrollbars(&self) -> bool {770 text,
771// // #[allow(non_upper_case_globals)]771 metadata: None,
772// // const NSScrollerStyleOverlay: NSInteger = 1;772 })
773 773 }
774// // unsafe {774 } else {
775// // let style: NSInteger = msg_send![class!(NSScroller), preferredScrollerStyle];775 None
776// // style == NSScrollerStyleOverlay776 }
777// // }777 }
778// // }778 }
779 779
780// // fn local_timezone(&self) -> UtcOffset {780 fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
781// // unsafe {781 let url = CFString::from(url);
782// // let local_timezone: id = msg_send![class!(NSTimeZone), localTimeZone];782 let username = CFString::from(username);
783// // let seconds_from_gmt: NSInteger = msg_send![local_timezone, secondsFromGMT];783 let password = CFData::from_buffer(password);
784// // UtcOffset::from_whole_seconds(seconds_from_gmt.try_into().unwrap()).unwrap()784
785// // }785 unsafe {
786// // }786 use security::*;
787 787
788// // fn path_for_auxiliary_executable(&self, name: &str) -> Result<PathBuf> {788 // First, check if there are already credentials for the given server. If so, then
789// // unsafe {789 // update the username and password.
790// // let bundle: id = NSBundle::mainBundle();790 let mut verb = "updating";
791// // if bundle.is_null() {791 let mut query_attrs = CFMutableDictionary::with_capacity(2);
792// // Err(anyhow!("app is not running inside a bundle"))792 query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
793// // } else {793 query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
794// // let name = ns_string(name);794
795// // let url: id = msg_send![bundle, URLForAuxiliaryExecutable: name];795 let mut attrs = CFMutableDictionary::with_capacity(4);
796// // if url.is_null() {796 attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
797// // Err(anyhow!("resource not found"))797 attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
798// // } else {798 attrs.set(kSecAttrAccount as *const _, username.as_CFTypeRef());
799// // ns_url_to_path(url)799 attrs.set(kSecValueData as *const _, password.as_CFTypeRef());
800// // }800
801// // }801 let mut status = SecItemUpdate(
802// // }802 query_attrs.as_concrete_TypeRef(),
803// // }803 attrs.as_concrete_TypeRef(),
804 804 );
805// // fn app_path(&self) -> Result<PathBuf> {805
806// // unsafe {806 // If there were no existing credentials for the given server, then create them.
807// // let bundle: id = NSBundle::mainBundle();807 if status == errSecItemNotFound {
808// // if bundle.is_null() {808 verb = "creating";
809// // Err(anyhow!("app is not running inside a bundle"))809 status = SecItemAdd(attrs.as_concrete_TypeRef(), ptr::null_mut());
810// // } else {810 }
811// // Ok(path_from_objc(msg_send![bundle, bundlePath]))811
812// // }812 if status != errSecSuccess {
813// // }813 return Err(anyhow!("{} password failed: {}", verb, status));
814// // }814 }
815 815 }
816// // fn app_version(&self) -> Result<platform::AppVersion> {816 Ok(())
817// // unsafe {817 }
818// // let bundle: id = NSBundle::mainBundle();818
819// // if bundle.is_null() {819 // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
820// // Err(anyhow!("app is not running inside a bundle"))820 // self.0.borrow_mut().menu_command = Some(callback);
821// // } else {821 // }
822// // let version: id = msg_send![bundle, objectForInfoDictionaryKey: ns_string("CFBundleShortVersionString")];822
823// // let len = msg_send![version, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];823 // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
824// // let bytes = version.UTF8String() as *const u8;824 // self.0.borrow_mut().will_open_menu = Some(callback);
825// // let version = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();825 // }
826// // version.parse()826
827// // }827 // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
828// // }828 // self.0.borrow_mut().validate_menu_command = Some(callback);
829// // }829 // }
830 830
831// // fn os_name(&self) -> &'static str {831 // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
832// // "macOS"832 // unsafe {
833// // }833 // let app: id = msg_send![APP_CLASS, sharedApplication];
834 834 // let mut state = self.0.borrow_mut();
835// // fn os_version(&self) -> Result<crate::platform::AppVersion> {835 // let actions = &mut state.menu_actions;
836// // unsafe {836 // app.setMainMenu_(self.create_menu_bar(
837// // let process_info = NSProcessInfo::processInfo(nil);837 // menus,
838// // let version = process_info.operatingSystemVersion();838 // app.delegate(),
839// // Ok(AppVersion {839 // actions,
840// // major: version.majorVersion as usize,840 // keystroke_matcher,
841// // minor: version.minorVersion as usize,841 // ));
842// // patch: version.patchVersion as usize,842 // }
843// // })843 // }
844// // }844
845// // }845 fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>> {
846 846 let url = CFString::from(url);
847// // fn restart(&self) {847 let cf_true = CFBoolean::true_value().as_CFTypeRef();
848// // use std::os::unix::process::CommandExt as _;848
849 849 unsafe {
850// // let app_pid = std::process::id().to_string();850 use security::*;
851// // let app_path = self851
852// // .app_path()852 // Find any credentials for the given server URL.
853// // .ok()853 let mut attrs = CFMutableDictionary::with_capacity(5);
854// // // When the app is not bundled, `app_path` returns the854 attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
855// // // directory containing the executable. Disregard this855 attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
856// // // and get the path to the executable itself.856 attrs.set(kSecReturnAttributes as *const _, cf_true);
857// // .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))857 attrs.set(kSecReturnData as *const _, cf_true);
858// // .unwrap_or_else(|| std::env::current_exe().unwrap());858
859 859 let mut result = CFTypeRef::from(ptr::null());
860// // // Wait until this process has exited and then re-open this path.860 let status = SecItemCopyMatching(attrs.as_concrete_TypeRef(), &mut result);
861// // let script = r#"861 match status {
862// // while kill -0 $0 2> /dev/null; do862 security::errSecSuccess => {}
863// // sleep 0.1863 security::errSecItemNotFound | security::errSecUserCanceled => return Ok(None),
864// // done864 _ => return Err(anyhow!("reading password failed: {}", status)),
865// // open "$1"865 }
866// // "#;866
867 867 let result = CFType::wrap_under_create_rule(result)
868// // let restart_process = Command::new("/bin/bash")868 .downcast::<CFDictionary>()
869// // .arg("-c")869 .ok_or_else(|| anyhow!("keychain item was not a dictionary"))?;
870// // .arg(script)870 let username = result
871// // .arg(app_pid)871 .find(kSecAttrAccount as *const _)
872// // .arg(app_path)872 .ok_or_else(|| anyhow!("account was missing from keychain item"))?;
873// // .process_group(0)873 let username = CFType::wrap_under_get_rule(*username)
874// // .spawn();874 .downcast::<CFString>()
875 875 .ok_or_else(|| anyhow!("account was not a string"))?;
876// // match restart_process {876 let password = result
877// // Ok(_) => self.quit(),877 .find(kSecValueData as *const _)
878// // Err(e) => log::error!("failed to spawn restart script: {:?}", e),878 .ok_or_else(|| anyhow!("password was missing from keychain item"))?;
879// // }879 let password = CFType::wrap_under_get_rule(*password)
880// // }880 .downcast::<CFData>()
881// // }881 .ok_or_else(|| anyhow!("password was not a string"))?;
882 882
883// unsafe fn path_from_objc(path: id) -> PathBuf {883 Ok(Some((username.to_string(), password.bytes().to_vec())))
884// let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];884 }
885// let bytes = path.UTF8String() as *const u8;885 }
886// let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();886
887// PathBuf::from(path)887 fn delete_credentials(&self, url: &str) -> Result<()> {
888// }888 let url = CFString::from(url);
889 889
890// unsafe fn get_foreground_platform(object: &mut Object) -> &MacForegroundPlatform {890 unsafe {
891// let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);891 use security::*;
892// assert!(!platform_ptr.is_null());892
893// &*(platform_ptr as *const MacForegroundPlatform)893 let mut query_attrs = CFMutableDictionary::with_capacity(2);
894// }894 query_attrs.set(kSecClass as *const _, kSecClassInternetPassword as *const _);
895 895 query_attrs.set(kSecAttrServer as *const _, url.as_CFTypeRef());
896// extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {896
897// unsafe {897 let status = SecItemDelete(query_attrs.as_concrete_TypeRef());
898// if let Some(event) = Event::from_native(native_event, None) {898
899// let platform = get_foreground_platform(this);899 if status != errSecSuccess {
900// if let Some(callback) = platform.0.borrow_mut().event.as_mut() {900 return Err(anyhow!("delete password failed: {}", status));
901// if callback(event) {901 }
902// return;902 }
903// }903 Ok(())
904// }904 }
905// }905}
906// msg_send![super(this, class!(NSApplication)), sendEvent: native_event]906
907// }907unsafe fn path_from_objc(path: id) -> PathBuf {
908// }908 let len = msg_send![path, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
909 909 let bytes = path.UTF8String() as *const u8;
910// extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {910 let path = str::from_utf8(slice::from_raw_parts(bytes, len)).unwrap();
911// unsafe {911 PathBuf::from(path)
912// let app: id = msg_send![APP_CLASS, sharedApplication];912}
913// app.setActivationPolicy_(NSApplicationActivationPolicyRegular);913
914 914unsafe fn get_foreground_platform(object: &mut Object) -> &MacPlatform {
915// let platform = get_foreground_platform(this);915 let platform_ptr: *mut c_void = *object.get_ivar(MAC_PLATFORM_IVAR);
916// let callback = platform.0.borrow_mut().finish_launching.take();916 assert!(!platform_ptr.is_null());
917// if let Some(callback) = callback {917 &*(platform_ptr as *const MacPlatform)
918// callback();918}
919// }919
920// }920extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
921// }921 unsafe {
922 922 if let Some(event) = Event::from_native(native_event, None) {
923// extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {923 let platform = get_foreground_platform(this);
924// if !has_open_windows {924 if let Some(callback) = platform.0.borrow_mut().event.as_mut() {
925// let platform = unsafe { get_foreground_platform(this) };925 if callback(event) {
926// if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {926 return;
927// callback();927 }
928// }928 }
929// }929 }
930// }930 msg_send![super(this, class!(NSApplication)), sendEvent: native_event]
931 931 }
932// extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {932}
933// let platform = unsafe { get_foreground_platform(this) };933
934// if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {934extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
935// callback();935 unsafe {
936// }936 let app: id = msg_send![APP_CLASS, sharedApplication];
937// }937 app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
938 938
939// extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {939 let platform = get_foreground_platform(this);
940// let platform = unsafe { get_foreground_platform(this) };940 let callback = platform.0.borrow_mut().finish_launching.take();
941// if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {941 if let Some(callback) = callback {
942// callback();942 callback();
943// }943 }
944// }944 }
945 945}
946// extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {946
947// let platform = unsafe { get_foreground_platform(this) };947extern "C" fn should_handle_reopen(this: &mut Object, _: Sel, _: id, has_open_windows: bool) {
948// if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {948 if !has_open_windows {
949// callback();949 let platform = unsafe { get_foreground_platform(this) };
950// }950 if let Some(callback) = platform.0.borrow_mut().reopen.as_mut() {
951// }951 callback();
952 952 }
953// extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {953 }
954// let urls = unsafe {954}
955// (0..urls.count())955
956// .into_iter()956extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
957// .filter_map(|i| {957 let platform = unsafe { get_foreground_platform(this) };
958// let url = urls.objectAtIndex(i);958 if let Some(callback) = platform.0.borrow_mut().become_active.as_mut() {
959// match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {959 callback();
960// Ok(string) => Some(string.to_string()),960 }
961// Err(err) => {961}
962// log::error!("error converting path to string: {}", err);962
963// None963extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
964// }964 let platform = unsafe { get_foreground_platform(this) };
965// }965 if let Some(callback) = platform.0.borrow_mut().resign_active.as_mut() {
966// })966 callback();
967// .collect::<Vec<_>>()967 }
968// };968}
969// let platform = unsafe { get_foreground_platform(this) };969
970// if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {970extern "C" fn will_terminate(this: &mut Object, _: Sel, _: id) {
971// callback(urls);971 let platform = unsafe { get_foreground_platform(this) };
972// }972 if let Some(callback) = platform.0.borrow_mut().quit.as_mut() {
973// }973 callback();
974 974 }
975// extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {975}
976// unsafe {976
977// let platform = get_foreground_platform(this);977extern "C" fn open_urls(this: &mut Object, _: Sel, _: id, urls: id) {
978// let mut platform = platform.0.borrow_mut();978 let urls = unsafe {
979// if let Some(mut callback) = platform.menu_command.take() {979 (0..urls.count())
980// let tag: NSInteger = msg_send![item, tag];980 .into_iter()
981// let index = tag as usize;981 .filter_map(|i| {
982// if let Some(action) = platform.menu_actions.get(index) {982 let url = urls.objectAtIndex(i);
983// callback(action.as_ref());983 match CStr::from_ptr(url.absoluteString().UTF8String() as *mut c_char).to_str() {
984// }984 Ok(string) => Some(string.to_string()),
985// platform.menu_command = Some(callback);985 Err(err) => {
986// }986 log::error!("error converting path to string: {}", err);
987// }987 None
988// }988 }
989 989 }
990// extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {990 })
991// unsafe {991 .collect::<Vec<_>>()
992// let mut result = false;992 };
993// let platform = get_foreground_platform(this);993 let platform = unsafe { get_foreground_platform(this) };
994// let mut platform = platform.0.borrow_mut();994 if let Some(callback) = platform.0.borrow_mut().open_urls.as_mut() {
995// if let Some(mut callback) = platform.validate_menu_command.take() {995 callback(urls);
996// let tag: NSInteger = msg_send![item, tag];996 }
997// let index = tag as usize;997}
998// if let Some(action) = platform.menu_actions.get(index) {998
999// result = callback(action.as_ref());999extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
1000// }1000 todo!()
1001// platform.validate_menu_command = Some(callback);1001 // unsafe {
1002// }1002 // let platform = get_foreground_platform(this);
1003// result1003 // let mut platform = platform.0.borrow_mut();
1004// }1004 // if let Some(mut callback) = platform.menu_command.take() {
1005// }1005 // let tag: NSInteger = msg_send![item, tag];
1006 1006 // let index = tag as usize;
1007// extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {1007 // if let Some(action) = platform.menu_actions.get(index) {
1008// unsafe {1008 // callback(action.as_ref());
1009// let platform = get_foreground_platform(this);1009 // }
1010// let mut platform = platform.0.borrow_mut();1010 // platform.menu_command = Some(callback);
1011// if let Some(mut callback) = platform.will_open_menu.take() {1011 // }
1012// callback();1012 // }
1013// platform.will_open_menu = Some(callback);1013}
1014// }1014
1015// }1015extern "C" fn validate_menu_item(this: &mut Object, _: Sel, item: id) -> bool {
1016// }1016 todo!()
1017 1017 // unsafe {
1018// unsafe fn ns_string(string: &str) -> id {1018 // let mut result = false;
1019// NSString::alloc(nil).init_str(string).autorelease()1019 // let platform = get_foreground_platform(this);
1020// }1020 // let mut platform = platform.0.borrow_mut();
1021 1021 // if let Some(mut callback) = platform.validate_menu_command.take() {
1022// unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {1022 // let tag: NSInteger = msg_send![item, tag];
1023// let path: *mut c_char = msg_send![url, fileSystemRepresentation];1023 // let index = tag as usize;
1024// if path.is_null() {1024 // if let Some(action) = platform.menu_actions.get(index) {
1025// Err(anyhow!(1025 // result = callback(action.as_ref());
1026// "url is not a file path: {}",1026 // }
1027// CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()1027 // platform.validate_menu_command = Some(callback);
1028// ))1028 // }
1029// } else {1029 // result
1030// Ok(PathBuf::from(OsStr::from_bytes(1030 // }
1031// CStr::from_ptr(path).to_bytes(),1031}
1032// )))1032
1033// }1033extern "C" fn menu_will_open(this: &mut Object, _: Sel, _: id) {
1034// }1034 unsafe {
1035 1035 let platform = get_foreground_platform(this);
1036// mod security {1036 let mut platform = platform.0.borrow_mut();
1037// #![allow(non_upper_case_globals)]1037 if let Some(mut callback) = platform.will_open_menu.take() {
1038// use super::*;1038 callback();
1039 1039 platform.will_open_menu = Some(callback);
1040// #[link(name = "Security", kind = "framework")]1040 }
1041// extern "C" {1041 }
1042// pub static kSecClass: CFStringRef;1042}
1043// pub static kSecClassInternetPassword: CFStringRef;1043
1044// pub static kSecAttrServer: CFStringRef;1044unsafe fn ns_string(string: &str) -> id {
1045// pub static kSecAttrAccount: CFStringRef;1045 NSString::alloc(nil).init_str(string).autorelease()
1046// pub static kSecValueData: CFStringRef;1046}
1047// pub static kSecReturnAttributes: CFStringRef;1047
1048// pub static kSecReturnData: CFStringRef;1048unsafe fn ns_url_to_path(url: id) -> Result<PathBuf> {
1049 1049 let path: *mut c_char = msg_send![url, fileSystemRepresentation];
1050// pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;1050 if path.is_null() {
1051// pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;1051 Err(anyhow!(
1052// pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;1052 "url is not a file path: {}",
1053// pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;1053 CStr::from_ptr(url.absoluteString().UTF8String()).to_string_lossy()
1054// }1054 ))
1055 1055 } else {
1056// pub const errSecSuccess: OSStatus = 0;1056 Ok(PathBuf::from(OsStr::from_bytes(
1057// pub const errSecUserCanceled: OSStatus = -128;1057 CStr::from_ptr(path).to_bytes(),
1058// pub const errSecItemNotFound: OSStatus = -25300;1058 )))
1059// }1059 }
1060 1060}
1061// #[cfg(test)]1061
1062// mod tests {1062mod security {
1063// use crate::platform::Platform;1063 #![allow(non_upper_case_globals)]
1064 1064 use super::*;
1065// use super::*;1065
1066 1066 #[link(name = "Security", kind = "framework")]
1067// #[test]1067 extern "C" {
1068// fn test_clipboard() {1068 pub static kSecClass: CFStringRef;
1069// let platform = build_platform();1069 pub static kSecClassInternetPassword: CFStringRef;
1070// assert_eq!(platform.read_from_clipboard(), None);1070 pub static kSecAttrServer: CFStringRef;
1071 1071 pub static kSecAttrAccount: CFStringRef;
1072// let item = ClipboardItem::new("1".to_string());1072 pub static kSecValueData: CFStringRef;
1073// platform.write_to_clipboard(item.clone());1073 pub static kSecReturnAttributes: CFStringRef;
1074// assert_eq!(platform.read_from_clipboard(), Some(item));1074 pub static kSecReturnData: CFStringRef;
1075 1075
1076// let item = ClipboardItem::new("2".to_string()).with_metadata(vec![3, 4]);1076 pub fn SecItemAdd(attributes: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1077// platform.write_to_clipboard(item.clone());1077 pub fn SecItemUpdate(query: CFDictionaryRef, attributes: CFDictionaryRef) -> OSStatus;
1078// assert_eq!(platform.read_from_clipboard(), Some(item));1078 pub fn SecItemDelete(query: CFDictionaryRef) -> OSStatus;
1079 1079 pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus;
1080// let text_from_other_app = "text from other app";1080 }
1081// unsafe {1081
1082// let bytes = NSData::dataWithBytes_length_(1082 pub const errSecSuccess: OSStatus = 0;
1083// nil,1083 pub const errSecUserCanceled: OSStatus = -128;
1084// text_from_other_app.as_ptr() as *const c_void,1084 pub const errSecItemNotFound: OSStatus = -25300;
1085// text_from_other_app.len() as u64,1085}
1086// );1086
1087// platform1087#[cfg(test)]
1088// .pasteboard1088mod tests {
1089// .setData_forType(bytes, NSPasteboardTypeString);1089 use crate::ClipboardItem;
1090// }1090
1091// assert_eq!(1091 use super::*;
1092// platform.read_from_clipboard(),1092
1093// Some(ClipboardItem::new(text_from_other_app.to_string()))1093 #[test]
1094// );1094 fn test_clipboard() {
1095// }1095 let platform = build_platform();
1096 1096 assert_eq!(platform.read_from_clipboard(), None);
1097// fn build_platform() -> MacPlatform {1097
1098// let mut platform = MacPlatform::new();1098 let item = ClipboardItem::new("1".to_string());
1099// platform.pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };1099 platform.write_to_clipboard(item.clone());
1100// platform1100 assert_eq!(platform.read_from_clipboard(), Some(item));
1101// }1101
1102// }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 @@
1use crate::{
2 point, px, size, Bounds, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
3 GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Run, RunStyle,
4 Size,
5};
6use cocoa::appkit::{CGFloat, CGPoint};
7use collections::HashMap;
8use core_foundation::{
9 array::CFIndex,
10 attributed_string::{CFAttributedStringRef, CFMutableAttributedString},
11 base::{CFRange, TCFType},
12 string::CFString,
13};
14use core_graphics::{
15 base::{kCGImageAlphaPremultipliedLast, CGGlyph},
16 color_space::CGColorSpace,
17 context::CGContext,
18};
19use core_text::{font::CTFont, line::CTLine, string_attributes::kCTFontAttributeName};
20use font_kit::{
21 handle::Handle, hinting::HintingOptions, metrics::Metrics, source::SystemSource,
22 sources::mem::MemSource,
23};
24use parking_lot::RwLock;
25use pathfinder_geometry::{
26 rect::{RectF, RectI},
27 transform2d::Transform2F,
28 vector::{Vector2F, Vector2I},
29};
30use std::{cell::RefCell, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
31
32use super::open_type;
33
34#[allow(non_upper_case_globals)]
35const kCGImageAlphaOnly: u32 = 7;
36
37pub struct MacTextSystem(RwLock<TextSystemState>);
38
39struct 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
47impl 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
59impl Default for MacTextSystem {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl 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
145impl 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)]
492struct StringIndexConverter<'a> {
493 text: &'a str,
494 utf8_ix: usize,
495 utf16_ix: usize,
496}
497
498impl<'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)]
531pub struct __CFTypesetter(c_void);
532
533pub type CTTypesetterRef = *const __CFTypesetter;
534
535#[link(name = "CoreText", kind = "framework")]
536extern "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
546impl 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
562impl 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
571impl 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
580impl 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
586impl 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
9}9}
10 10
11impl Platform for TestPlatform {11impl 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> {
13 todo!()57 todo!()
14 }58 }
15 59
@@ -21,7 +65,101 @@ impl Platform for TestPlatform { · 5 unmodified lines
21 todo!()65 todo!()
22 }66 }
23 67
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<()> {
25 todo!()163 todo!()
26 }164 }
27}165}
crates/gpui3/src/scene.rs+16−16
@@ -1,3 +1,5 @@
1use crate::{text::GlyphId, FontId};
2
1use super::{Bounds, Hsla, Pixels, Point};3use super::{Bounds, Hsla, Pixels, Point};
2use bytemuck::{Pod, Zeroable};4use bytemuck::{Pod, Zeroable};
3use plane_split::BspSplitter;5use plane_split::BspSplitter;
@@ -39,7 +41,7 @@ impl Scene { · 35 unmodified lines
39#[derive(Clone, Debug)]41#[derive(Clone, Debug)]
40pub enum Primitive {42pub enum Primitive {
41 Quad(Quad),43 Quad(Quad),
42 Glyph(Glyph),44 Glyph(RenderedGlyph),
43 Underline(Underline),45 Underline(Underline),
44}46}
45 47
@@ -58,7 +60,7 @@ impl Primitive { · 12 unmodified lines
58#[derive(Default)]60#[derive(Default)]
59pub struct PrimitiveBatch {61pub struct PrimitiveBatch {
60 pub quads: Vec<Quad>,62 pub quads: Vec<Quad>,
61 pub glyphs: Vec<Glyph>,63 pub glyphs: Vec<RenderedGlyph>,
62 pub underlines: Vec<Underline>,64 pub underlines: Vec<Underline>,
63}65}
64 66
@@ -96,26 +98,24 @@ unsafe impl Zeroable for Quad {} · 31 unmodified lines
96 98
97unsafe impl Pod for Quad {}99unsafe impl Pod for Quad {}
98 100
99#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
100pub struct GlyphId(u32);
101
102#[derive(Clone, Debug)]
103pub 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
111impl From<Quad> for Primitive {101impl From<Quad> for Primitive {
112 fn from(quad: Quad) -> Self {102 fn from(quad: Quad) -> Self {
113 Primitive::Quad(quad)103 Primitive::Quad(quad)
114 }104 }
115}105}
116 106
117impl From<Glyph> for Primitive {107#[derive(Debug, Clone, Copy)]
118 fn from(glyph: Glyph) -> Self {108#[repr(C)]
109pub 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
117impl From<RenderedGlyph> for Primitive {
118 fn from(glyph: RenderedGlyph) -> Self {
119 Primitive::Glyph(glyph)119 Primitive::Glyph(glyph)
120 }120 }
121}121}
crates/gpui3/src/text.rs+32−4
@@ -1,8 +1,7 @@
1use crate::{black, px};1use crate::{black, px};
2 2
3use super::{3use super::{
4 point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle,4 point, Bounds, FontId, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle, WindowContext,
5 WindowContext,
6};5};
7use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};6use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
8use smallvec::SmallVec;7use smallvec::SmallVec;
@@ -27,6 +26,35 @@ pub struct RunStyle { · 18 unmodified lines
27 pub underline: Option<UnderlineStyle>,26 pub underline: Option<UnderlineStyle>,
28}27}
29 28
29#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
30pub struct GlyphId(u32);
31
32impl From<GlyphId> for u32 {
33 fn from(value: GlyphId) -> Self {
34 value.0
35 }
36}
37
38impl From<u16> for GlyphId {
39 fn from(num: u16) -> Self {
40 GlyphId(num as u32)
41 }
42}
43
44impl From<u32> for GlyphId {
45 fn from(num: u32) -> Self {
46 GlyphId(num)
47 }
48}
49
50#[derive(Clone, Debug)]
51pub struct Glyph {
52 pub id: GlyphId,
53 pub position: Point<Pixels>,
54 pub index: usize,
55 pub is_emoji: bool,
56}
57
30impl TextLayoutCache {58impl TextLayoutCache {
31 pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {59 pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
32 Self {60 Self {
@@ -726,7 +754,7 @@ mod tests { · 693 unmodified lines
726 let cx = AppContext::test();754 let cx = AppContext::test();
727 755
728 let font_cache = cx.font_cache().clone();756 let font_cache = cx.font_cache().clone();
729 let font_system = cx.platform().font_system();757 let font_system = cx.platform().text_system();
730 let family = font_cache758 let family = font_cache
731 .load_family(&["Courier"], &Default::default())759 .load_family(&["Courier"], &Default::default())
732 .unwrap();760 .unwrap();
@@ -793,7 +821,7 @@ mod tests { · 60 unmodified lines
793 fn test_wrap_shaped_line() {821 fn test_wrap_shaped_line() {
794 let cx = AppContext::test();822 let cx = AppContext::test();
795 let font_cache = cx.font_cache().clone();823 let font_cache = cx.font_cache().clone();
796 let font_system = cx.platform().font_system();824 let font_system = cx.platform().text_system();
797 let text_layout_cache = TextLayoutCache::new(font_system.clone());825 let text_layout_cache = TextLayoutCache::new(font_system.clone());
798 826
799 let family = font_cache827 let family = font_cache
crates/storybook/Cargo.toml+0−6
@@ -13,22 +13,16 @@ test-support = [] · 12 unmodified lines
13 13
14[dependencies]14[dependencies]
15anyhow.workspace = true15anyhow.workspace = true
16bytemuck = "1.14.0"
17derive_more.workspace = true16derive_more.workspace = true
18gpui2 = { path = "../gpui2" }17gpui2 = { path = "../gpui2" }
19itertools = "0.11.0"
20log.workspace = true18log.workspace = true
21plane-split = "0.18.0"
22raw-window-handle = "0.5.2"
23refineable = { path = "../refineable" }19refineable = { path = "../refineable" }
24rust-embed.workspace = true20rust-embed.workspace = true
25serde.workspace = true21serde.workspace = true
26settings = { path = "../settings" }22settings = { path = "../settings" }
27simplelog = "0.9"23simplelog = "0.9"
28slotmap = "1.0.6"
29theme = { path = "../theme" }24theme = { path = "../theme" }
30util = { path = "../util" }25util = { path = "../util" }
31wgpu = "0.17.0"
32 26
33[dev-dependencies]27[dev-dependencies]
34gpui2 = { path = "../gpui2", features = ["test-support"] }28gpui2 = { 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