WIP

Nathan Sobo created

Change summary

.vscode/launch.json                 |  64 +++++
Cargo.lock                          | 231 ++++++++++++++++++++
gpui/Cargo.toml                     |   6 
gpui/build.rs                       |  23 ++
gpui/src/executor.rs                | 114 ++++++++++
gpui/src/lib.rs                     |   1 
gpui/src/platform/mac/app.rs        | 185 ++--------------
gpui/src/platform/mac/dispatch.h    |   1 
gpui/src/platform/mac/dispatcher.rs |  43 +++
gpui/src/platform/mac/event.rs      |   3 
gpui/src/platform/mac/geometry.rs   |  26 ++
gpui/src/platform/mac/mod.rs        |  34 ++
gpui/src/platform/mac/runner.rs     | 188 ++++++++++++++++
gpui/src/platform/mac/window.rs     | 353 +++++++++++++++++++++++++++++++
gpui/src/platform/mod.rs            |  30 ++
zed/src/main.rs                     |  37 ++
16 files changed, 1,162 insertions(+), 177 deletions(-)

Detailed changes

.vscode/launch.json 🔗

@@ -0,0 +1,64 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug executable 'zed'",
+            "cargo": {
+                "args": [
+                    "build",
+                    "--bin=zed",
+                    "--package=zed"
+                ],
+                "filter": {
+                    "name": "zed",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in executable 'zed'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--bin=zed",
+                    "--package=zed"
+                ],
+                "filter": {
+                    "name": "zed",
+                    "kind": "bin"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        },
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug unit tests in library 'gpui'",
+            "cargo": {
+                "args": [
+                    "test",
+                    "--no-run",
+                    "--lib",
+                    "--package=gpui"
+                ],
+                "filter": {
+                    "name": "gpui",
+                    "kind": "lib"
+                }
+            },
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}

Cargo.lock 🔗

@@ -9,6 +9,15 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "anyhow"
 version = "1.0.38"
@@ -44,7 +53,7 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146"
 dependencies = [
- "async-task",
+ "async-task 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "concurrent-queue",
  "fastrand",
  "futures-lite",
@@ -126,12 +135,28 @@ version = "4.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0"
 
+[[package]]
+name = "async-task"
+version = "4.0.3"
+source = "git+https://github.com/zedit-io/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e#341b57d6de98cdfd7b418567b8de2022ca993a6e"
+
 [[package]]
 name = "atomic-waker"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a"
 
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.0.1"
@@ -144,6 +169,29 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
+[[package]]
+name = "bindgen"
+version = "0.57.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
 [[package]]
 name = "bitflags"
 version = "1.2.1"
@@ -174,7 +222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9"
 dependencies = [
  "async-channel",
- "async-task",
+ "async-task 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic-waker",
  "fastrand",
  "futures-lite",
@@ -193,6 +241,15 @@ version = "1.0.67"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
 
+[[package]]
+name = "cexpr"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
+dependencies = [
+ "nom",
+]
+
 [[package]]
 name = "cfg-if"
 version = "0.1.10"
@@ -218,6 +275,32 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "clang-sys"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
 [[package]]
 name = "cocoa"
 version = "0.24.0"
@@ -329,6 +412,16 @@ dependencies = [
  "loom",
 ]
 
+[[package]]
+name = "ctor"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
+dependencies = [
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "dirs"
 version = "3.0.1"
@@ -349,6 +442,19 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "env_logger"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
 [[package]]
 name = "event-listener"
 version = "2.5.1"
@@ -457,17 +563,27 @@ dependencies = [
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
 
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
 [[package]]
 name = "gpui"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "async-task 4.0.3 (git+https://github.com/zedit-io/async-task?rev=341b57d6de98cdfd7b418567b8de2022ca993a6e)",
+ "bindgen",
  "cocoa",
  "core-foundation",
  "core-text",
+ "ctor",
  "foreign-types 0.5.0",
  "log",
  "metal",
+ "num_cpus",
  "objc",
  "pathfinder_color",
  "pathfinder_geometry",
@@ -475,6 +591,21 @@ dependencies = [
  "tree-sitter",
 ]
 
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
 [[package]]
 name = "instant"
 version = "0.1.9"
@@ -490,12 +621,28 @@ version = "1.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
 [[package]]
 name = "libc"
 version = "0.2.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
 
+[[package]]
+name = "libloading"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
 [[package]]
 name = "log"
 version = "0.4.14"
@@ -555,6 +702,16 @@ dependencies = [
  "socket2",
 ]
 
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
 [[package]]
 name = "num-integer"
 version = "0.1.44"
@@ -574,6 +731,16 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
 [[package]]
 name = "objc"
 version = "0.2.7"
@@ -633,6 +800,12 @@ dependencies = [
  "rustc_version",
 ]
 
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.4"
@@ -717,6 +890,12 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
 [[package]]
 name = "rustc_version"
 version = "0.2.3"
@@ -747,6 +926,12 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 
+[[package]]
+name = "shlex"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
+
 [[package]]
 name = "signal-hook"
 version = "0.3.6"
@@ -806,6 +991,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
 [[package]]
 name = "syn"
 version = "1.0.60"
@@ -826,6 +1017,15 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
 [[package]]
 name = "thread_local"
 version = "1.1.3"
@@ -856,6 +1056,12 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.1"
@@ -868,6 +1074,18 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d"
 
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+
 [[package]]
 name = "waker-fn"
 version = "1.1.0"
@@ -895,6 +1113,15 @@ dependencies = [
  "cc",
 ]
 
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"

gpui/Cargo.toml 🔗

@@ -5,11 +5,17 @@ name = "gpui"
 version = "0.1.0"
 
 [dependencies]
+async-task = {git = "https://github.com/zedit-io/async-task", rev = "341b57d6de98cdfd7b418567b8de2022ca993a6e"}
+ctor = "0.1"
+num_cpus = "1.13"
 pathfinder_color = "0.5"
 pathfinder_geometry = "0.5"
 smol = "1.2"
 tree-sitter = "0.17"
 
+[build-dependencies]
+bindgen = "0.57"
+
 [target.'cfg(target_os = "macos")'.dependencies]
 anyhow = "1"
 cocoa = "0.24"

gpui/build.rs 🔗

@@ -0,0 +1,23 @@
+use std::{env, path::PathBuf};
+
+fn main() {
+    generate_dispatch_bindings();
+}
+
+fn generate_dispatch_bindings() {
+    println!("cargo:rustc-link-lib=framework=System");
+    println!("cargo:rerun-if-changed=src/platform/mac/dispatch.h");
+
+    let bindings = bindgen::Builder::default()
+        .header("src/platform/mac/dispatch.h")
+        .whitelist_var("_dispatch_main_q")
+        .whitelist_function("dispatch_async_f")
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
+        .generate()
+        .expect("unable to generate bindings");
+
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    bindings
+        .write_to_file(out_path.join("dispatch_sys.rs"))
+        .expect("couldn't write bindings");
+}

gpui/src/executor.rs 🔗

@@ -0,0 +1,114 @@
+// #[cfg(not(test))]
+use anyhow::{anyhow, Result};
+use async_task::Runnable;
+use smol::prelude::*;
+use smol::{channel, Executor};
+use std::rc::Rc;
+use std::sync::Arc;
+use std::{marker::PhantomData, thread};
+
+use crate::platform;
+
+pub enum Foreground {
+    Platform {
+        dispatcher: Arc<dyn platform::Dispatcher>,
+        _not_send_or_sync: PhantomData<Rc<()>>,
+    },
+    Test(smol::LocalExecutor<'static>),
+}
+
+pub enum ForegroundTask<T> {
+    Platform(async_task::Task<T>),
+    Test(smol::Task<T>),
+}
+
+pub struct Background {
+    executor: Arc<smol::Executor<'static>>,
+    _stop: channel::Sender<()>,
+}
+
+pub type BackgroundTask<T> = smol::Task<T>;
+
+impl Foreground {
+    pub fn platform(dispatcher: Arc<dyn platform::Dispatcher>) -> Result<Self> {
+        if dispatcher.is_main_thread() {
+            Ok(Self::Platform {
+                dispatcher,
+                _not_send_or_sync: PhantomData,
+            })
+        } else {
+            Err(anyhow!("must be constructed on main thread"))
+        }
+    }
+
+    pub fn test() -> Self {
+        Self::Test(smol::LocalExecutor::new())
+    }
+
+    pub fn spawn<T: 'static>(
+        &self,
+        future: impl Future<Output = T> + 'static,
+    ) -> ForegroundTask<T> {
+        match self {
+            Self::Platform { dispatcher, .. } => {
+                let dispatcher = dispatcher.clone();
+                let schedule = move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
+                let (runnable, task) = async_task::spawn_local(future, schedule);
+                runnable.schedule();
+                ForegroundTask::Platform(task)
+            }
+            Self::Test(executor) => ForegroundTask::Test(executor.spawn(future)),
+        }
+    }
+
+    pub async fn run<T>(&self, future: impl Future<Output = T>) -> T {
+        match self {
+            Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
+            Self::Test(executor) => executor.run(future).await,
+        }
+    }
+}
+
+impl<T> ForegroundTask<T> {
+    pub fn detach(self) {
+        match self {
+            Self::Platform(task) => task.detach(),
+            Self::Test(task) => task.detach(),
+        }
+    }
+
+    pub async fn cancel(self) -> Option<T> {
+        match self {
+            Self::Platform(task) => task.cancel().await,
+            Self::Test(task) => task.cancel().await,
+        }
+    }
+}
+
+impl Background {
+    pub fn new() -> Self {
+        let executor = Arc::new(Executor::new());
+        let stop = channel::unbounded::<()>();
+
+        for i in 0..num_cpus::get() {
+            let executor = executor.clone();
+            let stop = stop.1.clone();
+            thread::Builder::new()
+                .name(format!("background-executor-{}", i))
+                .spawn(move || smol::block_on(executor.run(stop.recv())))
+                .unwrap();
+        }
+
+        Self {
+            executor,
+            _stop: stop.0,
+        }
+    }
+
+    pub fn spawn<T>(&self, future: impl Send + Future<Output = T> + 'static) -> BackgroundTask<T>
+    where
+        T: 'static + Send,
+    {
+        self.executor.spawn(future)
+    }
+}

gpui/src/platform/mac/app.rs 🔗

@@ -1,176 +1,39 @@
-use super::Event;
-pub use cocoa::foundation::NSSize;
-use cocoa::{
-    base::{id, nil},
-    foundation::{NSArray, NSAutoreleasePool, NSString},
-};
-use objc::{
-    class,
-    declare::ClassDecl,
-    msg_send,
-    runtime::{Class, Object, Sel},
-    sel, sel_impl,
-};
-use std::{
-    ffi::CStr,
-    os::raw::{c_char, c_void},
-    path::PathBuf,
-};
+use super::{BoolExt as _, Dispatcher, Window};
+use crate::{executor, platform};
+use anyhow::Result;
+use cocoa::base::id;
+use objc::{class, msg_send, sel, sel_impl};
+use std::{rc::Rc, sync::Arc};
 
-#[derive(Default)]
 pub struct App {
-    finish_launching_callback: Option<Box<dyn FnOnce()>>,
-    become_active_callback: Option<Box<dyn FnMut()>>,
-    resign_active_callback: Option<Box<dyn FnMut()>>,
-    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
-    open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+    dispatcher: Arc<Dispatcher>,
 }
 
-const RUST_WRAPPER_IVAR_NAME: &'static str = "rustWrapper";
-
-impl super::App for App {
-    fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
-        self.finish_launching_callback = Some(Box::new(callback));
-        self
-    }
-
-    fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
-        self.become_active_callback = Some(Box::new(callback));
-        self
-    }
-
-    fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
-        self.resign_active_callback = Some(Box::new(callback));
-        self
-    }
-
-    fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
-        self.event_callback = Some(Box::new(callback));
-        self
-    }
-
-    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
-        self.open_files_callback = Some(Box::new(callback));
-        self
-    }
-
-    fn run(self) {
-        unsafe {
-            let self_ptr = Box::into_raw(Box::new(self));
-
-            let pool = NSAutoreleasePool::new(nil);
-            let app: id = msg_send![build_app_class(), sharedApplication];
-            (*app).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
-            let app_delegate: id = msg_send![build_app_delegate_class(), new];
-            (*app_delegate).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
-            let _: () = msg_send![app, setDelegate: app_delegate];
-            let _: () = msg_send![app, run];
-            let _: () = msg_send![pool, drain];
-
-            // App is done running when we get here, so we can reinstantiate the Box and drop it.
-            Box::from_raw(self_ptr);
+impl App {
+    pub fn new() -> Self {
+        Self {
+            dispatcher: Arc::new(Dispatcher),
         }
     }
 }
 
-fn build_app_class() -> *const Class {
-    unsafe {
-        let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
-        decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
-        decl.add_method(
-            sel!(sendEvent:),
-            send_event as extern "C" fn(&Object, Sel, id),
-        );
-        decl.register()
-    }
-}
-
-fn build_app_delegate_class() -> *const Class {
-    unsafe {
-        let superclass = class!(NSResponder);
-        let mut decl = ClassDecl::new("GPUIApplicationDelegate", superclass).unwrap();
-        decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
-        decl.add_method(
-            sel!(applicationDidFinishLaunching:),
-            did_finish_launching as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(applicationDidBecomeActive:),
-            did_become_active as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(applicationDidResignActive:),
-            did_resign_active as extern "C" fn(&Object, Sel, id),
-        );
-        decl.add_method(
-            sel!(application:openFiles:),
-            open_files as extern "C" fn(&Object, Sel, id, id),
-        );
-        decl.register()
+impl platform::App for App {
+    fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
+        self.dispatcher.clone()
     }
-}
-
-unsafe fn get_app(object: &Object) -> &mut App {
-    let wrapper_ptr: *mut c_void = *object.get_ivar(RUST_WRAPPER_IVAR_NAME);
-    &mut *(wrapper_ptr as *mut App)
-}
 
-extern "C" fn send_event(this: &Object, _sel: Sel, native_event: id) {
-    let event = unsafe { Event::from_native(native_event, None) };
-
-    if let Some(event) = event {
-        let app = unsafe { get_app(this) };
-        if let Some(callback) = app.event_callback.as_mut() {
-            if callback(event) {
-                return;
-            }
+    fn activate(&self, ignoring_other_apps: bool) {
+        unsafe {
+            let app: id = msg_send![class!(NSApplication), sharedApplication];
+            let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()];
         }
     }
 
-    unsafe {
-        let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
-    }
-}
-
-extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
-    let app = unsafe { get_app(this) };
-    if let Some(callback) = app.finish_launching_callback.take() {
-        callback();
-    }
-}
-
-extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
-    let app = unsafe { get_app(this) };
-    if let Some(callback) = app.become_active_callback.as_mut() {
-        callback();
-    }
-}
-
-extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
-    let app = unsafe { get_app(this) };
-    if let Some(callback) = app.resign_active_callback.as_mut() {
-        callback();
-    }
-}
-
-extern "C" fn open_files(this: &Object, _: Sel, _: id, paths: id) {
-    let paths = unsafe {
-        (0..paths.count())
-            .into_iter()
-            .filter_map(|i| {
-                let path = paths.objectAtIndex(i);
-                match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
-                    Ok(string) => Some(PathBuf::from(string)),
-                    Err(err) => {
-                        log::error!("error converting path to string: {}", err);
-                        None
-                    }
-                }
-            })
-            .collect::<Vec<_>>()
-    };
-    let app = unsafe { get_app(this) };
-    if let Some(callback) = app.open_files_callback.as_mut() {
-        callback(paths);
+    fn open_window(
+        &self,
+        options: platform::WindowOptions,
+        executor: Rc<executor::Foreground>,
+    ) -> Result<Rc<dyn platform::Window>> {
+        Ok(Rc::new(Window::open(options, executor)?))
     }
 }

gpui/src/platform/mac/dispatcher.rs 🔗

@@ -0,0 +1,43 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use async_task::Runnable;
+use objc::{
+    class, msg_send,
+    runtime::{BOOL, YES},
+    sel, sel_impl,
+};
+use std::ffi::c_void;
+
+use crate::platform;
+
+include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
+
+pub fn dispatch_get_main_queue() -> dispatch_queue_t {
+    unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
+}
+
+pub struct Dispatcher;
+
+impl platform::Dispatcher for Dispatcher {
+    fn is_main_thread(&self) -> bool {
+        let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
+        is_main_thread == YES
+    }
+
+    fn run_on_main_thread(&self, runnable: Runnable) {
+        unsafe {
+            dispatch_async_f(
+                dispatch_get_main_queue(),
+                runnable.into_raw() as *mut c_void,
+                Some(trampoline),
+            );
+        }
+
+        extern "C" fn trampoline(runnable: *mut c_void) {
+            let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
+            task.run();
+        }
+    }
+}

gpui/src/platform/mac/event.rs 🔗

@@ -1,5 +1,4 @@
-use super::Event;
-use crate::{geometry::vector::vec2f, keymap::Keystroke};
+use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
 use cocoa::appkit::{
     NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
     NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,

gpui/src/platform/mac/geometry.rs 🔗

@@ -0,0 +1,26 @@
+use cocoa::foundation::{NSPoint, NSRect, NSSize};
+use pathfinder_geometry::{rect::RectF, vector::Vector2F};
+pub trait Vector2FExt {
+    fn to_ns_point(&self) -> NSPoint;
+    fn to_ns_size(&self) -> NSSize;
+}
+
+pub trait RectFExt {
+    fn to_ns_rect(&self) -> NSRect;
+}
+
+impl Vector2FExt for Vector2F {
+    fn to_ns_point(&self) -> NSPoint {
+        NSPoint::new(self.x() as f64, self.y() as f64)
+    }
+
+    fn to_ns_size(&self) -> NSSize {
+        NSSize::new(self.x() as f64, self.y() as f64)
+    }
+}
+
+impl RectFExt for RectF {
+    fn to_ns_rect(&self) -> NSRect {
+        NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size())
+    }
+}

gpui/src/platform/mac/mod.rs 🔗

@@ -1,8 +1,34 @@
-use super::*;
-
 mod app;
+mod dispatcher;
 mod event;
+mod geometry;
+mod runner;
+mod window;
+
+use crate::platform;
+pub use app::App;
+use cocoa::base::{BOOL, NO, YES};
+pub use dispatcher::Dispatcher;
+pub use runner::Runner;
+use window::Window;
+
+pub fn app() -> impl platform::App {
+    App::new()
+}
+
+pub fn runner() -> impl platform::Runner {
+    Runner::new()
+}
+trait BoolExt {
+    fn to_objc(self) -> BOOL;
+}
 
-pub fn app() -> impl App {
-    app::App::default()
+impl BoolExt for bool {
+    fn to_objc(self) -> BOOL {
+        if self {
+            YES
+        } else {
+            NO
+        }
+    }
 }

gpui/src/platform/mac/runner.rs 🔗

@@ -0,0 +1,188 @@
+use crate::platform::Event;
+use cocoa::{
+    appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
+    base::{id, nil},
+    foundation::{NSArray, NSAutoreleasePool, NSString},
+};
+use ctor::ctor;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Sel},
+    sel, sel_impl,
+};
+use std::{
+    ffi::CStr,
+    os::raw::{c_char, c_void},
+    path::PathBuf,
+    ptr,
+};
+
+const RUNNER_IVAR: &'static str = "runner";
+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>(RUNNER_IVAR);
+        decl.add_method(
+            sel!(sendEvent:),
+            send_event as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.register()
+    };
+
+    APP_DELEGATE_CLASS = {
+        let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
+        decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
+        decl.add_method(
+            sel!(applicationDidFinishLaunching:),
+            did_finish_launching as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationDidBecomeActive:),
+            did_become_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(applicationDidResignActive:),
+            did_resign_active as extern "C" fn(&mut Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(application:openFiles:),
+            open_files as extern "C" fn(&mut Object, Sel, id, id),
+        );
+        decl.register()
+    }
+}
+
+#[derive(Default)]
+pub struct Runner {
+    finish_launching_callback: Option<Box<dyn FnOnce()>>,
+    become_active_callback: Option<Box<dyn FnMut()>>,
+    resign_active_callback: Option<Box<dyn FnMut()>>,
+    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+    open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+}
+
+impl Runner {
+    pub fn new() -> Self {
+        Default::default()
+    }
+}
+
+impl crate::platform::Runner for Runner {
+    fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
+        self.finish_launching_callback = Some(Box::new(callback));
+        self
+    }
+
+    fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
+        log::info!("become active");
+        self.become_active_callback = Some(Box::new(callback));
+        self
+    }
+
+    fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
+        self.resign_active_callback = Some(Box::new(callback));
+        self
+    }
+
+    fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
+        self.event_callback = Some(Box::new(callback));
+        self
+    }
+
+    fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
+        self.open_files_callback = Some(Box::new(callback));
+        self
+    }
+
+    fn run(self) {
+        unsafe {
+            let self_ptr = Box::into_raw(Box::new(self));
+
+            let pool = NSAutoreleasePool::new(nil);
+            let app: id = msg_send![APP_CLASS, sharedApplication];
+            let _: () = msg_send![
+                app,
+                setActivationPolicy: NSApplicationActivationPolicyRegular
+            ];
+            (*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
+            let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
+            (*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
+            let _: () = msg_send![app, setDelegate: app_delegate];
+            let _: () = msg_send![app, run];
+            let _: () = msg_send![pool, drain];
+            // The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
+            Box::from_raw(self_ptr);
+        }
+    }
+}
+
+unsafe fn get_runner(object: &mut Object) -> &mut Runner {
+    let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
+    &mut *(runner_ptr as *mut Runner)
+}
+
+extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
+    let event = unsafe { Event::from_native(native_event, None) };
+
+    if let Some(event) = event {
+        let runner = unsafe { get_runner(this) };
+        if let Some(callback) = runner.event_callback.as_mut() {
+            if callback(event) {
+                return;
+            }
+        }
+    }
+
+    unsafe {
+        let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
+    }
+}
+
+extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
+    let runner = unsafe { get_runner(this) };
+    if let Some(callback) = runner.finish_launching_callback.take() {
+        callback();
+    }
+}
+
+extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
+    let runner = unsafe { get_runner(this) };
+    if let Some(callback) = runner.become_active_callback.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
+    let runner = unsafe { get_runner(this) };
+    if let Some(callback) = runner.resign_active_callback.as_mut() {
+        callback();
+    }
+}
+
+extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
+    let paths = unsafe {
+        (0..paths.count())
+            .into_iter()
+            .filter_map(|i| {
+                let path = paths.objectAtIndex(i);
+                match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
+                    Ok(string) => Some(PathBuf::from(string)),
+                    Err(err) => {
+                        log::error!("error converting path to string: {}", err);
+                        None
+                    }
+                }
+            })
+            .collect::<Vec<_>>()
+    };
+    let runner = unsafe { get_runner(this) };
+    if let Some(callback) = runner.open_files_callback.as_mut() {
+        callback(paths);
+    }
+}

gpui/src/platform/mac/window.rs 🔗

@@ -0,0 +1,353 @@
+use crate::{
+    executor,
+    geometry::vector::Vector2F,
+    platform::{self, Event},
+};
+use anyhow::{anyhow, Result};
+use cocoa::{
+    appkit::{
+        NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable,
+        NSWindow, NSWindowStyleMask,
+    },
+    base::{id, nil},
+    foundation::{NSAutoreleasePool, NSSize, NSString},
+};
+use ctor::ctor;
+use objc::{
+    class,
+    declare::ClassDecl,
+    msg_send,
+    runtime::{Class, Object, Sel, BOOL, NO, YES},
+    sel, sel_impl,
+};
+use smol::Timer;
+use std::{
+    cell::{Cell, RefCell},
+    ffi::c_void,
+    mem, ptr,
+    rc::Rc,
+    time::{Duration, Instant},
+};
+
+use super::geometry::RectFExt;
+
+const WINDOW_STATE_IVAR: &'static str = "windowState";
+
+static mut WINDOW_CLASS: *const Class = ptr::null();
+static mut VIEW_CLASS: *const Class = ptr::null();
+static mut DELEGATE_CLASS: *const Class = ptr::null();
+
+#[ctor]
+unsafe fn build_classes() {
+    WINDOW_CLASS = {
+        let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
+        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+        decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
+        decl.add_method(
+            sel!(canBecomeMainWindow),
+            yes as extern "C" fn(&Object, Sel) -> BOOL,
+        );
+        decl.add_method(
+            sel!(canBecomeKeyWindow),
+            yes as extern "C" fn(&Object, Sel) -> BOOL,
+        );
+        decl.add_method(
+            sel!(sendEvent:),
+            send_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.register()
+    };
+
+    VIEW_CLASS = {
+        let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
+        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
+        decl.add_method(
+            sel!(keyDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseDown:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseUp:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(mouseDragged:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(scrollWheel:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
+        decl.register()
+    };
+
+    DELEGATE_CLASS = {
+        let mut decl = ClassDecl::new("GPUIWindowDelegate", class!(NSObject)).unwrap();
+        decl.add_method(
+            sel!(dealloc),
+            dealloc_delegate as extern "C" fn(&Object, Sel),
+        );
+        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
+        decl.add_method(
+            sel!(windowDidResize:),
+            window_did_resize as extern "C" fn(&Object, Sel, id),
+        );
+        decl.register()
+    };
+}
+
+pub struct Window(Rc<WindowState>);
+
+struct WindowState {
+    native_window: id,
+    event_callback: RefCell<Option<Box<dyn FnMut(Event) -> bool>>>,
+    resize_callback: RefCell<Option<Box<dyn FnMut(NSSize, f64)>>>,
+    synthetic_drag_counter: Cell<usize>,
+    executor: Rc<executor::Foreground>,
+}
+
+impl Window {
+    pub fn open(
+        options: platform::WindowOptions,
+        executor: Rc<executor::Foreground>,
+    ) -> Result<Self> {
+        unsafe {
+            let pool = NSAutoreleasePool::new(nil);
+
+            let frame = options.bounds.to_ns_rect();
+            let style_mask = NSWindowStyleMask::NSClosableWindowMask
+                | NSWindowStyleMask::NSMiniaturizableWindowMask
+                | NSWindowStyleMask::NSResizableWindowMask
+                | NSWindowStyleMask::NSTitledWindowMask;
+
+            let native_window: id = msg_send![WINDOW_CLASS, alloc];
+            let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
+                frame,
+                style_mask,
+                NSBackingStoreBuffered,
+                NO,
+            );
+
+            if native_window == nil {
+                return Err(anyhow!("window returned nil from initializer"));
+            }
+
+            let delegate: id = msg_send![DELEGATE_CLASS, alloc];
+            let delegate = delegate.init();
+            if native_window == nil {
+                return Err(anyhow!("delegate returned nil from initializer"));
+            }
+            native_window.setDelegate_(delegate);
+
+            let native_view: id = msg_send![VIEW_CLASS, alloc];
+            let native_view = NSView::init(native_view);
+            if native_view == nil {
+                return Err(anyhow!("view return nil from initializer"));
+            }
+
+            let window = Self(Rc::new(WindowState {
+                native_window,
+                event_callback: RefCell::new(None),
+                resize_callback: RefCell::new(None),
+                synthetic_drag_counter: Cell::new(0),
+                executor,
+            }));
+
+            (*native_window).set_ivar(
+                WINDOW_STATE_IVAR,
+                Rc::into_raw(window.0.clone()) as *const c_void,
+            );
+            (*native_view).set_ivar(
+                WINDOW_STATE_IVAR,
+                Rc::into_raw(window.0.clone()) as *const c_void,
+            );
+            (*delegate).set_ivar(
+                WINDOW_STATE_IVAR,
+                Rc::into_raw(window.0.clone()) as *const c_void,
+            );
+
+            if let Some(title) = options.title.as_ref() {
+                native_window.setTitle_(NSString::alloc(nil).init_str(title));
+            }
+            native_window.setAcceptsMouseMovedEvents_(YES);
+
+            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
+            native_view.setWantsBestResolutionOpenGLSurface_(YES);
+
+            // From winit crate: On Mojave, views automatically become layer-backed shortly after
+            // being added to a native_window. Changing the layer-backedness of a view breaks the
+            // association between the view and its associated OpenGL context. To work around this,
+            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
+            // itself and break the association with its context.
+            native_view.setWantsLayer(YES);
+
+            native_view.layer().setBackgroundColor_(
+                msg_send![class!(NSColor), colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0],
+            );
+
+            native_window.setContentView_(native_view.autorelease());
+            native_window.makeFirstResponder_(native_view);
+
+            native_window.center();
+            native_window.makeKeyAndOrderFront_(nil);
+
+            pool.drain();
+
+            Ok(window)
+        }
+    }
+
+    pub fn zoom(&self) {
+        unsafe {
+            self.0.native_window.performZoom_(nil);
+        }
+    }
+
+    pub fn size(&self) -> NSSize {
+        self.0.size()
+    }
+
+    pub fn backing_scale_factor(&self) -> f64 {
+        self.0.backing_scale_factor()
+    }
+
+    pub fn on_event<F: 'static + FnMut(Event) -> bool>(&mut self, callback: F) {
+        *self.0.event_callback.borrow_mut() = Some(Box::new(callback));
+    }
+
+    pub fn on_resize<F: 'static + FnMut(NSSize, f64)>(&mut self, callback: F) {
+        *self.0.resize_callback.borrow_mut() = Some(Box::new(callback));
+    }
+}
+
+impl Drop for Window {
+    fn drop(&mut self) {
+        unsafe {
+            self.0.native_window.close();
+            let _: () = msg_send![self.0.native_window.delegate(), release];
+        }
+    }
+}
+
+impl platform::Window for Window {}
+
+impl WindowState {
+    fn size(&self) -> NSSize {
+        let view_frame = unsafe { NSView::frame(self.native_window.contentView()) };
+        view_frame.size
+    }
+
+    fn backing_scale_factor(&self) -> f64 {
+        unsafe {
+            let screen: id = msg_send![self.native_window, screen];
+            NSScreen::backingScaleFactor(screen)
+        }
+    }
+
+    fn next_synthetic_drag_id(&self) -> usize {
+        let next_id = self.synthetic_drag_counter.get() + 1;
+        self.synthetic_drag_counter.set(next_id);
+        next_id
+    }
+}
+
+unsafe fn window_state(object: &Object) -> Rc<WindowState> {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    let rc1 = Rc::from_raw(raw as *mut WindowState);
+    let rc2 = rc1.clone();
+    mem::forget(rc1);
+    rc2
+}
+
+unsafe fn drop_window_state(object: &Object) {
+    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
+    Rc::from_raw(raw as *mut WindowState);
+}
+
+extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
+    YES
+}
+
+extern "C" fn dealloc_window(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let () = msg_send![super(this, class!(NSWindow)), dealloc];
+    }
+}
+
+extern "C" fn dealloc_view(this: &Object, _: Sel) {
+    unsafe {
+        drop_window_state(this);
+        let () = msg_send![super(this, class!(NSView)), dealloc];
+    }
+}
+
+extern "C" fn dealloc_delegate(this: &Object, _: Sel) {
+    unsafe {
+        let raw: *mut c_void = *this.get_ivar(WINDOW_STATE_IVAR);
+        Rc::from_raw(raw as *mut WindowState);
+        let () = msg_send![super(this, class!(NSObject)), dealloc];
+    }
+}
+
+extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
+    let window = unsafe { window_state(this) };
+
+    let event = unsafe { Event::from_native(native_event, Some(window.size().height as f32)) };
+
+    if let Some(event) = event {
+        match event {
+            Event::LeftMouseDragged { position } => schedule_synthetic_drag(&window, position),
+            Event::LeftMouseUp { .. } => {
+                window.next_synthetic_drag_id();
+            }
+            _ => {}
+        }
+
+        if let Some(callback) = window.event_callback.borrow_mut().as_mut() {
+            if callback(event) {
+                return;
+            }
+        }
+    }
+}
+
+extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
+    unsafe {
+        let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
+    }
+}
+
+extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
+    let window = unsafe { window_state(this) };
+    let size = window.size();
+    let scale_factor = window.backing_scale_factor();
+    if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
+        callback(size, scale_factor);
+    }
+    drop(window);
+}
+
+fn schedule_synthetic_drag(window_state: &Rc<WindowState>, position: Vector2F) {
+    let drag_id = window_state.next_synthetic_drag_id();
+    let weak_window_state = Rc::downgrade(window_state);
+    let instant = Instant::now() + Duration::from_millis(16);
+    window_state
+        .executor
+        .spawn(async move {
+            Timer::at(instant).await;
+            if let Some(window_state) = weak_window_state.upgrade() {
+                if window_state.synthetic_drag_counter.get() == drag_id {
+                    if let Some(callback) = window_state.event_callback.borrow_mut().as_mut() {
+                        schedule_synthetic_drag(&window_state, position);
+                        callback(Event::LeftMouseDragged { position });
+                    }
+                }
+            }
+        })
+        .detach();
+}

gpui/src/platform/mod.rs 🔗

@@ -6,11 +6,13 @@ pub mod current {
     pub use super::mac::*;
 }
 
-use std::path::PathBuf;
-
+use crate::{executor, geometry::rect::RectF};
+use anyhow::Result;
+use async_task::Runnable;
 use event::Event;
+use std::{path::PathBuf, rc::Rc, sync::Arc};
 
-pub trait App {
+pub trait Runner {
     fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self where;
     fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
     fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
@@ -18,3 +20,25 @@ pub trait App {
     fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
     fn run(self);
 }
+
+pub trait App {
+    fn dispatcher(&self) -> Arc<dyn Dispatcher>;
+    fn activate(&self, ignoring_other_apps: bool);
+    fn open_window(
+        &self,
+        options: WindowOptions,
+        executor: Rc<executor::Foreground>,
+    ) -> Result<Rc<dyn Window>>;
+}
+
+pub trait Dispatcher: Send + Sync {
+    fn is_main_thread(&self) -> bool;
+    fn run_on_main_thread(&self, task: Runnable);
+}
+
+pub trait Window {}
+
+pub struct WindowOptions<'a> {
+    pub bounds: RectF,
+    pub title: Option<&'a str>,
+}

zed/src/main.rs 🔗

@@ -1,14 +1,41 @@
-use std::fs;
-
 use fs::OpenOptions;
-use gpui::platform::{current as platform, App as _};
+use gpui::{
+    executor,
+    geometry::{rect::RectF, vector::vec2f},
+    platform::{current as platform, App as _, Runner as _, WindowOptions},
+};
 use log::LevelFilter;
 use simplelog::SimpleLogger;
+use std::{fs, mem, rc::Rc, sync::Arc};
 
 fn main() {
     init_logger();
-    platform::app()
-        .on_finish_launching(|| log::info!("finish launching"))
+
+    let platform = Arc::new(platform::app());
+
+    let foreground = Rc::new(
+        executor::Foreground::platform(platform.dispatcher())
+            .expect("could not foreground create executor"),
+    );
+
+    platform::runner()
+        .on_finish_launching(move || {
+            log::info!("finish launching");
+            if stdout_is_a_pty() {
+                platform.activate(true);
+            }
+            let window = platform
+                .open_window(
+                    WindowOptions {
+                        bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)),
+                        title: Some("Zed"),
+                    },
+                    foreground,
+                )
+                .expect("error opening window");
+
+            mem::forget(window); // Leak window for now so it doesn't close
+        })
         .run();
 }