1624 lines · 59.8 KB · Rust
RawDownload
use super::{    attributed_string::{NSAttributedString, NSMutableAttributedString},    events::key_to_native,    renderer, screen_capture, BoolExt,};use crate::{    hash, Action, AnyWindowHandle, BackgroundExecutor, ClipboardEntry, ClipboardItem,    ClipboardString, CursorStyle, ForegroundExecutor, Image, ImageFormat, Keymap, MacDispatcher,    MacDisplay, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay,    PlatformTextSystem, PlatformWindow, Result, ScreenCaptureSource, SemanticVersion, Task,    WindowAppearance, WindowParams,};use anyhow::{anyhow, Context as _};use block::ConcreteBlock;use cocoa::{    appkit::{        NSApplication, NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,        NSEventModifierFlags, NSMenu, NSMenuItem, NSModalResponse, NSOpenPanel, NSPasteboard,        NSPasteboardTypePNG, NSPasteboardTypeRTF, NSPasteboardTypeRTFD, NSPasteboardTypeString,        NSPasteboardTypeTIFF, NSSavePanel, NSWindow,    },    base::{id, nil, selector, BOOL, NO, YES},    foundation::{        NSArray, NSAutoreleasePool, NSBundle, NSData, NSInteger, NSProcessInfo, NSRange, NSString,        NSUInteger, NSURL,    },};use core_foundation::{    base::{CFRelease, CFType, CFTypeRef, OSStatus, TCFType},    boolean::CFBoolean,    data::CFData,    dictionary::{CFDictionary, CFDictionaryRef, CFMutableDictionary},    runloop::CFRunLoopRun,    string::{CFString, CFStringRef},};use ctor::ctor;use futures::channel::oneshot;use objc::{    class,    declare::ClassDecl,    msg_send,    runtime::{Class, Object, Sel},    sel, sel_impl,};use parking_lot::Mutex;use ptr::null_mut;use std::{    cell::Cell,    convert::TryInto,    ffi::{c_void, CStr, OsStr},    os::{raw::c_char, unix::ffi::OsStrExt},    path::{Path, PathBuf},    process::Command,    ptr,    rc::Rc,    slice, str,    sync::Arc,};use strum::IntoEnumIterator;use util::ResultExt; #[allow(non_upper_case_globals)]const NSUTF8StringEncoding: NSUInteger = 4; const MAC_PLATFORM_IVAR: &str = "platform";static mut APP_CLASS: *const Class = ptr::null();static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); #[ctor]unsafe fn build_classes() {    APP_CLASS = {        let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);        decl.register()    };     APP_DELEGATE_CLASS = {        let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();        decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);        decl.add_method(            sel!(applicationDidFinishLaunching:),            did_finish_launching as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(applicationShouldHandleReopen:hasVisibleWindows:),            should_handle_reopen as extern "C" fn(&mut Object, Sel, id, bool),        );        decl.add_method(            sel!(applicationWillTerminate:),            will_terminate as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(handleGPUIMenuItem:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        // Add menu item handlers so that OS save panels have the correct key commands        decl.add_method(            sel!(cut:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(copy:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(paste:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(selectAll:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(undo:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(redo:),            handle_menu_item as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(validateMenuItem:),            validate_menu_item as extern "C" fn(&mut Object, Sel, id) -> bool,        );        decl.add_method(            sel!(menuWillOpen:),            menu_will_open as extern "C" fn(&mut Object, Sel, id),        );        decl.add_method(            sel!(applicationDockMenu:),            handle_dock_menu as extern "C" fn(&mut Object, Sel, id) -> id,        );        decl.add_method(            sel!(application:openURLs:),            open_urls as extern "C" fn(&mut Object, Sel, id, id),        );         decl.add_method(            sel!(onKeyboardLayoutChange:),            on_keyboard_layout_change as extern "C" fn(&mut Object, Sel, id),        );         decl.register()    }} pub(crate) struct MacPlatform(Mutex<MacPlatformState>); pub(crate) struct MacPlatformState {    background_executor: BackgroundExecutor,    foreground_executor: ForegroundExecutor,    text_system: Arc<dyn PlatformTextSystem>,    renderer_context: renderer::Context,    headless: bool,    pasteboard: id,    text_hash_pasteboard_type: id,    metadata_pasteboard_type: id,    reopen: Option<Box<dyn FnMut()>>,    on_keyboard_layout_change: Option<Box<dyn FnMut()>>,    quit: Option<Box<dyn FnMut()>>,    menu_command: Option<Box<dyn FnMut(&dyn Action)>>,    validate_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,    will_open_menu: Option<Box<dyn FnMut()>>,    menu_actions: Vec<Box<dyn Action>>,    open_urls: Option<Box<dyn FnMut(Vec<String>)>>,    finish_launching: Option<Box<dyn FnOnce()>>,    dock_menu: Option<id>,} impl Default for MacPlatform {    fn default() -> Self {        Self::new(false)    }} impl MacPlatform {    pub(crate) fn new(headless: bool) -> Self {        let dispatcher = Arc::new(MacDispatcher::new());         #[cfg(feature = "font-kit")]        let text_system = Arc::new(crate::MacTextSystem::new());         #[cfg(not(feature = "font-kit"))]        let text_system = Arc::new(crate::NoopTextSystem::new());         Self(Mutex::new(MacPlatformState {            headless,            text_system,            background_executor: BackgroundExecutor::new(dispatcher.clone()),            foreground_executor: ForegroundExecutor::new(dispatcher),            renderer_context: renderer::Context::default(),            pasteboard: unsafe { NSPasteboard::generalPasteboard(nil) },            text_hash_pasteboard_type: unsafe { ns_string("zed-text-hash") },            metadata_pasteboard_type: unsafe { ns_string("zed-metadata") },            reopen: None,            quit: None,            menu_command: None,            validate_menu_command: None,            will_open_menu: None,            menu_actions: Default::default(),            open_urls: None,            finish_launching: None,            dock_menu: None,            on_keyboard_layout_change: None,        }))    }     unsafe fn read_from_pasteboard(&self, pasteboard: *mut Object, kind: id) -> Option<&[u8]> {        let data = pasteboard.dataForType(kind);        if data == nil {            None        } else {            Some(slice::from_raw_parts(                data.bytes() as *mut u8,                data.length() as usize,            ))        }    }     unsafe fn create_menu_bar(        &self,        menus: Vec<Menu>,        delegate: id,        actions: &mut Vec<Box<dyn Action>>,        keymap: &Keymap,    ) -> id {        let application_menu = NSMenu::new(nil).autorelease();        application_menu.setDelegate_(delegate);         for menu_config in menus {            let menu = NSMenu::new(nil).autorelease();            let menu_title = ns_string(&menu_config.name);            menu.setTitle_(menu_title);            menu.setDelegate_(delegate);             for item_config in menu_config.items {                menu.addItem_(Self::create_menu_item(                    item_config,                    delegate,                    actions,                    keymap,                ));            }             let menu_item = NSMenuItem::new(nil).autorelease();            menu_item.setTitle_(menu_title);            menu_item.setSubmenu_(menu);            application_menu.addItem_(menu_item);             if menu_config.name == "Window" {                let app: id = msg_send![APP_CLASS, sharedApplication];                app.setWindowsMenu_(menu);            }        }         application_menu    }     unsafe fn create_dock_menu(        &self,        menu_items: Vec<MenuItem>,        delegate: id,        actions: &mut Vec<Box<dyn Action>>,        keymap: &Keymap,    ) -> id {        let dock_menu = NSMenu::new(nil);        dock_menu.setDelegate_(delegate);        for item_config in menu_items {            dock_menu.addItem_(Self::create_menu_item(                item_config,                delegate,                actions,                keymap,            ));        }         dock_menu    }     unsafe fn create_menu_item(        item: MenuItem,        delegate: id,        actions: &mut Vec<Box<dyn Action>>,        keymap: &Keymap,    ) -> id {        match item {            MenuItem::Separator => NSMenuItem::separatorItem(nil),            MenuItem::Action {                name,                action,                os_action,            } => {                let keystrokes = crate::Keymap::binding_to_display_from_bindings(                    keymap.bindings_for_action(action.as_ref()),                )                .map(|binding| binding.keystrokes());                 let selector = match os_action {                    Some(crate::OsAction::Cut) => selector("cut:"),                    Some(crate::OsAction::Copy) => selector("copy:"),                    Some(crate::OsAction::Paste) => selector("paste:"),                    Some(crate::OsAction::SelectAll) => selector("selectAll:"),                    // "undo:" and "redo:" are always disabled in our case, as                    // we don't have a NSTextView/NSTextField to enable them on.                    Some(crate::OsAction::Undo) => selector("handleGPUIMenuItem:"),                    Some(crate::OsAction::Redo) => selector("handleGPUIMenuItem:"),                    None => selector("handleGPUIMenuItem:"),                };                 let item;                if let Some(keystrokes) = keystrokes {                    if keystrokes.len() == 1 {                        let keystroke = &keystrokes[0];                        let mut mask = NSEventModifierFlags::empty();                        for (modifier, flag) in &[                            (                                keystroke.modifiers.platform,                                NSEventModifierFlags::NSCommandKeyMask,                            ),                            (                                keystroke.modifiers.control,                                NSEventModifierFlags::NSControlKeyMask,                            ),                            (                                keystroke.modifiers.alt,                                NSEventModifierFlags::NSAlternateKeyMask,                            ),                            (                                keystroke.modifiers.shift,                                NSEventModifierFlags::NSShiftKeyMask,                            ),                        ] {                            if *modifier {                                mask |= *flag;                            }                        }                         item = NSMenuItem::alloc(nil)                            .initWithTitle_action_keyEquivalent_(                                ns_string(&name),                                selector,                                ns_string(key_to_native(&keystroke.key).as_ref()),                            )                            .autorelease();                        if MacPlatform::os_version().unwrap() >= SemanticVersion::new(12, 0, 0) {                            let _: () =                                msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO];                        }                        item.setKeyEquivalentModifierMask_(mask);                    }                    // For multi-keystroke bindings, render the keystroke as part of the title.                    else {                        use std::fmt::Write;                         let mut name = format!("{name} [");                        for (i, keystroke) in keystrokes.iter().enumerate() {                            if i > 0 {                                name.push(' ');                            }                            write!(&mut name, "{}", keystroke).unwrap();                        }                        name.push(']');                         item = NSMenuItem::alloc(nil)                            .initWithTitle_action_keyEquivalent_(                                ns_string(&name),                                selector,                                ns_string(""),                            )                            .autorelease();                    }                } else {                    item = NSMenuItem::alloc(nil)                        .initWithTitle_action_keyEquivalent_(                            ns_string(&name),                            selector,                            ns_string(""),                        )                        .autorelease();                }                 let tag = actions.len() as NSInteger;                let _: () = msg_send![item, setTag: tag];                actions.push(action);                item            }            MenuItem::Submenu(Menu { name, items }) => {                let item = NSMenuItem::new(nil).autorelease();                let submenu = NSMenu::new(nil).autorelease();                submenu.setDelegate_(delegate);                for item in items {                    submenu.addItem_(Self::create_menu_item(item, delegate, actions, keymap));                }                item.setSubmenu_(submenu);                item.setTitle_(ns_string(&name));                if name == "Services" {                    let app: id = msg_send![APP_CLASS, sharedApplication];                    app.setServicesMenu_(item);                }                 item            }        }    }     fn os_version() -> Result<SemanticVersion> {        unsafe {            let process_info = NSProcessInfo::processInfo(nil);            let version = process_info.operatingSystemVersion();            Ok(SemanticVersion::new(                version.majorVersion as usize,                version.minorVersion as usize,                version.patchVersion as usize,            ))        }    }} impl Platform for MacPlatform {    fn background_executor(&self) -> BackgroundExecutor {        self.0.lock().background_executor.clone()    }     fn foreground_executor(&self) -> crate::ForegroundExecutor {        self.0.lock().foreground_executor.clone()    }     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {        self.0.lock().text_system.clone()    }     fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {        let mut state = self.0.lock();        if state.headless {            drop(state);            on_finish_launching();            unsafe { CFRunLoopRun() };        } else {            state.finish_launching = Some(on_finish_launching);            drop(state);        }         unsafe {            let app: id = msg_send![APP_CLASS, sharedApplication];            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];            app.setDelegate_(app_delegate);             let self_ptr = self as *const Self as *const c_void;            (*app).set_ivar(MAC_PLATFORM_IVAR, self_ptr);            (*app_delegate).set_ivar(MAC_PLATFORM_IVAR, self_ptr);             let pool = NSAutoreleasePool::new(nil);            app.run();            pool.drain();             (*app).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());            (*NSWindow::delegate(app)).set_ivar(MAC_PLATFORM_IVAR, null_mut::<c_void>());        }    }     fn quit(&self) {        // Quitting the app causes us to close windows, which invokes `Window::on_close` callbacks        // synchronously before this method terminates. If we call `Platform::quit` while holding a        // borrow of the app state (which most of the time we will do), we will end up        // double-borrowing the app state in the `on_close` callbacks for our open windows. To solve        // this, we make quitting the application asynchronous so that we aren't holding borrows to        // the app state on the stack when we actually terminate the app.         use super::dispatcher::{dispatch_get_main_queue, dispatch_sys::dispatch_async_f};         unsafe {            dispatch_async_f(dispatch_get_main_queue(), ptr::null_mut(), Some(quit));        }         unsafe extern "C" fn quit(_: *mut c_void) {            let app = NSApplication::sharedApplication(nil);            let _: () = msg_send![app, terminate: nil];        }    }     fn restart(&self, _binary_path: Option<PathBuf>) {        use std::os::unix::process::CommandExt as _;         let app_pid = std::process::id().to_string();        let app_path = self            .app_path()            .ok()            // When the app is not bundled, `app_path` returns the            // directory containing the executable. Disregard this            // and get the path to the executable itself.            .and_then(|path| (path.extension()?.to_str()? == "app").then_some(path))            .unwrap_or_else(|| std::env::current_exe().unwrap());         // Wait until this process has exited and then re-open this path.        let script = r#"            while kill -0 $0 2> /dev/null; do                sleep 0.1            done            open "$1"        "#;

Lines 1–500 of 1624.