This commit is contained in:
Nathan Sobo 2021-02-20 16:05:36 -07:00
parent b400449a58
commit 222f9d373d
16 changed files with 1164 additions and 179 deletions

64
.vscode/launch.json vendored Normal file
View File

@ -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}"
}
]
}

231
Cargo.lock generated
View File

@ -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"

View File

@ -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"

23
gpui/build.rs Normal file
View File

@ -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");
}

114
gpui/src/executor.rs Normal file
View File

@ -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)
}
}

View File

@ -1,3 +1,4 @@
pub mod executor;
pub mod keymap;
pub mod platform;

View File

@ -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 App {
pub fn new() -> Self {
Self {
dispatcher: Arc::new(Dispatcher),
}
}
}
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
impl platform::App for App {
fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
self.dispatcher.clone()
}
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) {
fn activate(&self, ignoring_other_apps: bool) {
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);
}
}
}
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()
}
}
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;
}
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)?))
}
}

View File

@ -0,0 +1 @@
#include <dispatch/dispatch.h>

View File

@ -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();
}
}
}

View File

@ -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,

View File

@ -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())
}
}

View File

@ -1,8 +1,34 @@
use super::*;
mod app;
mod dispatcher;
mod event;
mod geometry;
mod runner;
mod window;
pub fn app() -> impl App {
app::App::default()
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;
}
impl BoolExt for bool {
fn to_objc(self) -> BOOL {
if self {
YES
} else {
NO
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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>,
}

View File

@ -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();
}