Skip to main content

gpui_base/input/base/
native.rs

1#[cfg(target_os = "macos")]
2mod macos {
3    use std::{cell::RefCell, collections::HashMap, mem, ptr, sync::Once};
4
5    use gpui::Window;
6    use objc2::{
7        ffi, msg_send,
8        rc::Retained,
9        runtime::{AnyObject, AnyProtocol, Imp, Sel},
10        sel,
11    };
12    use objc2_foundation::NSString;
13    use raw_window_handle::{HasWindowHandle, RawWindowHandle};
14
15    static INSTALL_TEXT_CONTENT: Once = Once::new();
16
17    thread_local! {
18        static CONTENT_TYPES: RefCell<HashMap<usize, Retained<NSString>>> =
19            RefCell::new(HashMap::new());
20    }
21
22    pub fn set_text_content_type(window: &Window, content_type: Option<&str>) {
23        let Some(view) = ns_view(window) else {
24            return;
25        };
26
27        INSTALL_TEXT_CONTENT.call_once(|| install_text_content(view));
28        if view
29            .class()
30            .instance_method(sel!(setContentType:))
31            .is_none()
32        {
33            return;
34        }
35
36        let ns_content_type = content_type.map(NSString::from_str);
37        let ns_content_type = ns_content_type.as_ref().map_or(ptr::null_mut(), |value| {
38            Retained::as_ptr(value).cast_mut().cast::<AnyObject>()
39        });
40
41        unsafe {
42            let _: () = msg_send![view, setContentType: ns_content_type];
43        }
44    }
45
46    fn ns_view(window: &Window) -> Option<&AnyObject> {
47        let handle = HasWindowHandle::window_handle(window).ok()?;
48        let RawWindowHandle::AppKit(handle) = handle.as_raw() else {
49            return None;
50        };
51
52        Some(unsafe { &*(handle.ns_view.as_ptr() as *const AnyObject) })
53    }
54
55    fn install_text_content(view: &AnyObject) {
56        let class = view.class();
57        let class = class as *const _ as *mut _;
58
59        unsafe {
60            let protocol = AnyProtocol::get(c"NSTextContent");
61            if let Some(protocol) = protocol {
62                ffi::class_addProtocol(class, protocol);
63            }
64
65            let content_type_imp: Imp = mem::transmute(
66                content_type as unsafe extern "C-unwind" fn(&AnyObject, Sel) -> *mut AnyObject,
67            );
68            let set_content_type_imp: Imp = mem::transmute(
69                set_content_type as unsafe extern "C-unwind" fn(&AnyObject, Sel, *mut AnyObject),
70            );
71
72            ffi::class_addMethod(class, sel!(contentType), content_type_imp, c"@@:".as_ptr());
73            ffi::class_addMethod(
74                class,
75                sel!(setContentType:),
76                set_content_type_imp,
77                c"v@:@".as_ptr(),
78            );
79        }
80    }
81
82    unsafe extern "C-unwind" fn content_type(this: &AnyObject, _: Sel) -> *mut AnyObject {
83        let key = this as *const _ as usize;
84        CONTENT_TYPES.with(|content_types| {
85            content_types
86                .borrow()
87                .get(&key)
88                .map_or(ptr::null_mut(), |value| {
89                    Retained::as_ptr(value).cast_mut().cast::<AnyObject>()
90                })
91        })
92    }
93
94    unsafe extern "C-unwind" fn set_content_type(this: &AnyObject, _: Sel, value: *mut AnyObject) {
95        let key = this as *const _ as usize;
96        CONTENT_TYPES.with(|content_types| {
97            let mut content_types = content_types.borrow_mut();
98            if value.is_null() {
99                content_types.remove(&key);
100            } else if let Some(value) = unsafe { Retained::retain(value.cast::<NSString>()) } {
101                content_types.insert(key, value);
102            }
103        });
104    }
105}
106
107#[cfg(target_os = "macos")]
108pub use macos::set_text_content_type;
109
110use gpui::SharedString;
111
112/// Presentation-independent context-menu model produced by the editor.
113#[derive(Default)]
114pub struct NativeMenu {
115    pub items: Vec<NativeMenuItem>,
116}
117
118pub enum NativeMenuItem {
119    Separator,
120    Action {
121        label: SharedString,
122        disabled: bool,
123        action: Box<dyn gpui::Action>,
124    },
125}
126
127impl NativeMenu {
128    pub fn new() -> Self {
129        Self::default()
130    }
131
132    pub fn menu(self, label: impl Into<SharedString>, action: Box<dyn gpui::Action>) -> Self {
133        self.menu_with_disabled(label, false, action)
134    }
135
136    pub fn menu_with_disabled(
137        mut self,
138        label: impl Into<SharedString>,
139        disabled: bool,
140        action: Box<dyn gpui::Action>,
141    ) -> Self {
142        self.items.push(NativeMenuItem::Action {
143            label: label.into(),
144            disabled,
145            action,
146        });
147        self
148    }
149
150    pub fn separator(mut self) -> Self {
151        self.items.push(NativeMenuItem::Separator);
152        self
153    }
154}